1 module nulib.uuid; 2 import nulib.random; 3 import nulib.string; 4 import nulib.memory.endian; 5 import nulib.text.ascii; 6 import nulib.conv; 7 import numem; 8 9 /** 10 UUID Variants 11 */ 12 enum UUIDVariant { 13 ncs, 14 rfc4122, 15 microsoft, 16 reserved 17 } 18 19 /** 20 Creates a UUID object at compile time from a UUID string. 21 22 Params: 23 uuid = The UUID string to generate a UUID object from. 24 forceMS = Whether to force the microsoft format. 25 */ 26 template CTUUID(string uuid, bool forceMS = false) { 27 UUID genUUID(string slice) { 28 UUID uuid; 29 30 if (__ctfe) { 31 if (!UUID.validate(slice)) 32 return uuid; 33 34 // Get from string 35 uuid.time_low = parseHex!uint(slice[0..8]); 36 uuid.time_mid = parseHex!ushort(slice[9..13]); 37 uuid.time_hi_and_version = parseHex!ushort(slice[14..18]); 38 uuid.clk_seq = parseHex!ushort(slice[19..23]); 39 40 // Get bytes 41 foreach(i; 0..UUID.node.length) { 42 uuid.node[i] = parseHex!ubyte(slice[24+(i*2)..24+(i*2)+2]); 43 } 44 45 // Flip clk_seq if need be. 46 uuid.handleMsFormat(forceMS); 47 } 48 return uuid; 49 } 50 51 enum CTUUID = genUUID(uuid); 52 } 53 54 /** 55 RFC4122 compliant UUIDs 56 */ 57 struct UUID { 58 @nogc nothrow: 59 private: 60 enum VERSION_BITMASK = 0b11110000_00000000; 61 62 // NCS Variant Bits 63 enum V_NCS_BITMASK = 0b11000000_00000000; 64 enum V_NCS_BITS = 0b10000000_00000000; 65 66 // RFC Variant Bits 67 enum V_RFC_BITMASK = 0b11100000_00000000; 68 enum V_RFC_BITS = 0b11000000_00000000; 69 70 // Microsoft Variant Bits 71 enum V_MSF_BITMASK = 0b11100000_00000000; 72 enum V_MSF_BITS = 0b11100000_00000000; 73 74 union { 75 struct { 76 uint time_low; 77 ushort time_mid; 78 ushort time_hi_and_version; 79 ushort clk_seq; 80 ubyte[6] node; 81 } 82 ubyte[16] bdata; 83 ulong[2] ldata; 84 } 85 86 this(ulong[2] data) inout { 87 this.ldata = data; 88 } 89 90 // Helper that swaps bytes in MS format. 91 void handleMsFormat(bool force = false) { 92 if ((clk_seq & V_MSF_BITMASK) == V_MSF_BITS || force) { 93 ushort old_seq = clk_seq; 94 this.clk_seq = 95 (old_seq >> 8) | 96 ((old_seq & 0xFF) << 8); 97 } 98 } 99 100 public: 101 102 /** 103 Length of a UUID string 104 */ 105 enum uuidStringLength = 36; 106 107 /** 108 Special "nil" UUID 109 */ 110 enum UUID nil = UUID([0LU, 0LU]); 111 alias min = nil; 112 113 /** 114 Special "max" UUID 115 */ 116 enum UUID max = UUID([ulong.max, ulong.max]); 117 118 /** 119 The underlying data of the UUID. 120 */ 121 @property ubyte[] data() return { return this.bdata[0..$]; } 122 123 /** 124 The version of the UUID structure 125 */ 126 @property int uuidVersion() { 127 return cast(int)(time_hi_and_version >> 12); 128 } 129 130 deprecated("use .uuidVersion instead!") 131 alias getVersion = uuidVersion; 132 133 /** 134 The variant of the UUID structure 135 */ 136 @property UUIDVariant uuidVariant() { 137 if ((clk_seq & V_MSF_BITMASK) == V_MSF_BITS) 138 return UUIDVariant.microsoft; 139 if ((clk_seq & V_RFC_BITMASK) == V_RFC_BITS) 140 return UUIDVariant.rfc4122; 141 if ((clk_seq & V_NCS_BITMASK) == V_NCS_BITS) 142 return UUIDVariant.ncs; 143 144 return UUIDVariant.reserved; 145 } 146 147 deprecated("use .uuidVariant instead!") 148 alias getVariant = uuidVariant; 149 150 /** 151 Constructs a UUID from the specified byte slice 152 */ 153 this(ubyte[16] bytes) { 154 this.bdata = bytes; 155 this.time_low = nu_ntoh(this.time_low); 156 this.time_mid = nu_ntoh(this.time_mid); 157 this.time_hi_and_version = nu_ntoh(this.time_hi_and_version); 158 } 159 160 /** 161 Provides compatibility with .NET's Guid struct. 162 */ 163 this(uint time_low, ushort time_mid, ushort time_hi_and_version, ubyte clk0, ubyte clk1, ubyte d0, ubyte d1, ubyte d2, ubyte d3, ubyte d4, ubyte d5) { 164 this.time_low = time_low; 165 this.time_mid = time_mid; 166 this.time_hi_and_version = time_hi_and_version; 167 this.clk_seq = clk0 << 8 | clk1; 168 this.node[0] = d0; 169 this.node[1] = d1; 170 this.node[2] = d2; 171 this.node[3] = d3; 172 this.node[4] = d4; 173 this.node[5] = d5; 174 175 this.handleMsFormat(); 176 } 177 178 /** 179 Constructs a UUID from the specified string. 180 */ 181 this(string text) { 182 this = UUID.parse(text); 183 } 184 185 /** 186 Validates the correctness of a UUID string. 187 188 Params: 189 slice = The string slice to validate 190 191 Returns: 192 $(D true) if the slice is a valid UUID string, 193 $(D false) otherwise. 194 */ 195 static bool validate(string slice) { 196 197 // Incorrect length 198 if (slice.length != UUID.uuidStringLength) 199 return false; 200 201 foreach(i; 0..UUID.uuidStringLength) { 202 203 // Dash positions 204 if (i == 8 || i == 13 || i == 18 || i == 23) { 205 if (slice[i] != '-') 206 return false; 207 else 208 continue; 209 } 210 211 // Hex positions 212 if (!isHex(slice[i])) 213 return false; 214 } 215 216 return true; 217 } 218 219 /** 220 Creates a new UUIDv3 with a specified random number generator. 221 222 Params: 223 random = The random number generator to use. 224 225 Returns: 226 A new psuedorandomly generated UUID. 227 */ 228 static UUID createRandom(RandomBase random) { 229 UUID uuid; 230 uuid.time_hi_and_version &= 0x0FFF; 231 uuid.time_hi_and_version |= (3 << 12); 232 uuid.clk_seq &= 0x3F; 233 uuid.clk_seq |= 0x80; 234 235 ubyte[] dst = uuid.node[0..$]; 236 random.next(dst); 237 return uuid; 238 } 239 240 /** 241 Tries to parse a UUID from a string. 242 243 Params: 244 slice = The string slice to parse. 245 246 Returns: 247 A new UUID parsed from the string, or a nil UUID on failure. 248 */ 249 static UUID parse(string slice) { 250 if (!UUID.validate(slice)) 251 return UUID.nil; 252 253 UUID uuid; 254 255 // Get from string 256 uuid.time_low = slice[0..8].toInt!uint(16); 257 uuid.time_mid = slice[9..13].toInt!ushort(16); 258 uuid.time_hi_and_version = slice[14..18].toInt!ushort(16); 259 uuid.clk_seq = slice[19..23].toInt!ushort(16); 260 261 // Get bytes 262 foreach(i; 0..6) { 263 size_t start = 24+(i*2); 264 uuid.node[i] = slice[start..start+2].toInt!ubyte(16); 265 } 266 267 uuid.handleMsFormat(); 268 return uuid; 269 } 270 271 /** 272 Returns byte stream from UUID with data in network order. 273 */ 274 ubyte[16] toBytes() { 275 UUID datacopy; 276 datacopy.bdata[0..$] = bdata[0..$]; 277 278 datacopy.time_low = nu_ntoh(this.time_low); 279 datacopy.time_mid = nu_ntoh(this.time_mid); 280 datacopy.time_hi_and_version = nu_ntoh(this.time_hi_and_version); 281 return datacopy.bdata; 282 } 283 284 /** 285 Returns UUID string 286 */ 287 nstring toString() { 288 import nulib.c.stdio : snprintf; 289 290 char[uuidStringLength+1] buffer; 291 snprintf( 292 cast(char*)buffer.ptr, 293 buffer.length, 294 "%0.8x-%0.4hx-%0.4hx-%0.4hx-%.2hhx%.2hhx%.2hhx%.2hhx%.2hhx%.2hhx", 295 time_low, 296 time_mid, 297 time_hi_and_version, 298 clk_seq, 299 node[0], 300 node[1], 301 node[2], 302 node[3], 303 node[4], 304 node[5], 305 ); 306 307 nstring ret = nstring(cast(string)buffer[0..uuidStringLength]); 308 return ret; 309 } 310 311 /** 312 Checks equality between 2 UUIDs. 313 */ 314 bool opEquals(const UUID other) const { 315 return this.ldata[0] == other.ldata[0] && this.ldata[1] == other.ldata[1]; 316 } 317 318 /** 319 Compares 2 UUIDs lexically. 320 321 Lexical order is NOT temporal! 322 */ 323 int opCmp(const UUID other) const { 324 325 // First check things which are endian dependent. 326 if (this.time_low != other.time_low) 327 return this.time_low < other.time_low; 328 329 if (this.time_mid != other.time_mid) 330 return this.time_mid < other.time_mid; 331 332 if (this.time_hi_and_version != other.time_hi_and_version) 333 return this.time_hi_and_version < other.time_hi_and_version; 334 335 // Then check all the nodes 336 static foreach(i; 0..node.length) { 337 if (this.node[i] < other.node[i]) return -1; 338 if (this.node[i] > other.node[i]) return 1; 339 } 340 341 // They're the same. 342 return 0; 343 } 344 } 345 346 @("uuid: parsing") 347 unittest { 348 const string[4] tests = [ 349 "ce1a553c-762d-11ef-b864-0242ac120002", 350 "56ba8f28-d9aa-4452-80c1-4ce66d064f6c", 351 "01920815-46af-71fa-9025-35d7592a401b", 352 "7f204bc7-53fe-4ffb-acaf-3f0cd0cf69cb" 353 ]; 354 355 const UUIDVariant[4] variants = [ 356 UUIDVariant.ncs, 357 UUIDVariant.ncs, 358 UUIDVariant.ncs, 359 UUIDVariant.ncs, 360 ]; 361 362 const int[4] versions = [ 363 1, 364 4, 365 7, 366 4 367 ]; 368 369 foreach(i; 0..4) { 370 import std.format : format; 371 import std.conv : text; 372 UUID uuid = UUID.parse(tests[i]); 373 374 assert( 375 uuid.uuidVariant == variants[i], 376 "Expected %s, got %s".format(variants[i], uuid.uuidVariant) 377 ); 378 379 assert( 380 uuid.uuidVersion == versions[i], 381 "uuid=%s (test %s), version=%s, expected=%s!".format(tests[i], i+1, uuid.uuidVersion, versions[i]) 382 ); 383 } 384 } 385 386 @("uuid: comparison") 387 unittest { 388 UUID uuid1 = UUID.parse("ce1a553c-762d-11ef-b864-0242ac120002"); 389 UUID uuid2 = UUID.parse("56ba8f28-d9aa-4452-80c1-4ce66d064f6c"); 390 UUID uuid3 = UUID.parse("ce1a553c-762d-11ef-b864-0242ac120002"); 391 392 assert(uuid1 == uuid3); 393 assert(uuid1 != uuid2); 394 } 395 396 @("uuid: toString") 397 unittest { 398 import std.format : format; 399 400 UUID uuid1 = UUID.parse("ce1a553c-762d-11ef-b864-0242ac120002"); 401 nstring str = uuid1.toString(); 402 assert(str == "ce1a553c-762d-11ef-b864-0242ac120002", "Expected %s, got %s".format("ce1a553c-762d-11ef-b864-0242ac120002", str[])); 403 } 404 405 @("uuid: generation") 406 unittest { 407 import numem.core; 408 409 Random random = nogc_new!Random(42); 410 scope(exit) nogc_delete(random); 411 412 UUID uuid = UUID.createRandom(random); 413 scope(exit) nogc_delete(uuid); 414 assert(uuid != UUID.nil); 415 } 416 417 @("uuid: UUID.validate") 418 unittest { 419 assert( UUID.validate("6ba7b810-9dad-11d1-80b4-00c04fd430c8")); 420 assert( UUID.validate("449ed9d5-6810-44c6-9506-504ceeb27e0d")); 421 assert(!UUID.validate("")); 422 assert(!UUID.validate("6ba7b81i-9dad-11d1-80b4-00c04fd430c8")); 423 assert(!UUID.validate("\0\0\0\0\0\0\0\0\0\0\0\0\0\0")); 424 }