From 54658df5d8fcc18c2bb09c9f5dc3169afb14c8bf Mon Sep 17 00:00:00 2001 From: Johannes Pfau Date: Wed, 27 Jun 2012 11:10:23 +0200 Subject: [PATCH] Add std.uuid module --- posix.mak | 2 +- std/uuid.d | 1581 ++++++++++++++++++++++++++++++++++++++++++++++++++++ unittest.d | 2 + win32.mak | 15 +- 4 files changed, 1595 insertions(+), 5 deletions(-) create mode 100644 std/uuid.d diff --git a/posix.mak b/posix.mak index d615cde13..90a0d207a 100644 --- a/posix.mak +++ b/posix.mak @@ -158,7 +158,7 @@ STD_MODULES = $(addprefix std/, algorithm array ascii base64 bigint \ metastrings mmfile numeric outbuffer parallelism path perf \ process random range regex regexp signals socket socketstream \ stdint stdio stdiobase stream string syserror system traits \ - typecons typetuple uni uri utf variant xml zip zlib) + typecons typetuple uni uri utf uuid variant xml zip zlib) STD_NET_MODULES = $(addprefix std/net/, isemail curl) diff --git a/std/uuid.d b/std/uuid.d new file mode 100644 index 000000000..3ed329d55 --- /dev/null +++ b/std/uuid.d @@ -0,0 +1,1581 @@ +/** + + +$(BOOKTABLE , +$(TR $(TH Category) $(TH Functions) +) +$(TR $(TDNW Parsing UUIDs) $(TD $(MYREF parseUUID) $(MYREF +UUID(string)) $(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) ) +) +) + + * 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. + * + * 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 + * $(D 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. + * + * Examples: + * ------------------------ + * 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); + * + * ------------------------ + * 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: Boost License 1.0 + * Authors: Johannes Pfau + * Source: $(PHOBOSSRC std/_uuid.d) + * + * Macros: + * MYREF = $1  + * MYREF2 = $1  + * MYREF3 = $(D $1) + */ +/* 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; + +import std.algorithm, std.array, std.ascii; +import std.conv, std.random, std.range, std.string, std.traits, std.md5; + +//import std.crypto.hash.sha; +//import std.crypto.hash.md5; + +/** + * + */ +public struct UUID +{ + private: + @safe nothrow pure char toChar(size_t i) const + { + if(i <= 9) + return cast(char)('0' + i); + else + return cast(char)('a' + (i-10)); + } + + @safe nothrow pure char[36] _toString() const + { + char[36] result; + + size_t i=0; + foreach(entry; this.data) + { + const size_t hi = (entry >> 4) & 0x0F; + result[i++] = toChar(hi); + + const size_t lo = (entry) & 0x0F; + result[i++] = toChar(lo); + + if (i == 8 || i == 13 || i == 18 || i == 23) + { + result[i++] = '-'; + } + } + + return result; + } + + 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"); + } + + + 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 $(XREF _variant, _Variant). This has nothing + * to do with $(XREF _variant, _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 + * $(D 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 + } + + /** + * 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. + * + * Examples: + * ----------------------------------------------- + * auto rawData = uuid.data; //get data + * rawData[0] = 1; //modify + * uuid.data = rawData; //set data + * uuid.data[1] = 2; //modify directly + * ----------------------------------------------- + */ + ubyte[16] data; + + /* + * 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. + */ + + 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. + * + * Examples: + * ------------------------- + * ubyte[16] data = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]; + * auto tmp = UUID(data); + * assert(tmp.data == data); + * ------------------------- + */ + @safe pure nothrow this()(ubyte[16] uuidData) + { + data = uuidData; + } + + unittest + { + ubyte[16] data = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]; + auto tmp = UUID(data); + assert(tmp.data == data); + + enum UUID ctfeID = UUID(cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11,12, + 13,14,15]); + assert(ctfeID == tmp); + } + +/+ + Not Working! DMD interprets the ubyte literals as ints, then complains the int can't + be converted to ubyte! + + /** + * 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. + * + * Examples: + * ------------------------- + * 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 pure nothrow this()(ubyte[16] uuidData...) + { + data = uuidData; + } + + 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)))); + } + +++/ + + /** + * + * 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) + * + * Examples: + * ------------------------- + * 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! + * ------------------------- + * + * BUGS: Could be pure, but this depends on parse!(string, 16). + */ + @trusted this(T)(T[] uuid) if(isSomeChar!(Unqual!T)) + { + 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"); + } + + ubyte[16] data2; //ctfe bug + size_t element = 0, pairStart = -1; + + foreach(pos, dchar character; uuid) + { + if(pos == 8 || pos == 13 || pos == 18 || pos == 23) + { + if(character != '-') + { + throw new UUIDParsingException(to!string(uuid), pos, + UUIDParsingException.Reason.invalidChar, "Expected '-'"); + } + } + else + { + if(pairStart == -1) + pairStart = pos; + else + { + try + { + data2[element++] = parse!ubyte(uuid[pairStart .. pos+1], 16); + pairStart = -1; + } + catch(Exception e) + { + throw new UUIDParsingException(to!string(uuid), pos, + UUIDParsingException.Reason.invalidChar, "Couldn't parse ubyte", e); + } + } + } + } + + assert(element <= 16); + + if(element < 16) + { + throw new UUIDParsingException(to!string(uuid), 0, + UUIDParsingException.Reason.tooLittle, "Insufficient Input"); + } + + this.data = data2; + } + + unittest + { + import std.exception; + import std.typetuple; + + foreach(S; TypeTuple!(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} + * + * Examples: + * ------------------------- + * UUID id; + * assert(id.empty); + * id = UUID("00000000-0000-0000-0000-000000000001"); + * assert(!id.empty); + * ------------------------- + */ + @trusted pure nothrow @property bool empty() const + { + if(__ctfe) + return find!"a!=0"(data[]).empty; //simple + + 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"); + } + + unittest + { + UUID id; + assert(id.empty); + + 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 $(XREF _variant, _Variant). This has nothing + * to do with $(XREF _variant, _Variant). The type of this property is + * $(MYREF3 std.uuid.UUID.Variant, _Variant). + * + * See_Also: + * $(MYREF3 UUID.Variant, Variant) + * + * Examples: + * ------------------------ + * assert(UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46").variant + * == UUID.Variant.rfc4122); + * ------------------------ + */ + @safe pure nothrow @property Variant variant() const + { + //variant is stored in octet 7 + //which is index 8, since indexes count backwards + auto 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; + } + } + + //Verify Example. + unittest + { + assert(UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46").variant + == UUID.Variant.rfc4122); + } + unittest + { + 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) + * + * Examples: + * ---------------------------- + * assert(UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46").uuidVersion + * == UUID.Version.randomNumberBased); + * ---------------------------- + */ + @safe pure nothrow @property Version uuidVersion() const + { + //version is stored in octet 9 + //which is index 6, since indexes count backwards + auto 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; + } + + //Verify Example. + unittest + { + assert(UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46").uuidVersion + == UUID.Version.randomNumberBased); + } + unittest + { + 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. + * + * Note: linear complexity + * + * Examples: + * ---------------------------- + * UUID u1; + * auto u2 = UUID(cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]); + * u1.swap(u2); + * + * assert(u1.data == cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]); + * assert(u2.data == cast(ubyte[16])[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); + * ---------------------------- + */ + @safe nothrow void swap(ref UUID rhs) + { + std.algorithm.swap(this.data, rhs.data); + } + + unittest + { + UUID u1; + auto u2 = UUID(cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]); + u1.swap(u2); + + auto values1 = cast(ubyte[16])[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; + auto values2 = cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]; + assert(u1.data == values2); + assert(u2.data == values1); + + u1.swap(u2); + assert(u2.data == values2); + assert(u1.data == values1); + } + + /** + * All of the standard numeric operators are defined for + * the UUID struct. + * + * Examples: + * ------------------------- + * //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); + * ------------------------- + */ + @safe pure nothrow bool opEquals(const UUID s) const + { + return s.data == this.data; + } + + /** + * ditto + */ + @safe pure nothrow bool opEquals(ref const UUID s) const + { + return s.data == this.data; + } + + /** + * ditto + */ + @safe pure nothrow int opCmp(ref const UUID s) const + { + return cmp(this.data[], s.data[]); + } + + /** + * ditto + */ + @safe pure nothrow int opCmp(const UUID s) const + { + return cmp(this.data[], s.data[]); + } + + /** + * ditto + */ + @safe pure nothrow size_t toHash() const + { + size_t seed = 0; + foreach(entry; this.data) + seed ^= cast(size_t)entry + 0x9e3779b9 + (seed << 6) + (seed >> 2); + + return seed; + } + + 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()); + } + + /** + * Return the UUID as a string in the canonical form. + * + * Examples: + * ---------------------------------- + * auto id = UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"); + * assert(id.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46"); + * ---------------------------------- + */ + void toString(scope void delegate(const(char)[]) sink) const + { + sink(_toString()); + } + + ///ditto + @safe pure nothrow string toString() const + { + //@@@BUG@@@ workaround for bugzilla 5700 + try + return _toString().idup; + catch(Exception) + assert(0, "It should be impossible for idup to throw."); + } + + 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(const(char)[] data) + { + buf ~= data; + } + u1.toString(&sink); + assert(buf == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46"); + } +} + +unittest +{ + assert(UUID.init.empty); +} + + +/** + * 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 $(D 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 currently not supported as $(D std.md5) doesn't work in CTFE. + * + * Examples: + * --------------------------------------- + * //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); + * --------------------------------------- + * + * 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 $(D std.uuid) is compatible with Boost's implementation. + * $(D 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!). + * + * BUGS: Could be pure, but this depends on the MD5 hash code. + */ +@safe UUID md5UUID(const(char[]) name, const UUID namespace = UUID.init) +{ + return md5UUID(cast(const(ubyte[]))name, namespace); +} + +/** + * ditto + */ +@trusted UUID md5UUID(const(ubyte[]) data, const UUID namespace = UUID.init) +{ + MD5_CTX 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.update(namespace.data); + hash.update(data); + + UUID u; + hash.finish(u.data); + + //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; +} + +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); +} +/+ + FIXME: need 3 more unittests: have to check simlpeID.data and id.data (to make sure we have the same + result on all systems, especially considering endianess). id.data needs to be checked for the ubyte + case as well +/** + * 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 $(D UUID.init) is used. + * + * Note: + * The default namespaces ($(LREF dnsNamespace), ...) defined by + * this module should be used when appropriate. + * + * CTFE: + * As long as Phobos has no standard SHA-1 implementation, CTFE support + * for this function can't be guaranteed. CTFE support will depend on + * whether the SHA-1 implementation supports CTFE. + * + * Examples: + * --------------------------------------- + * //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); + * --------------------------------------- + * + * 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 $(D std.uuid) is compatible with Boost's implementation. + * $(D 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!). + * + * BUGS: Could be pure, but this depends on the SHA-1 hash code. + */ +@trusted UUID sha1UUID(const(char[]) name, const UUID namespace = UUID.init) +{ + return sha1UUID(cast(const(ubyte[]))name, namespace); +} + +/** + * ditto + */ +@trusted UUID sha1UUID(const(ubyte[]) data, const UUID namespace = UUID.init) +{ + SHA1 sha = new SHA1(); + + /* + * 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); + + UUID u; + sha.finish(u.data[]); + + //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; +} + +unittest +{ + auto simpleID = sha1UUID("test.uuid.any.string"); + auto namespace = sha1UUID("my.app"); + auto id = sha1UUID("some-description", namespace); + + 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); + + 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. + * + * CTFE: + * This function is not supported at compile time. + * + * Examples: + * ------------------------------------------ + * //simple call + * auto uuid = randomUUID(); + * + * //provide a custom RNG. Must be seeded manually. + * Xorshift192 gen; + * + * gen.seed(unpredictableSeed()); + * auto uuid3 = randomUUID(gen); + * ------------------------------------------ + */ +@trusted UUID randomUUID()() +{ + return randomUUID(rndGen); +} +/* + * Original boost.uuid used Mt19937, we don't want + * to use anything worse than that. If Random is changed + * to something else, this assert and the randomUUID function + * have to be updated. + */ +static assert(is(typeof(rndGen) == Mt19937)); + +/** + * ditto + */ +UUID randomUUID(RNG)(ref RNG randomGen) if(isUniformRNG!(RNG) && + isIntegral!(typeof(RNG.front))) +{ + enum size_t elemSize = typeof(RNG.front).sizeof; + static assert(elemSize <= 16); + UUID u; + foreach(size_t i; iota(cast(size_t)0, cast(size_t)16, elemSize)) + { + randomGen.popFront(); + immutable randomValue = randomGen.front; + u.data[i .. i + elemSize] = *cast(ubyte[elemSize]*)&randomValue; + } + + //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; +} + +unittest +{ + import std.random; + //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]) + * ) + * + * 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. + * + * Examples: + * ------------------------- + * 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! + * ------------------------- + * + * BUGS: Could be pure, but this depends on parse!(string, 16). + */ + +@trusted UUID parseUUID(T)(T uuidString) if(isSomeString!T) +{ + return parseUUID(uuidString); +} + +///ditto +UUID parseUUID(Range)(ref Range uuidRange) if(isInputRange!Range + && is(Unqual!(ElementType!Range) == dchar)) +{ + 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) + { + 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) + { + 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) + { + dchar 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"); + } + result.data[element++] = parse!ubyte(uuidRange[0 .. 2], 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; + result.data[element++] = parse!ubyte(copyBuf[], 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; +} + +unittest +{ + import std.exception; + import std.typetuple; + + 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 TestRange!false TestInputRange; + alias TestRange!true TestForwardRange; + + 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)); + } + + foreach(S; TypeTuple!(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")); + } +} + +/** + * 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. + * + * Examples: + * ------------------- + * 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); + * } + * + * writeln(found); + * ------------------- + */ +enum uuidRegex = r"[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}"; + +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.length == 2); + assert(canFind(found, UUID("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))); + assert(canFind(found, 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 +{ + public: + /** + * 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__) + { + input = input; + position = pos; + 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); + } +} diff --git a/unittest.d b/unittest.d index 65ddb6dd2..927093ac4 100644 --- a/unittest.d +++ b/unittest.d @@ -53,6 +53,7 @@ public import std.typetuple; public import std.uni; public import std.uri; public import std.utf; +public import std.uuid; public import std.variant; public import std.zip; public import std.zlib; @@ -125,6 +126,7 @@ version (all) bool isEmail = std.net.isemail.isEmail("abc"); auto http = std.net.curl.HTTP("dlang.org"); + auto uuid = randomUUID(); } puts("Success!"); return 0; diff --git a/win32.mak b/win32.mak index f58f8caa8..e6bb705da 100644 --- a/win32.mak +++ b/win32.mak @@ -118,6 +118,8 @@ SRC_STD_3= std\csv.d std\math.d std\complex.d std\numeric.d std\bigint.d \ std\compiler.d std\cpuid.d \ std\system.d std\concurrency.d +SRC_STD_4= std\uuid.d + SRC_STD_REST= std\variant.d \ std\syserror.d std\zlib.d \ std\stream.d std\socket.d std\socketstream.d \ @@ -127,10 +129,10 @@ SRC_STD_REST= std\variant.d \ std\stdint.d \ std\json.d \ std\parallelism.d \ - std\mathspecial.d \ + std\mathspecial.d \ std\process.d -SRC_STD_ALL= $(SRC_STD_1_HEAVY) $(SRC_STD_2_HEAVY) $(SRC_STD_3) $(SRC_STD_REST) +SRC_STD_ALL= $(SRC_STD_1_HEAVY) $(SRC_STD_2_HEAVY) $(SRC_STD_3) $(SRC_STD_4) $(SRC_STD_REST) SRC= unittest.d index.d @@ -142,7 +144,7 @@ SRC_STD= std\zlib.d std\zip.d std\stdint.d std\container.d std\conv.d std\utf.d std\syserror.d \ std\regexp.d std\random.d std\stream.d std\process.d \ std\socket.d std\socketstream.d std\format.d \ - std\stdio.d std\perf.d std\uni.d \ + std\stdio.d std\perf.d std\uni.d std\uuid.d \ std\cstream.d std\demangle.d \ std\signals.d std\cpuid.d std\typetuple.d std\traits.d \ std\metastrings.d std\getopt.d \ @@ -314,6 +316,7 @@ DOCS= $(DOC)\object.html \ $(DOC)\std_uni.html \ $(DOC)\std_uri.html \ $(DOC)\std_utf.html \ + $(DOC)\std_uuid.html \ $(DOC)\std_variant.html \ $(DOC)\std_xml.html \ $(DOC)\std_zip.html \ @@ -343,12 +346,13 @@ $(LIB) : $(SRC_TO_COMPILE) \ $(DMD) -lib -of$(LIB) -Xfphobos.json $(DFLAGS) $(SRC_TO_COMPILE) \ etc\c\zlib\zlib.lib $(DRUNTIMELIB) -UNITTEST_OBJS= unittest1.obj unittest2.obj unittest3.obj +UNITTEST_OBJS= unittest1.obj unittest2.obj unittest3.obj unittest4.obj unittest : $(LIB) $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest1.obj $(SRC_STD_1_HEAVY) $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest2.obj $(SRC_STD_2_HEAVY) $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest3.obj $(SRC_STD_3) + $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest4.obj $(SRC_STD_4) $(DMD) $(UDFLAGS) -L/co -unittest unittest.d $(SRC_STD_REST) $(SRC_TO_COMPILE_NOT_STD) $(UNITTEST_OBJS) \ etc\c\zlib\zlib.lib $(DRUNTIMELIB) unittest @@ -596,6 +600,9 @@ $(DOC)\std_uri.html : $(STDDOC) std\uri.d $(DOC)\std_utf.html : $(STDDOC) std\utf.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_utf.html $(STDDOC) std\utf.d +$(DOC)\std_uuid.html : $(STDDOC) std\uuid.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_uuid.html $(STDDOC) std\uuid.d + $(DOC)\std_variant.html : $(STDDOC) std\variant.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_variant.html $(STDDOC) std\variant.d