mirror of
https://github.com/dlang/phobos.git
synced 2025-04-26 13:10:35 +03:00

All characters of interest to std.uuid.parseUUID are ASCII so there is no reason it cannot work on a range of chars or wchars.
1758 lines
57 KiB
D
1758 lines
57 KiB
D
/**
|
|
* A $(LINK2 http://en.wikipedia.org/wiki/Universally_unique_identifier, UUID), or
|
|
* $(LINK2 http://en.wikipedia.org/wiki/Universally_unique_identifier, Universally unique identifier),
|
|
* is intended to uniquely identify information in a distributed environment
|
|
* without significant central coordination. It can be
|
|
* used to tag objects with very short lifetimes, or to reliably identify very
|
|
* persistent objects across a network.
|
|
*
|
|
$(SCRIPT inhibitQuickIndex = 1;)
|
|
|
|
$(DIVC quickindex,
|
|
$(BOOKTABLE ,
|
|
$(TR $(TH Category) $(TH Functions)
|
|
)
|
|
$(TR $(TDNW Parsing UUIDs)
|
|
$(TD $(MYREF parseUUID)
|
|
$(MYREF UUID)
|
|
$(MYREF UUIDParsingException)
|
|
$(MYREF uuidRegex)
|
|
)
|
|
)
|
|
$(TR $(TDNW Generating UUIDs)
|
|
$(TD $(MYREF sha1UUID)
|
|
$(MYREF randomUUID)
|
|
$(MYREF md5UUID)
|
|
)
|
|
)
|
|
$(TR $(TDNW Using UUIDs)
|
|
$(TD $(MYREF2 UUID.uuidVersion, uuidVersion)
|
|
$(MYREF2 UUID.variant, variant)
|
|
$(MYREF2 UUID.toString, toString)
|
|
$(MYREF2 UUID.data, data)
|
|
$(MYREF2 UUID.swap, swap)
|
|
$(MYREF2 UUID.opEquals, opEquals)
|
|
$(MYREF2 UUID.opCmp, opCmp)
|
|
$(MYREF2 UUID.toHash, toHash)
|
|
)
|
|
)
|
|
$(TR $(TDNW UUID namespaces)
|
|
$(TD $(MYREF dnsNamespace)
|
|
$(MYREF urlNamespace)
|
|
$(MYREF oidNamespace)
|
|
$(MYREF x500Namespace)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
* UUIDs have many applications. Some examples follow: Databases may use UUIDs to identify
|
|
* rows or records in order to ensure that they are unique across different
|
|
* databases, or for publication/subscription services. Network messages may be
|
|
* identified with a UUID to ensure that different parts of a message are put back together
|
|
* again. Distributed computing may use UUIDs to identify a remote procedure call.
|
|
* Transactions and classes involved in serialization may be identified by UUIDs.
|
|
* Microsoft's component object model (COM) uses UUIDs to distinguish different software
|
|
* component interfaces. UUIDs are inserted into documents from Microsoft Office programs.
|
|
* UUIDs identify audio or video streams in the Advanced Systems Format (ASF). UUIDs are
|
|
* also a basis for OIDs (object identifiers), and URNs (uniform resource name).
|
|
*
|
|
* An attractive feature of UUIDs when compared to alternatives is their relative small size,
|
|
* of 128 bits, or 16 bytes. Another is that the creation of UUIDs does not require
|
|
* a centralized authority.
|
|
*
|
|
* When UUIDs are generated by one of the defined mechanisms, they are either guaranteed
|
|
* to be unique, different from all other generated UUIDs (that is, it has never been
|
|
* generated before and it will never be generated again), or it is extremely likely
|
|
* to be unique (depending on the mechanism).
|
|
*
|
|
* For efficiency, UUID is implemented as a struct. UUIDs are therefore empty if not explicitly
|
|
* initialized. An UUID is empty if $(MYREF3 UUID.empty, empty) is true. Empty UUIDs are equal to
|
|
* `UUID.init`, which is a UUID with all 16 bytes set to 0.
|
|
* Use UUID's constructors or the UUID generator functions to get an initialized UUID.
|
|
*
|
|
* This is a port of $(LINK2 http://www.boost.org/doc/libs/1_42_0/libs/uuid/uuid.html,
|
|
* boost.uuid) from the Boost project with some minor additions and API
|
|
* changes for a more D-like API.
|
|
*
|
|
* Standards:
|
|
* $(LINK2 http://www.ietf.org/rfc/rfc4122.txt, RFC 4122)
|
|
*
|
|
* See_Also:
|
|
* $(LINK http://en.wikipedia.org/wiki/Universally_unique_identifier)
|
|
*
|
|
* Copyright: Copyright Johannes Pfau 2011 - .
|
|
* License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
|
|
* Authors: Johannes Pfau
|
|
* Source: $(PHOBOSSRC std/uuid.d)
|
|
*
|
|
* Macros:
|
|
* MYREF2 = <a href="#$2">$(TT $1)</a>
|
|
* MYREF3 = <a href="#$2">`$1`</a>
|
|
*/
|
|
/* Copyright Johannes Pfau 2011 - 2012.
|
|
* Distributed under the Boost Software License, Version 1.0.
|
|
* (See accompanying file LICENSE_1_0.txt or copy at
|
|
* http://www.boost.org/LICENSE_1_0.txt)
|
|
*/
|
|
module std.uuid;
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.uuid;
|
|
|
|
UUID[] ids;
|
|
ids ~= randomUUID();
|
|
ids ~= md5UUID("test.name.123");
|
|
ids ~= sha1UUID("test.name.123");
|
|
|
|
foreach (entry; ids)
|
|
{
|
|
assert(entry.variant == UUID.Variant.rfc4122);
|
|
}
|
|
assert(ids[0].uuidVersion == UUID.Version.randomNumberBased);
|
|
assert(ids[1].toString() == "22390768-cced-325f-8f0f-cfeaa19d0ccd");
|
|
assert(ids[1].data == [34, 57, 7, 104, 204, 237, 50, 95, 143, 15, 207,
|
|
234, 161, 157, 12, 205]);
|
|
UUID id;
|
|
assert(id.empty);
|
|
}
|
|
|
|
import std.range.primitives;
|
|
import std.traits;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
public struct UUID
|
|
{
|
|
import std.meta : AliasSeq, allSatisfy;
|
|
|
|
private:
|
|
alias skipSeq = AliasSeq!(8, 13, 18, 23);
|
|
alias byteSeq = AliasSeq!(0,2,4,6,9,11,14,16,19,21,24,26,28,30,32,34);
|
|
|
|
@safe pure nothrow @nogc Char toChar(Char)(size_t i) const
|
|
{
|
|
if (i <= 9)
|
|
return cast(Char)('0' + i);
|
|
else
|
|
return cast(Char)('a' + (i-10));
|
|
}
|
|
|
|
@safe pure nothrow unittest
|
|
{
|
|
assert(UUID(cast(ubyte[16])[138, 179, 6, 14, 44, 186, 79, 35, 183, 76, 181, 45,
|
|
179, 189, 251, 70]).toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
|
|
}
|
|
|
|
// Reinterpret the UUID as an array of some other primitive.
|
|
@trusted ref T[16 / T.sizeof] asArrayOf(T)() return
|
|
if (isIntegral!T)
|
|
{
|
|
return *cast(typeof(return)*)&data;
|
|
}
|
|
|
|
public:
|
|
/**
|
|
* RFC 4122 defines different internal data layouts for UUIDs. These are
|
|
* the UUID formats supported by this module. It's
|
|
* possible to read, compare and use all these Variants, but
|
|
* UUIDs generated by this module will always be in rfc4122 format.
|
|
*
|
|
* Note: Do not confuse this with $(REF _Variant, std,_variant).
|
|
*/
|
|
enum Variant
|
|
{
|
|
ncs, /// NCS backward compatibility
|
|
rfc4122, /// Defined in RFC 4122 document
|
|
microsoft, /// Microsoft Corporation backward compatibility
|
|
future ///Reserved for future use
|
|
}
|
|
|
|
/**
|
|
* RFC 4122 defines different UUID versions. The version shows
|
|
* how a UUID was generated, e.g. a version 4 UUID was generated
|
|
* from a random number, a version 3 UUID from an MD5 hash of a name.
|
|
*
|
|
* Note:
|
|
* All of these UUID versions can be read and processed by
|
|
* `std.uuid`, but only version 3, 4 and 5 UUIDs can be generated.
|
|
*/
|
|
enum Version
|
|
{
|
|
///Unknown version
|
|
unknown = -1,
|
|
///Version 1
|
|
timeBased = 1,
|
|
///Version 2
|
|
dceSecurity = 2,
|
|
///Version 3 (Name based + MD5)
|
|
nameBasedMD5 = 3,
|
|
///Version 4 (Random)
|
|
randomNumberBased = 4,
|
|
///Version 5 (Name based + SHA-1)
|
|
nameBasedSHA1 = 5
|
|
}
|
|
|
|
union
|
|
{
|
|
/**
|
|
* It is sometimes useful to get or set the 16 bytes of a UUID
|
|
* directly.
|
|
*
|
|
* Note:
|
|
* UUID uses a 16-ubyte representation for the UUID data.
|
|
* RFC 4122 defines a UUID as a special structure in big-endian
|
|
* format. These 16-ubytes always equal the big-endian structure
|
|
* defined in RFC 4122.
|
|
*
|
|
* Example:
|
|
* -----------------------------------------------
|
|
* auto rawData = uuid.data; //get data
|
|
* rawData[0] = 1; //modify
|
|
* uuid.data = rawData; //set data
|
|
* uuid.data[1] = 2; //modify directly
|
|
* -----------------------------------------------
|
|
*/
|
|
ubyte[16] data;
|
|
private ulong[2] ulongs;
|
|
static if (size_t.sizeof == 4)
|
|
private uint[4] uints;
|
|
}
|
|
|
|
/*
|
|
* We could use a union here to also provide access to the
|
|
* fields specified in RFC 4122, but as we never have to access
|
|
* those (only necessary for version 1 (and maybe 2) UUIDs),
|
|
* that is not needed right now.
|
|
*/
|
|
|
|
@safe pure unittest
|
|
{
|
|
UUID tmp;
|
|
tmp.data = cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11,12,
|
|
13,14,15];
|
|
assert(tmp.data == cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11,
|
|
12,13,14,15]);
|
|
tmp.data[2] = 3;
|
|
assert(tmp.data == cast(ubyte[16])[0,1,3,3,4,5,6,7,8,9,10,11,
|
|
12,13,14,15]);
|
|
|
|
auto tmp2 = cast(immutable UUID) tmp;
|
|
assert(tmp2.data == cast(ubyte[16])[0,1,3,3,4,5,6,7,8,9,10,11,
|
|
12,13,14,15]);
|
|
}
|
|
|
|
/**
|
|
* Construct a UUID struct from the 16 byte representation
|
|
* of a UUID.
|
|
*/
|
|
@safe pure nothrow @nogc this(ref const scope ubyte[16] uuidData)
|
|
{
|
|
data = uuidData;
|
|
}
|
|
/// ditto
|
|
@safe pure nothrow @nogc this(const ubyte[16] uuidData)
|
|
{
|
|
data = uuidData;
|
|
}
|
|
|
|
///
|
|
@safe pure unittest
|
|
{
|
|
enum ubyte[16] data = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
|
|
auto uuid = UUID(data);
|
|
enum ctfe = UUID(data);
|
|
assert(uuid.data == data);
|
|
assert(ctfe.data == data);
|
|
}
|
|
|
|
/**
|
|
* Construct a UUID struct from the 16 byte representation
|
|
* of a UUID. Variadic constructor to allow a simpler syntax, see examples.
|
|
* You need to pass exactly 16 ubytes.
|
|
*/
|
|
@safe pure this(T...)(T uuidData)
|
|
if (uuidData.length == 16 && allSatisfy!(isIntegral, T))
|
|
{
|
|
import std.conv : to;
|
|
|
|
foreach (idx, it; uuidData)
|
|
{
|
|
this.data[idx] = to!ubyte(it);
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
auto tmp = UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
|
|
assert(tmp.data == cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11,
|
|
12,13,14,15]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
UUID tmp = UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
|
|
assert(tmp.data == cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11,
|
|
12,13,14,15]);
|
|
|
|
enum UUID ctfeID = UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
|
|
assert(ctfeID == tmp);
|
|
|
|
//Too few arguments
|
|
assert(!__traits(compiles, typeof(UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14))));
|
|
|
|
//Too many arguments
|
|
assert(!__traits(compiles, typeof(UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,1))));
|
|
}
|
|
|
|
/**
|
|
* <a name="UUID(string)"></a>
|
|
* Parse a UUID from its canonical string form. An UUID in its
|
|
* canonical form looks like this: 8ab3060e-2cba-4f23-b74c-b52db3bdfb46
|
|
*
|
|
* Throws:
|
|
* $(LREF UUIDParsingException) if the input is invalid
|
|
*
|
|
* CTFE:
|
|
* This function is supported in CTFE code. Note that error messages
|
|
* caused by a malformed UUID parsed at compile time can be cryptic,
|
|
* but errors are detected and reported at
|
|
* compile time.
|
|
*
|
|
* Note:
|
|
* This is a strict parser. It only accepts the pattern above.
|
|
* It doesn't support any leading or trailing characters. It only
|
|
* accepts characters used for hex numbers and the string must have
|
|
* hyphens exactly like above.
|
|
*
|
|
* For a less strict parser, see $(LREF parseUUID)
|
|
*/
|
|
this(T)(in T[] uuid) if (isSomeChar!(Unqual!T))
|
|
{
|
|
import std.conv : to, parse;
|
|
if (uuid.length < 36)
|
|
{
|
|
throw new UUIDParsingException(to!string(uuid), 0,
|
|
UUIDParsingException.Reason.tooLittle, "Insufficient Input");
|
|
}
|
|
if (uuid.length > 36)
|
|
{
|
|
throw new UUIDParsingException(to!string(uuid), 35, UUIDParsingException.Reason.tooMuch,
|
|
"Input is too long, need exactly 36 characters");
|
|
}
|
|
static immutable skipInd = [skipSeq];
|
|
foreach (pos; skipInd)
|
|
if (uuid[pos] != '-')
|
|
throw new UUIDParsingException(to!string(uuid), pos,
|
|
UUIDParsingException.Reason.invalidChar, "Expected '-'");
|
|
|
|
ubyte[16] data2; //ctfe bug
|
|
uint pos = void;
|
|
|
|
foreach (i, p; byteSeq)
|
|
{
|
|
enum uint s = 'a'-10-'0';
|
|
uint h = uuid[p];
|
|
uint l = uuid[p+1];
|
|
pos = p;
|
|
if (h < '0') goto Lerr;
|
|
if (l < '0') goto Lerr;
|
|
if (h > '9')
|
|
{
|
|
h |= 0x20; //poorman's tolower
|
|
if (h < 'a') goto Lerr;
|
|
if (h > 'f') goto Lerr;
|
|
h -= s;
|
|
}
|
|
if (l > '9')
|
|
{
|
|
l |= 0x20; //poorman's tolower
|
|
if (l < 'a') goto Lerr;
|
|
if (l > 'f') goto Lerr;
|
|
l -= s;
|
|
}
|
|
h -= '0';
|
|
l -= '0';
|
|
|
|
data2[i] = cast(ubyte)((h << 4) ^ l);
|
|
}
|
|
this.data = data2;
|
|
return;
|
|
|
|
Lerr: throw new UUIDParsingException(to!string(uuid), pos,
|
|
UUIDParsingException.Reason.invalidChar, "Couldn't parse ubyte");
|
|
}
|
|
|
|
///
|
|
@safe pure unittest
|
|
{
|
|
auto id = UUID("8AB3060E-2cba-4f23-b74c-b52db3bdfb46");
|
|
assert(id.data == [138, 179, 6, 14, 44, 186, 79, 35, 183, 76,
|
|
181, 45, 179, 189, 251, 70]);
|
|
assert(id.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
|
|
|
|
//Can also be used in CTFE, for example as UUID literals:
|
|
enum ctfeID = UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
|
|
//here parsing is done at compile time, no runtime overhead!
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
import std.conv : to;
|
|
import std.exception;
|
|
import std.meta : AliasSeq;
|
|
|
|
static foreach (S; AliasSeq!(char[], const(char)[], immutable(char)[],
|
|
wchar[], const(wchar)[], immutable(wchar)[],
|
|
dchar[], const(dchar)[], immutable(dchar)[],
|
|
immutable(char[]), immutable(wchar[]), immutable(dchar[])))
|
|
{{
|
|
//Test valid, working cases
|
|
assert(UUID(to!S("00000000-0000-0000-0000-000000000000")).empty);
|
|
|
|
auto id = UUID(to!S("8AB3060E-2cba-4f23-b74c-b52db3bdfb46"));
|
|
assert(id.data == [138, 179, 6, 14, 44, 186, 79, 35, 183, 76,
|
|
181, 45, 179, 189, 251, 70]);
|
|
assert(id.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
|
|
|
|
enum UUID ctfe = UUID(to!S("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
|
|
assert(ctfe == id);
|
|
|
|
assert(UUID(to!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a")).data
|
|
== [86, 104, 18, 45, 157, 240, 73, 164, 173, 11, 185, 176, 165, 127, 136, 106]);
|
|
|
|
//Test too short UUIDS
|
|
auto except = collectException!UUIDParsingException(
|
|
UUID(to!S("5668122d-9df0-49a4-ad0b-b9b0a57f886")));
|
|
assert(except && except.reason == UUIDParsingException.Reason.tooLittle);
|
|
|
|
//Test too long UUIDS
|
|
except = collectException!UUIDParsingException(
|
|
UUID(to!S("5668122d-9df0-49a4-ad0b-b9b0a57f886aa")));
|
|
assert(except && except.reason == UUIDParsingException.Reason.tooMuch);
|
|
|
|
//Test dashes
|
|
except = collectException!UUIDParsingException(
|
|
UUID(to!S("8ab3060e2cba-4f23-b74c-b52db3bdfb-46")));
|
|
assert(except && except.reason == UUIDParsingException.Reason.invalidChar);
|
|
|
|
//Test dashes 2
|
|
except = collectException!UUIDParsingException(
|
|
UUID(to!S("8ab3-060e2cba-4f23-b74c-b52db3bdfb46")));
|
|
assert(except && except.reason == UUIDParsingException.Reason.invalidChar);
|
|
|
|
//Test invalid characters
|
|
//make sure 36 characters in total or we'll get a 'tooMuch' reason
|
|
except = collectException!UUIDParsingException(
|
|
UUID(to!S("{8ab3060e-2cba-4f23-b74c-b52db3bdf6}")));
|
|
assert(except && except.reason == UUIDParsingException.Reason.invalidChar);
|
|
|
|
//Boost test
|
|
assert(UUID(to!S("01234567-89ab-cdef-0123-456789ABCDEF"))
|
|
== UUID(cast(ubyte[16])[0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,0x01,
|
|
0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]));
|
|
}
|
|
}}
|
|
|
|
/**
|
|
* Returns true if and only if the UUID is equal
|
|
* to {00000000-0000-0000-0000-000000000000}
|
|
*/
|
|
@trusted pure nothrow @nogc @property bool empty() const
|
|
{
|
|
if (__ctfe)
|
|
return data == (ubyte[16]).init;
|
|
|
|
auto p = cast(const(size_t*))data.ptr;
|
|
static if (size_t.sizeof == 4)
|
|
return p[0] == 0 && p[1] == 0 && p[2] == 0 && p[3] == 0;
|
|
else static if (size_t.sizeof == 8)
|
|
return p[0] == 0 && p[1] == 0;
|
|
else
|
|
static assert(false, "nonsense, it's not 32 or 64 bit");
|
|
}
|
|
|
|
///
|
|
@safe pure unittest
|
|
{
|
|
UUID id;
|
|
assert(id.empty);
|
|
id = UUID("00000000-0000-0000-0000-000000000001");
|
|
assert(!id.empty);
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
ubyte[16] getData(size_t i)
|
|
{
|
|
ubyte[16] data;
|
|
data[i] = 1;
|
|
return data;
|
|
}
|
|
|
|
for (size_t i = 0; i < 16; i++)
|
|
{
|
|
assert(!UUID(getData(i)).empty);
|
|
}
|
|
|
|
enum ctfeEmpty = UUID.init.empty;
|
|
assert(ctfeEmpty);
|
|
|
|
bool ctfeTest()
|
|
{
|
|
for (size_t i = 0; i < 16; i++)
|
|
{
|
|
auto ctfeEmpty2 = UUID(getData(i)).empty;
|
|
assert(!ctfeEmpty2);
|
|
}
|
|
return true;
|
|
}
|
|
enum res = ctfeTest();
|
|
}
|
|
|
|
/**
|
|
* RFC 4122 defines different internal data layouts for UUIDs.
|
|
* Returns the format used by this UUID.
|
|
*
|
|
* Note: Do not confuse this with $(REF _Variant, std,_variant).
|
|
* The type of this property is $(MYREF3 std.uuid.UUID.Variant, _Variant).
|
|
*
|
|
* See_Also:
|
|
* $(MYREF3 UUID.Variant, Variant)
|
|
*/
|
|
@safe pure nothrow @nogc @property Variant variant() const
|
|
{
|
|
//variant is stored in octet 7
|
|
//which is index 8, since indexes count backwards
|
|
immutable octet7 = data[8]; //octet 7 is array index 8
|
|
|
|
if ((octet7 & 0x80) == 0x00) //0b0xxxxxxx
|
|
return Variant.ncs;
|
|
else if ((octet7 & 0xC0) == 0x80) //0b10xxxxxx
|
|
return Variant.rfc4122;
|
|
else if ((octet7 & 0xE0) == 0xC0) //0b110xxxxx
|
|
return Variant.microsoft;
|
|
else
|
|
{
|
|
//assert((octet7 & 0xE0) == 0xE0, "Unknown UUID variant!") //0b111xxxx
|
|
return Variant.future;
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe pure unittest
|
|
{
|
|
assert(UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46").variant
|
|
== UUID.Variant.rfc4122);
|
|
}
|
|
@system pure unittest
|
|
{
|
|
// @system due to Variant
|
|
Variant[ubyte] tests = cast(Variant[ubyte])[0x00 : Variant.ncs,
|
|
0x10 : Variant.ncs,
|
|
0x20 : Variant.ncs,
|
|
0x30 : Variant.ncs,
|
|
0x40 : Variant.ncs,
|
|
0x50 : Variant.ncs,
|
|
0x60 : Variant.ncs,
|
|
0x70 : Variant.ncs,
|
|
0x80 : Variant.rfc4122,
|
|
0x90 : Variant.rfc4122,
|
|
0xa0 : Variant.rfc4122,
|
|
0xb0 : Variant.rfc4122,
|
|
0xc0 : Variant.microsoft,
|
|
0xd0 : Variant.microsoft,
|
|
0xe0 : Variant.future,
|
|
0xf0 : Variant.future];
|
|
foreach (key, value; tests)
|
|
{
|
|
UUID u;
|
|
u.data[8] = key;
|
|
assert(u.variant == value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* RFC 4122 defines different UUID versions. The version shows
|
|
* how a UUID was generated, e.g. a version 4 UUID was generated
|
|
* from a random number, a version 3 UUID from an MD5 hash of a name.
|
|
* Returns the version used by this UUID.
|
|
*
|
|
* See_Also:
|
|
* $(MYREF3 UUID.Version, Version)
|
|
*/
|
|
@safe pure nothrow @nogc @property Version uuidVersion() const
|
|
{
|
|
//version is stored in octet 9
|
|
//which is index 6, since indexes count backwards
|
|
immutable octet9 = data[6];
|
|
if ((octet9 & 0xF0) == 0x10)
|
|
return Version.timeBased;
|
|
else if ((octet9 & 0xF0) == 0x20)
|
|
return Version.dceSecurity;
|
|
else if ((octet9 & 0xF0) == 0x30)
|
|
return Version.nameBasedMD5;
|
|
else if ((octet9 & 0xF0) == 0x40)
|
|
return Version.randomNumberBased;
|
|
else if ((octet9 & 0xF0) == 0x50)
|
|
return Version.nameBasedSHA1;
|
|
else
|
|
return Version.unknown;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
assert(UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46").uuidVersion
|
|
== UUID.Version.randomNumberBased);
|
|
}
|
|
@system unittest
|
|
{
|
|
// @system due to cast
|
|
Version[ubyte] tests = cast(Version[ubyte]) [
|
|
0x00 : UUID.Version.unknown,
|
|
0x10 : UUID.Version.timeBased,
|
|
0x20 : UUID.Version.dceSecurity,
|
|
0x30 : UUID.Version.nameBasedMD5,
|
|
0x40 : UUID.Version.randomNumberBased,
|
|
0x50 : UUID.Version.nameBasedSHA1,
|
|
0x60 : UUID.Version.unknown,
|
|
0x70 : UUID.Version.unknown,
|
|
0x80 : UUID.Version.unknown,
|
|
0x90 : UUID.Version.unknown,
|
|
0xa0 : UUID.Version.unknown,
|
|
0xb0 : UUID.Version.unknown,
|
|
0xc0 : UUID.Version.unknown,
|
|
0xd0 : UUID.Version.unknown,
|
|
0xe0 : UUID.Version.unknown,
|
|
0xf0 : UUID.Version.unknown];
|
|
foreach (key, value; tests)
|
|
{
|
|
UUID u;
|
|
u.data[6] = key;
|
|
assert(u.uuidVersion == value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Swap the data of this UUID with the data of rhs.
|
|
*/
|
|
@safe pure nothrow @nogc void swap(ref UUID rhs)
|
|
{
|
|
immutable bck = data;
|
|
data = rhs.data;
|
|
rhs.data = bck;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
immutable ubyte[16] data = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
|
|
UUID u1;
|
|
UUID u2 = UUID(data);
|
|
u1.swap(u2);
|
|
|
|
assert(u1 == UUID(data));
|
|
assert(u2 == UUID.init);
|
|
}
|
|
|
|
/**
|
|
* All of the standard numeric operators are defined for
|
|
* the UUID struct.
|
|
*/
|
|
@safe pure nothrow @nogc bool opEquals(const UUID s) const
|
|
{
|
|
return ulongs[0] == s.ulongs[0] && ulongs[1] == s.ulongs[1];
|
|
}
|
|
|
|
///
|
|
@safe pure unittest
|
|
{
|
|
//compare UUIDs
|
|
assert(UUID("00000000-0000-0000-0000-000000000000") == UUID.init);
|
|
|
|
//UUIDs in associative arrays:
|
|
int[UUID] test = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0") : 1,
|
|
UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a") : 2,
|
|
UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1") : 3];
|
|
|
|
assert(test[UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")] == 3);
|
|
|
|
//UUIDS can be sorted:
|
|
import std.algorithm;
|
|
UUID[] ids = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0"),
|
|
UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a"),
|
|
UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")];
|
|
sort(ids);
|
|
}
|
|
|
|
/**
|
|
* ditto
|
|
*/
|
|
@safe pure nothrow @nogc bool opEquals(ref const scope UUID s) const
|
|
{
|
|
return ulongs[0] == s.ulongs[0] && ulongs[1] == s.ulongs[1];
|
|
}
|
|
|
|
/**
|
|
* ditto
|
|
*/
|
|
@safe pure nothrow @nogc int opCmp(const UUID s) const
|
|
{
|
|
import std.algorithm.comparison : cmp;
|
|
return cmp(this.data[], s.data[]);
|
|
}
|
|
|
|
/**
|
|
* ditto
|
|
*/
|
|
@safe pure nothrow @nogc int opCmp(ref const scope UUID s) const
|
|
{
|
|
import std.algorithm.comparison : cmp;
|
|
return cmp(this.data[], s.data[]);
|
|
}
|
|
|
|
/**
|
|
* ditto
|
|
*/
|
|
@safe pure nothrow @nogc UUID opAssign(const UUID s)
|
|
{
|
|
ulongs[0] = s.ulongs[0];
|
|
ulongs[1] = s.ulongs[1];
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* ditto
|
|
*/
|
|
@safe pure nothrow @nogc UUID opAssign(ref const scope UUID s)
|
|
{
|
|
ulongs[0] = s.ulongs[0];
|
|
ulongs[1] = s.ulongs[1];
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* ditto
|
|
*/
|
|
//MurmurHash2
|
|
@safe pure nothrow @nogc size_t toHash() const
|
|
{
|
|
static if (size_t.sizeof == 4)
|
|
{
|
|
enum uint m = 0x5bd1e995;
|
|
enum uint n = 16;
|
|
enum uint r = 24;
|
|
|
|
uint h = n;
|
|
|
|
uint k = uints[0];
|
|
k *= m;
|
|
k ^= k >> r;
|
|
k *= m;
|
|
|
|
h ^= k;
|
|
h *= m;
|
|
|
|
k = uints[1];
|
|
k *= m;
|
|
k ^= k >> r;
|
|
k *= m;
|
|
|
|
h ^= k;
|
|
h *= m;
|
|
|
|
k = uints[2];
|
|
k *= m;
|
|
k ^= k >> r;
|
|
k *= m;
|
|
|
|
h ^= k;
|
|
h *= m;
|
|
|
|
k = uints[3];
|
|
k *= m;
|
|
k ^= k >> r;
|
|
k *= m;
|
|
|
|
h ^= k;
|
|
h *= m;
|
|
}
|
|
else
|
|
{
|
|
enum ulong m = 0xc6a4a7935bd1e995UL;
|
|
enum ulong n = m * 16;
|
|
enum uint r = 47;
|
|
|
|
ulong h = n;
|
|
|
|
ulong k = ulongs[0];
|
|
k *= m;
|
|
k ^= k >> r;
|
|
k *= m;
|
|
|
|
h ^= k;
|
|
h *= m;
|
|
|
|
k = ulongs[1];
|
|
k *= m;
|
|
k ^= k >> r;
|
|
k *= m;
|
|
|
|
h ^= k;
|
|
h *= m;
|
|
}
|
|
return h;
|
|
}
|
|
@safe unittest
|
|
{
|
|
assert(UUID("00000000-0000-0000-0000-000000000000") == UUID.init);
|
|
int[UUID] test = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0") : 1,
|
|
UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a") : 2,
|
|
UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1") : 3];
|
|
|
|
assert(test[UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")] == 3);
|
|
|
|
import std.algorithm;
|
|
UUID[] ids = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0"),
|
|
UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a"),
|
|
UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")];
|
|
sort(ids);
|
|
auto id2 = ids.dup;
|
|
|
|
ids = [UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a"),
|
|
UUID("8a94f585-d180-44f7-8929-6fca0189c7d0"),
|
|
UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")];
|
|
sort(ids);
|
|
assert(ids == id2);
|
|
|
|
//test comparsion
|
|
UUID u1;
|
|
UUID u2 = UUID(cast(ubyte[16])[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);
|
|
UUID u3 = UUID(cast(ubyte[16])[255,255,255,255,255,255,255,255,255,
|
|
255,255,255,255,255,255,255]);
|
|
|
|
assert(u1 == u1);
|
|
|
|
assert(u1 != u2);
|
|
|
|
assert(u1 < u2);
|
|
assert(u2 < u3);
|
|
|
|
assert(u1 <= u1);
|
|
assert(u1 <= u2);
|
|
assert(u2 <= u3);
|
|
|
|
assert(u2 >= u2);
|
|
assert(u3 >= u2);
|
|
|
|
assert(u3 >= u3);
|
|
assert(u2 >= u1);
|
|
assert(u3 >= u1);
|
|
|
|
// test hash
|
|
assert(u1.toHash() != u2.toHash());
|
|
assert(u2.toHash() != u3.toHash());
|
|
assert(u3.toHash() != u1.toHash());
|
|
}
|
|
|
|
|
|
/**
|
|
* Write the UUID into `sink` as an ASCII string in the canonical form,
|
|
* which is 36 characters in the form "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
|
* Params:
|
|
* sink = OutputRange or writeable array at least 36 entries long
|
|
*/
|
|
void toString(Writer)(scope Writer sink) const
|
|
{
|
|
char[36] result = void;
|
|
foreach (pos; skipSeq)
|
|
result[pos] = '-';
|
|
foreach (i, pos; byteSeq)
|
|
{
|
|
const uint entry = this.data[i];
|
|
const uint hi = entry >> 4;
|
|
result[pos ] = toChar!char(hi);
|
|
const uint lo = (entry) & 0x0F;
|
|
result[pos+1] = toChar!char(lo);
|
|
}
|
|
static if (!__traits(compiles, put(sink, result[])) || isSomeString!Writer)
|
|
{
|
|
foreach (i, c; result)
|
|
sink[i] = cast(typeof(sink[i]))c;
|
|
}
|
|
else
|
|
{
|
|
put(sink, result[]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the UUID as a string in the canonical form.
|
|
*/
|
|
@trusted pure nothrow string toString() const
|
|
{
|
|
import std.exception : assumeUnique;
|
|
auto result = new char[36];
|
|
toString(result);
|
|
return result.assumeUnique;
|
|
}
|
|
|
|
///
|
|
@safe pure unittest
|
|
{
|
|
immutable str = "8ab3060e-2cba-4f23-b74c-b52db3bdfb46";
|
|
auto id = UUID(str);
|
|
assert(id.toString() == str);
|
|
}
|
|
|
|
@safe pure nothrow @nogc unittest
|
|
{
|
|
import std.meta : AliasSeq;
|
|
static foreach (Char; AliasSeq!(char, wchar, dchar))
|
|
{{
|
|
alias String = immutable(Char)[];
|
|
//CTFE
|
|
enum String s = "8ab3060e-2cba-4f23-b74c-b52db3bdfb46";
|
|
enum id = UUID(s);
|
|
static if (is(Char == char))
|
|
{
|
|
enum p = id.toString();
|
|
static assert(s == p);
|
|
}
|
|
//nogc
|
|
Char[36] str;
|
|
id.toString(str[]);
|
|
assert(str == s);
|
|
}}
|
|
}
|
|
|
|
@system pure nothrow @nogc unittest
|
|
{
|
|
// @system due to cast
|
|
import std.encoding : Char = AsciiChar;
|
|
enum utfstr = "8ab3060e-2cba-4f23-b74c-b52db3bdfb46";
|
|
alias String = immutable(Char)[];
|
|
enum String s = cast(String) utfstr;
|
|
enum id = UUID(utfstr);
|
|
//nogc
|
|
Char[36] str;
|
|
id.toString(str[]);
|
|
assert(str == s);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
auto u1 = UUID(cast(ubyte[16])[138, 179, 6, 14, 44, 186, 79,
|
|
35, 183, 76, 181, 45, 179, 189, 251, 70]);
|
|
assert(u1.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
|
|
u1 = UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
|
|
assert(u1.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
|
|
|
|
char[] buf;
|
|
void sink(scope const(char)[] data)
|
|
{
|
|
buf ~= data;
|
|
}
|
|
u1.toString(&sink);
|
|
assert(buf == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
UUID id;
|
|
assert(id.empty);
|
|
|
|
id = randomUUID;
|
|
assert(!id.empty);
|
|
|
|
id = UUID(cast(ubyte[16]) [138, 179, 6, 14, 44, 186, 79,
|
|
35, 183, 76, 181, 45, 179, 189, 251, 70]);
|
|
assert(id.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
|
|
}
|
|
|
|
/**
|
|
* This function generates a name based (Version 3) UUID from a namespace UUID and a name.
|
|
* If no namespace UUID was passed, the empty UUID `UUID.init` is used.
|
|
*
|
|
* Note:
|
|
* The default namespaces ($(LREF dnsNamespace), ...) defined by
|
|
* this module should be used when appropriate.
|
|
*
|
|
* RFC 4122 recommends to use Version 5 UUIDs (SHA-1) instead of Version 3
|
|
* UUIDs (MD5) for new applications.
|
|
*
|
|
* CTFE:
|
|
* CTFE is not supported.
|
|
*
|
|
* Note:
|
|
* RFC 4122 isn't very clear on how UUIDs should be generated from names.
|
|
* It is possible that different implementations return different UUIDs
|
|
* for the same input, so be warned. The implementation for UTF-8 strings
|
|
* and byte arrays used by `std.uuid` is compatible with Boost's implementation.
|
|
* `std.uuid` guarantees that the same input to this function will generate
|
|
* the same output at any time, on any system (this especially means endianness
|
|
* doesn't matter).
|
|
*
|
|
* Note:
|
|
* This function does not provide overloads for wstring and dstring, as
|
|
* there's no clear answer on how that should be implemented. It could be
|
|
* argued, that string, wstring and dstring input should have the same output,
|
|
* but that wouldn't be compatible with Boost, which generates different output
|
|
* for strings and wstrings. It's always possible to pass wstrings and dstrings
|
|
* by using the ubyte[] function overload (but be aware of endianness issues!).
|
|
*/
|
|
@safe pure nothrow @nogc UUID md5UUID(const(char[]) name, const UUID namespace = UUID.init)
|
|
{
|
|
return md5UUID(cast(const(ubyte[]))name, namespace);
|
|
}
|
|
|
|
/// ditto
|
|
@safe pure nothrow @nogc UUID md5UUID(const(ubyte[]) data, const UUID namespace = UUID.init)
|
|
{
|
|
import std.digest.md : MD5;
|
|
|
|
MD5 hash;
|
|
hash.start();
|
|
|
|
/*
|
|
* NOTE: RFC 4122 says namespace should be converted to big-endian.
|
|
* We always keep the UUID data in big-endian representation, so
|
|
* that's fine
|
|
*/
|
|
hash.put(namespace.data[]);
|
|
hash.put(data[]);
|
|
|
|
UUID u;
|
|
u.data = hash.finish();
|
|
|
|
//set variant
|
|
//must be 0b10xxxxxx
|
|
u.data[8] &= 0b10111111;
|
|
u.data[8] |= 0b10000000;
|
|
|
|
//set version
|
|
//must be 0b0011xxxx
|
|
u.data[6] &= 0b00111111;
|
|
u.data[6] |= 0b00110000;
|
|
|
|
return u;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
//Use default UUID.init namespace
|
|
auto simpleID = md5UUID("test.uuid.any.string");
|
|
|
|
//use a name-based id as namespace
|
|
auto namespace = md5UUID("my.app");
|
|
auto id = md5UUID("some-description", namespace);
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
auto simpleID = md5UUID("test.uuid.any.string");
|
|
assert(simpleID.data == cast(ubyte[16])[126, 206, 86, 72, 29, 233, 62, 213, 178, 139, 198, 136,
|
|
188, 135, 153, 123]);
|
|
auto namespace = md5UUID("my.app");
|
|
auto id = md5UUID("some-description", namespace);
|
|
assert(id.data == cast(ubyte[16])[166, 138, 167, 79, 48, 219, 55, 166, 170, 103, 39, 73, 216,
|
|
150, 144, 164]);
|
|
|
|
auto constTest = md5UUID(cast(const(char)[])"test");
|
|
constTest = md5UUID(cast(const(char[]))"test");
|
|
|
|
char[] mutable = "test".dup;
|
|
id = md5UUID(mutable, namespace);
|
|
|
|
const(ubyte)[] data = cast(ubyte[])[0,1,2,244,165,222];
|
|
id = md5UUID(data);
|
|
assert(id.data == cast(ubyte[16])[16, 50, 29, 247, 243, 185, 61, 178, 157, 100, 253, 236, 73,
|
|
76, 51, 47]);
|
|
|
|
assert(id.variant == UUID.Variant.rfc4122);
|
|
assert(id.uuidVersion == UUID.Version.nameBasedMD5);
|
|
|
|
auto correct = UUID("3d813cbb-47fb-32ba-91df-831e1593ac29");
|
|
|
|
auto u = md5UUID("www.widgets.com", dnsNamespace);
|
|
//enum ctfeId = md5UUID("www.widgets.com", dnsNamespace);
|
|
//assert(ctfeId == u);
|
|
assert(u == correct);
|
|
assert(u.variant == UUID.Variant.rfc4122);
|
|
assert(u.uuidVersion == UUID.Version.nameBasedMD5);
|
|
}
|
|
|
|
/**
|
|
* This function generates a name based (Version 5) UUID from a namespace
|
|
* UUID and a name.
|
|
* If no namespace UUID was passed, the empty UUID `UUID.init` is used.
|
|
*
|
|
* Note:
|
|
* The default namespaces ($(LREF dnsNamespace), ...) defined by
|
|
* this module should be used when appropriate.
|
|
*
|
|
* CTFE:
|
|
* CTFE is not supported.
|
|
*
|
|
* Note:
|
|
* RFC 4122 isn't very clear on how UUIDs should be generated from names.
|
|
* It is possible that different implementations return different UUIDs
|
|
* for the same input, so be warned. The implementation for UTF-8 strings
|
|
* and byte arrays used by `std.uuid` is compatible with Boost's implementation.
|
|
* `std.uuid` guarantees that the same input to this function will generate
|
|
* the same output at any time, on any system (this especially means endianness
|
|
* doesn't matter).
|
|
*
|
|
* Note:
|
|
* This function does not provide overloads for wstring and dstring, as
|
|
* there's no clear answer on how that should be implemented. It could be
|
|
* argued, that string, wstring and dstring input should have the same output,
|
|
* but that wouldn't be compatible with Boost, which generates different output
|
|
* for strings and wstrings. It's always possible to pass wstrings and dstrings
|
|
* by using the ubyte[] function overload (but be aware of endianness issues!).
|
|
*/
|
|
@safe pure nothrow @nogc UUID sha1UUID(scope const(char)[] name, scope const UUID namespace = UUID.init)
|
|
{
|
|
return sha1UUID(cast(const(ubyte[]))name, namespace);
|
|
}
|
|
|
|
/// ditto
|
|
@safe pure nothrow @nogc UUID sha1UUID(scope const(ubyte)[] data, scope const UUID namespace = UUID.init)
|
|
{
|
|
import std.digest.sha : SHA1;
|
|
|
|
SHA1 sha;
|
|
sha.start();
|
|
|
|
/*
|
|
* NOTE: RFC 4122 says namespace should be converted to big-endian.
|
|
* We always keep the UUID data in big-endian representation, so
|
|
* that's fine
|
|
*/
|
|
sha.put(namespace.data[]);
|
|
sha.put(data[]);
|
|
|
|
auto hash = sha.finish();
|
|
auto u = UUID();
|
|
u.data[] = hash[0 .. 16];
|
|
|
|
//set variant
|
|
//must be 0b10xxxxxx
|
|
u.data[8] &= 0b10111111;
|
|
u.data[8] |= 0b10000000;
|
|
|
|
//set version
|
|
//must be 0b0101xxxx
|
|
u.data[6] &= 0b01011111;
|
|
u.data[6] |= 0b01010000;
|
|
|
|
return u;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
//Use default UUID.init namespace
|
|
auto simpleID = sha1UUID("test.uuid.any.string");
|
|
|
|
//use a name-based id as namespace
|
|
auto namespace = sha1UUID("my.app");
|
|
auto id = sha1UUID("some-description", namespace);
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
auto simpleID = sha1UUID("test.uuid.any.string");
|
|
assert(simpleID.data == cast(ubyte[16])[16, 209, 239, 61, 99, 12, 94, 70, 159, 79, 255, 250,
|
|
131, 79, 14, 147]);
|
|
auto namespace = sha1UUID("my.app");
|
|
auto id = sha1UUID("some-description", namespace);
|
|
assert(id.data == cast(ubyte[16])[225, 94, 195, 219, 126, 75, 83, 71, 157, 52, 247, 43, 238, 248,
|
|
148, 46]);
|
|
|
|
auto constTest = sha1UUID(cast(const(char)[])"test");
|
|
constTest = sha1UUID(cast(const(char[]))"test");
|
|
|
|
char[] mutable = "test".dup;
|
|
id = sha1UUID(mutable, namespace);
|
|
|
|
const(ubyte)[] data = cast(ubyte[])[0,1,2,244,165,222];
|
|
id = sha1UUID(data);
|
|
assert(id.data == cast(ubyte[16])[60, 65, 92, 240, 96, 46, 95, 238, 149, 100, 12, 64, 199, 194,
|
|
243, 12]);
|
|
|
|
auto correct = UUID("21f7f8de-8051-5b89-8680-0195ef798b6a");
|
|
|
|
auto u = sha1UUID("www.widgets.com", dnsNamespace);
|
|
assert(u == correct);
|
|
assert(u.variant == UUID.Variant.rfc4122);
|
|
assert(u.uuidVersion == UUID.Version.nameBasedSHA1);
|
|
}
|
|
|
|
/**
|
|
* This function generates a random number based UUID from a random
|
|
* number generator.
|
|
*
|
|
* This function is not supported at compile time.
|
|
*
|
|
* Params:
|
|
* randomGen = uniform RNG
|
|
* See_Also: $(REF isUniformRNG, std,random)
|
|
*/
|
|
@safe UUID randomUUID()
|
|
{
|
|
import std.random : rndGen;
|
|
// A PRNG with fewer than `n` bytes of state cannot produce
|
|
// every distinct `n` byte sequence.
|
|
static if (typeof(rndGen).sizeof >= UUID.sizeof)
|
|
{
|
|
return randomUUID(rndGen);
|
|
}
|
|
else
|
|
{
|
|
import std.random : unpredictableSeed, Xorshift192;
|
|
static assert(Xorshift192.sizeof >= UUID.sizeof);
|
|
static Xorshift192 rng;
|
|
static bool initialized;
|
|
if (!initialized)
|
|
{
|
|
rng.seed(unpredictableSeed);
|
|
initialized = true;
|
|
}
|
|
return randomUUID(rng);
|
|
}
|
|
}
|
|
|
|
/// ditto
|
|
UUID randomUUID(RNG)(ref RNG randomGen)
|
|
if (isInputRange!RNG && isIntegral!(ElementType!RNG))
|
|
{
|
|
import std.random : isUniformRNG;
|
|
static assert(isUniformRNG!RNG, "randomGen must be a uniform RNG");
|
|
|
|
alias E = ElementEncodingType!RNG;
|
|
enum size_t elemSize = E.sizeof;
|
|
static assert(elemSize <= 16);
|
|
static assert(16 % elemSize == 0);
|
|
|
|
UUID u;
|
|
foreach (ref E e ; u.asArrayOf!E())
|
|
{
|
|
e = randomGen.front;
|
|
randomGen.popFront();
|
|
}
|
|
|
|
//set variant
|
|
//must be 0b10xxxxxx
|
|
u.data[8] &= 0b10111111;
|
|
u.data[8] |= 0b10000000;
|
|
|
|
//set version
|
|
//must be 0b0100xxxx
|
|
u.data[6] &= 0b01001111;
|
|
u.data[6] |= 0b01000000;
|
|
|
|
return u;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.random : Xorshift192, unpredictableSeed;
|
|
|
|
//simple call
|
|
auto uuid = randomUUID();
|
|
|
|
//provide a custom RNG. Must be seeded manually.
|
|
Xorshift192 gen;
|
|
|
|
gen.seed(unpredictableSeed);
|
|
auto uuid3 = randomUUID(gen);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.random : Xorshift192, unpredictableSeed;
|
|
//simple call
|
|
auto uuid = randomUUID();
|
|
|
|
//provide a custom RNG. Must be seeded manually.
|
|
Xorshift192 gen;
|
|
gen.seed(unpredictableSeed);
|
|
auto uuid3 = randomUUID(gen);
|
|
|
|
auto u1 = randomUUID();
|
|
auto u2 = randomUUID();
|
|
assert(u1 != u2);
|
|
assert(u1.variant == UUID.Variant.rfc4122);
|
|
assert(u1.uuidVersion == UUID.Version.randomNumberBased);
|
|
}
|
|
|
|
/**
|
|
* This is a less strict parser compared to the parser used in the
|
|
* UUID constructor. It enforces the following rules:
|
|
*
|
|
* $(UL
|
|
* $(LI hex numbers are always two hexdigits([0-9a-fA-F]))
|
|
* $(LI there must be exactly 16 such pairs in the input, not less, not more)
|
|
* $(LI there can be exactly one dash between two hex-pairs, but not more)
|
|
* $(LI there can be multiple characters enclosing the 16 hex pairs,
|
|
* as long as these characters do not contain [0-9a-fA-F])
|
|
* )
|
|
*
|
|
* Note:
|
|
* Like most parsers, it consumes its argument. This means:
|
|
* -------------------------
|
|
* string s = "8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46";
|
|
* parseUUID(s);
|
|
* assert(s == "");
|
|
* -------------------------
|
|
*
|
|
* Throws:
|
|
* $(LREF UUIDParsingException) if the input is invalid
|
|
*
|
|
* CTFE:
|
|
* This function is supported in CTFE code. Note that error messages
|
|
* caused by a malformed UUID parsed at compile time can be cryptic,
|
|
* but errors are detected and reported at compile time.
|
|
*/
|
|
UUID parseUUID(T)(T uuidString)
|
|
if (isSomeString!T)
|
|
{
|
|
return parseUUID(uuidString);
|
|
}
|
|
|
|
///ditto
|
|
UUID parseUUID(Range)(ref Range uuidRange)
|
|
if (isInputRange!Range && isSomeChar!(ElementType!Range))
|
|
{
|
|
import std.ascii : isHexDigit;
|
|
import std.conv : ConvException, parse;
|
|
|
|
static if (isForwardRange!Range)
|
|
auto errorCopy = uuidRange.save;
|
|
|
|
void parserError()(size_t pos, UUIDParsingException.Reason reason, string message, Throwable next = null,
|
|
string file = __FILE__, size_t line = __LINE__)
|
|
{
|
|
static if (isForwardRange!Range)
|
|
{
|
|
import std.conv : to;
|
|
static if (isInfinite!Range)
|
|
{
|
|
throw new UUIDParsingException(to!string(take(errorCopy, pos)), pos, reason, message,
|
|
next, file, line);
|
|
}
|
|
else
|
|
{
|
|
throw new UUIDParsingException(to!string(errorCopy), pos, reason, message, next, file,
|
|
line);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new UUIDParsingException("", pos, reason, message, next, file, line);
|
|
}
|
|
}
|
|
|
|
static if (hasLength!Range)
|
|
{
|
|
import std.conv : to;
|
|
if (uuidRange.length < 32)
|
|
{
|
|
throw new UUIDParsingException(to!string(uuidRange), 0, UUIDParsingException.Reason.tooLittle,
|
|
"Insufficient Input");
|
|
}
|
|
}
|
|
|
|
UUID result;
|
|
size_t consumed;
|
|
size_t element = 0;
|
|
|
|
//skip garbage
|
|
size_t skip()()
|
|
{
|
|
size_t skipped;
|
|
while (!uuidRange.empty && !isHexDigit(uuidRange.front))
|
|
{
|
|
skipped++;
|
|
uuidRange.popFront();
|
|
}
|
|
return skipped;
|
|
}
|
|
|
|
consumed += skip();
|
|
|
|
if (uuidRange.empty)
|
|
parserError(consumed, UUIDParsingException.Reason.tooLittle, "Insufficient Input");
|
|
|
|
bool dashAllowed = false;
|
|
|
|
parseLoop: while (!uuidRange.empty)
|
|
{
|
|
immutable character = uuidRange.front;
|
|
|
|
if (character == '-')
|
|
{
|
|
if (!dashAllowed)
|
|
parserError(consumed, UUIDParsingException.Reason.invalidChar, "Unexpected '-'");
|
|
else
|
|
dashAllowed = false;
|
|
|
|
consumed++;
|
|
}
|
|
else if (!isHexDigit(character))
|
|
{
|
|
parserError(consumed, UUIDParsingException.Reason.invalidChar,
|
|
"Unexpected character (wanted a hexDigit)");
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
consumed += 2;
|
|
static if (isSomeString!Range)
|
|
{
|
|
if (uuidRange.length < 2)
|
|
{
|
|
parserError(consumed, UUIDParsingException.Reason.tooLittle,
|
|
"Insufficient Input");
|
|
}
|
|
auto part = uuidRange[0 .. 2];
|
|
result.data[element++] = parse!ubyte(part, 16);
|
|
uuidRange.popFront();
|
|
}
|
|
else
|
|
{
|
|
dchar[2] copyBuf;
|
|
copyBuf[0] = character;
|
|
uuidRange.popFront();
|
|
if (uuidRange.empty)
|
|
{
|
|
parserError(consumed, UUIDParsingException.Reason.tooLittle,
|
|
"Insufficient Input");
|
|
}
|
|
copyBuf[1] = uuidRange.front;
|
|
auto part = copyBuf[];
|
|
result.data[element++] = parse!ubyte(part, 16);
|
|
}
|
|
|
|
if (element == 16)
|
|
{
|
|
uuidRange.popFront();
|
|
break parseLoop;
|
|
}
|
|
|
|
dashAllowed = true;
|
|
}
|
|
catch (ConvException e)
|
|
{
|
|
parserError(consumed, UUIDParsingException.Reason.invalidChar,
|
|
"Couldn't parse ubyte", e);
|
|
}
|
|
}
|
|
uuidRange.popFront();
|
|
}
|
|
assert(element <= 16);
|
|
|
|
if (element < 16)
|
|
parserError(consumed, UUIDParsingException.Reason.tooLittle, "Insufficient Input");
|
|
|
|
consumed += skip();
|
|
if (!uuidRange.empty)
|
|
parserError(consumed, UUIDParsingException.Reason.invalidChar, "Unexpected character");
|
|
|
|
return result;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
auto id = parseUUID("8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46");
|
|
//no dashes
|
|
id = parseUUID("8ab3060e2cba4f23b74cb52db3bdfb46");
|
|
//dashes at different positions
|
|
id = parseUUID("8a-b3-06-0e2cba4f23b74c-b52db3bdfb-46");
|
|
//leading / trailing characters
|
|
id = parseUUID("{8ab3060e-2cba-4f23-b74c-b52db3bdfb46}");
|
|
//unicode
|
|
id = parseUUID("ü8ab3060e2cba4f23b74cb52db3bdfb46ü");
|
|
//multiple trailing/leading characters
|
|
id = parseUUID("///8ab3060e2cba4f23b74cb52db3bdfb46||");
|
|
|
|
//Can also be used in CTFE, for example as UUID literals:
|
|
enum ctfeID = parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
|
|
//here parsing is done at compile time, no runtime overhead!
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
import std.conv : to;
|
|
import std.exception;
|
|
import std.meta;
|
|
|
|
struct TestRange(bool forward)
|
|
{
|
|
dstring input;
|
|
|
|
@property dchar front()
|
|
{
|
|
return input.front;
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
input.popFront();
|
|
}
|
|
|
|
@property bool empty()
|
|
{
|
|
return input.empty;
|
|
}
|
|
|
|
static if (forward)
|
|
{
|
|
@property TestRange!true save()
|
|
{
|
|
return this;
|
|
}
|
|
}
|
|
}
|
|
alias TestInputRange = TestRange!false;
|
|
alias TestForwardRange = TestRange!true;
|
|
|
|
assert(isInputRange!TestInputRange);
|
|
assert(is(ElementType!TestInputRange == dchar));
|
|
assert(isInputRange!TestForwardRange);
|
|
assert(isForwardRange!TestForwardRange);
|
|
assert(is(ElementType!TestForwardRange == dchar));
|
|
|
|
//Helper function for unittests - Need to pass ranges by ref
|
|
UUID parseHelper(T)(string input)
|
|
{
|
|
static if (is(T == TestInputRange) || is(T == TestForwardRange))
|
|
{
|
|
T range = T(to!dstring(input));
|
|
return parseUUID(range);
|
|
}
|
|
else
|
|
return parseUUID(to!T(input));
|
|
}
|
|
|
|
static foreach (S; AliasSeq!(char[], const(char)[], immutable(char)[],
|
|
wchar[], const(wchar)[], immutable(wchar)[],
|
|
dchar[], const(dchar)[], immutable(dchar)[],
|
|
immutable(char[]), immutable(wchar[]), immutable(dchar[]),
|
|
TestForwardRange, TestInputRange))
|
|
{{
|
|
//Verify examples.
|
|
auto id = parseHelper!S("8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46");
|
|
//no dashes
|
|
id = parseHelper!S("8ab3060e2cba4f23b74cb52db3bdfb46");
|
|
//dashes at different positions
|
|
id = parseHelper!S("8a-b3-06-0e2cba4f23b74c-b52db3bdfb-46");
|
|
//leading / trailing characters
|
|
id = parseHelper!S("{8ab3060e-2cba-4f23-b74c-b52db3bdfb46}");
|
|
//unicode
|
|
id = parseHelper!S("ü8ab3060e2cba4f23b74cb52db3bdfb46ü");
|
|
//multiple trailing/leading characters
|
|
id = parseHelper!S("///8ab3060e2cba4f23b74cb52db3bdfb46||");
|
|
enum ctfeId = parseHelper!S("8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
|
|
assert(parseHelper!S("8AB3060E-2cba-4f23-b74c-b52db3bdfb46") == ctfeId);
|
|
|
|
//Test valid, working cases
|
|
assert(parseHelper!S("00000000-0000-0000-0000-000000000000").empty);
|
|
assert(parseHelper!S("8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46").data
|
|
== [138, 179, 6, 14, 44, 186, 79, 35, 183, 76, 181, 45, 179, 189, 251, 70]);
|
|
|
|
assert(parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a").data
|
|
== [86, 104, 18, 45, 157, 240, 73, 164, 173, 11, 185, 176, 165, 127, 136, 106]);
|
|
|
|
//wstring / dstring
|
|
assert(parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a").data
|
|
== [86, 104, 18, 45, 157, 240, 73, 164, 173, 11, 185, 176, 165, 127, 136, 106]);
|
|
assert(parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a").data
|
|
== [86, 104, 18, 45, 157, 240, 73, 164, 173, 11, 185, 176, 165, 127, 136, 106]);
|
|
|
|
//Test too short UUIDS
|
|
auto except = collectException!UUIDParsingException(
|
|
parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886"));
|
|
assert(except && except.reason == UUIDParsingException.Reason.tooLittle);
|
|
|
|
//Test too long UUIDS
|
|
except = collectException!UUIDParsingException(
|
|
parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886aa"));
|
|
assert(except && except.reason == UUIDParsingException.Reason.invalidChar);
|
|
|
|
//Test too long UUIDS 2
|
|
except = collectException!UUIDParsingException(
|
|
parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a-aa"));
|
|
assert(except && except.reason == UUIDParsingException.Reason.invalidChar);
|
|
|
|
//Test dashes
|
|
assert(parseHelper!S("8ab3060e2cba-4f23-b74c-b52db3bdfb46")
|
|
== parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
|
|
assert(parseHelper!S("8ab3-060e2cba-4f23-b74c-b52db3bdfb46")
|
|
== parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
|
|
assert(parseHelper!S("8ab3060e2cba4f23b74cb52db3bdfb46")
|
|
== parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
|
|
|
|
except = collectException!UUIDParsingException(
|
|
parseHelper!S("8-ab3060e2cba-4f23-b74c-b52db3bdfb46"));
|
|
assert(except && except.reason == UUIDParsingException.Reason.invalidChar);
|
|
|
|
//Test leading/trailing characters
|
|
assert(parseHelper!S("{8ab3060e-2cba-4f23-b74c-b52db3bdfb46}")
|
|
== parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
|
|
assert(parseHelper!S("{8ab3060e2cba4f23b74cb52db3bdfb46}")
|
|
== parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
|
|
|
|
//Boost test
|
|
auto u_increasing = UUID(cast(ubyte[16])[0x01, 0x23, 0x45, 0x67, 0x89, 0xab,
|
|
0xcd, 0xef,0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]);
|
|
assert(parseHelper!S("0123456789abcdef0123456789ABCDEF") == UUID(cast(ubyte[16])[0x01,
|
|
0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]));
|
|
|
|
//unicode
|
|
assert(parseHelper!S("ü8ab3060e2cba4f23b74cb52db3bdfb46ü")
|
|
== parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
|
|
|
|
//multiple trailing/leading characters
|
|
assert(parseHelper!S("///8ab3060e2cba4f23b74cb52db3bdfb46||")
|
|
== parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
|
|
}}
|
|
|
|
// Test input range with non-dchar element type.
|
|
{
|
|
import std.utf : byCodeUnit;
|
|
auto range = "8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46".byCodeUnit;
|
|
assert(parseUUID(range).data == [138, 179, 6, 14, 44, 186, 79, 35, 183, 76, 181, 45, 179, 189, 251, 70]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Default namespace from RFC 4122
|
|
*
|
|
* Name string is a fully-qualified domain name
|
|
*/
|
|
enum dnsNamespace = UUID("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
|
|
|
|
/**
|
|
* Default namespace from RFC 4122
|
|
*
|
|
* Name string is a URL
|
|
*/
|
|
enum urlNamespace = UUID("6ba7b811-9dad-11d1-80b4-00c04fd430c8");
|
|
|
|
/**
|
|
* Default namespace from RFC 4122
|
|
*
|
|
* Name string is an ISO OID
|
|
*/
|
|
enum oidNamespace = UUID("6ba7b812-9dad-11d1-80b4-00c04fd430c8");
|
|
|
|
/**
|
|
* Default namespace from RFC 4122
|
|
*
|
|
* Name string is an X.500 DN (in DER or a text output format)
|
|
*/
|
|
enum x500Namespace = UUID("6ba7b814-9dad-11d1-80b4-00c04fd430c8");
|
|
|
|
/**
|
|
* Regex string to extract UUIDs from text.
|
|
*/
|
|
enum uuidRegex = "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}"~
|
|
"-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}";
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.algorithm;
|
|
import std.regex;
|
|
|
|
string test = "Lorem ipsum dolor sit amet, consetetur "~
|
|
"6ba7b814-9dad-11d1-80b4-00c04fd430c8 sadipscing \n"~
|
|
"elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore \r\n"~
|
|
"magna aliquyam erat, sed diam voluptua. "~
|
|
"8ab3060e-2cba-4f23-b74c-b52db3bdfb46 At vero eos et accusam et "~
|
|
"justo duo dolores et ea rebum.";
|
|
|
|
auto r = regex(uuidRegex, "g");
|
|
UUID[] found;
|
|
foreach (c; match(test, r))
|
|
{
|
|
found ~= UUID(c.hit);
|
|
}
|
|
assert(found == [
|
|
UUID("6ba7b814-9dad-11d1-80b4-00c04fd430c8"),
|
|
UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* This exception is thrown if an error occurs when parsing a UUID
|
|
* from a string.
|
|
*/
|
|
public class UUIDParsingException : Exception
|
|
{
|
|
/**
|
|
* The reason why parsing the UUID string failed (if known)
|
|
*/
|
|
enum Reason
|
|
{
|
|
unknown, ///
|
|
tooLittle, ///The passed in input was correct, but more input was expected.
|
|
tooMuch, ///The input data is too long (There's no guarantee the first part of the data is valid)
|
|
invalidChar, ///Encountered an invalid character
|
|
|
|
}
|
|
///ditto
|
|
Reason reason;
|
|
///The original input string which should have been parsed.
|
|
string input;
|
|
///The position in the input string where the error occurred.
|
|
size_t position;
|
|
|
|
private this(string input, size_t pos, Reason why = Reason.unknown, string msg = "",
|
|
Throwable next = null, string file = __FILE__, size_t line = __LINE__) pure @trusted
|
|
{
|
|
import std.array : replace;
|
|
import std.format : format;
|
|
this.input = input;
|
|
this.position = pos;
|
|
this.reason = why;
|
|
string message = format("An error occured in the UUID parser: %s\n" ~
|
|
" * Input:\t'%s'\n * Position:\t%s", msg, replace(replace(input,
|
|
"\r", "\\r"), "\n", "\\n"), pos);
|
|
super(message, file, line, next);
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.exception : collectException;
|
|
|
|
const inputUUID = "this-is-an-invalid-uuid";
|
|
auto ex = collectException!UUIDParsingException(UUID(inputUUID));
|
|
assert(ex !is null); // check that exception was thrown
|
|
assert(ex.input == inputUUID);
|
|
assert(ex.position == 0);
|
|
assert(ex.reason == UUIDParsingException.Reason.tooLittle);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
auto ex = new UUIDParsingException("foo", 10, UUIDParsingException.Reason.tooMuch);
|
|
assert(ex.input == "foo");
|
|
assert(ex.position == 10);
|
|
assert(ex.reason == UUIDParsingException.Reason.tooMuch);
|
|
}
|