diff options
author | Iain Buclaw <ibuclaw@gcc.gnu.org> | 2018-10-28 19:51:47 +0000 |
---|---|---|
committer | Iain Buclaw <ibuclaw@gcc.gnu.org> | 2018-10-28 19:51:47 +0000 |
commit | b4c522fabd0df7be08882d2207df8b2765026110 (patch) | |
tree | b5ffc312b0a441c1ba24323152aec463fdbe5e9f /libphobos/src/std/uuid.d | |
parent | 01ce9e31a02c8039d88e90f983735104417bf034 (diff) | |
download | gcc-b4c522fabd0df7be08882d2207df8b2765026110.zip gcc-b4c522fabd0df7be08882d2207df8b2765026110.tar.gz gcc-b4c522fabd0df7be08882d2207df8b2765026110.tar.bz2 |
Add D front-end, libphobos library, and D2 testsuite.
ChangeLog:
* Makefile.def (target_modules): Add libphobos.
(flags_to_pass): Add GDC, GDCFLAGS, GDC_FOR_TARGET and
GDCFLAGS_FOR_TARGET.
(dependencies): Make libphobos depend on libatomic, libbacktrace
configure, and zlib configure.
(language): Add language d.
* Makefile.in: Rebuild.
* Makefile.tpl (BUILD_EXPORTS): Add GDC and GDCFLAGS.
(HOST_EXPORTS): Add GDC.
(POSTSTAGE1_HOST_EXPORTS): Add GDC and GDC_FOR_BUILD.
(BASE_TARGET_EXPORTS): Add GDC.
(GDC_FOR_BUILD, GDC, GDCFLAGS): New variables.
(GDC_FOR_TARGET, GDC_FLAGS_FOR_TARGET): New variables.
(EXTRA_HOST_FLAGS): Add GDC.
(STAGE1_FLAGS_TO_PASS): Add GDC.
(EXTRA_TARGET_FLAGS): Add GDC and GDCFLAGS.
* config-ml.in: Treat GDC and GDCFLAGS like other compiler/flag
environment variables.
* configure: Rebuild.
* configure.ac: Add target-libphobos to target_libraries. Set and
substitute GDC_FOR_BUILD and GDC_FOR_TARGET.
config/ChangeLog:
* multi.m4: Set GDC.
gcc/ChangeLog:
* Makefile.in (tm_d_file_list, tm_d_include_list): New variables.
(TM_D_H, D_TARGET_DEF, D_TARGET_H, D_TARGET_OBJS): New variables.
(tm_d.h, cs-tm_d.h, default-d.o): New rules.
(d/d-target-hooks-def.h, s-d-target-hooks-def-h): New rules.
(s-tm-texi): Also check timestamp on d-target.def.
(generated_files): Add TM_D_H and d-target-hooks-def.h.
(build/genhooks.o): Also depend on D_TARGET_DEF.
* config.gcc (tm_d_file, d_target_objs, target_has_targetdm): New
variables.
* config/aarch64/aarch64-d.c: New file.
* config/aarch64/aarch64-linux.h (GNU_USER_TARGET_D_CRITSEC_SIZE):
Define.
* config/aarch64/aarch64-protos.h (aarch64_d_target_versions): New
prototype.
* config/aarch64/aarch64.h (TARGET_D_CPU_VERSIONS): Define.
* config/aarch64/t-aarch64 (aarch64-d.o): New rule.
* config/arm/arm-d.c: New file.
* config/arm/arm-protos.h (arm_d_target_versions): New prototype.
* config/arm/arm.h (TARGET_D_CPU_VERSIONS): Define.
* config/arm/linux-eabi.h (EXTRA_TARGET_D_OS_VERSIONS): Define.
* config/arm/t-arm (arm-d.o): New rule.
* config/default-d.c: New file.
* config/glibc-d.c: New file.
* config/gnu.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/i386/i386-d.c: New file.
* config/i386/i386-protos.h (ix86_d_target_versions): New prototype.
* config/i386/i386.h (TARGET_D_CPU_VERSIONS): Define.
* config/i386/linux-common.h (EXTRA_TARGET_D_OS_VERSIONS): Define.
(GNU_USER_TARGET_D_CRITSEC_SIZE): Define.
* config/i386/t-i386 (i386-d.o): New rule.
* config/kfreebsd-gnu.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/kopensolaris-gnu.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/linux-android.h (ANDROID_TARGET_D_OS_VERSIONS): Define.
* config/linux.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/mips/linux-common.h (EXTRA_TARGET_D_OS_VERSIONS): Define.
* config/mips/mips-d.c: New file.
* config/mips/mips-protos.h (mips_d_target_versions): New prototype.
* config/mips/mips.h (TARGET_D_CPU_VERSIONS): Define.
* config/mips/t-mips (mips-d.o): New rule.
* config/powerpcspe/linux.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/powerpcspe/linux64.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/powerpcspe/powerpcspe-d.c: New file.
* config/powerpcspe/powerpcspe-protos.h (rs6000_d_target_versions):
New prototype.
* config/powerpcspe/powerpcspe.c (rs6000_output_function_epilogue):
Support GNU D by using 0 as the language type.
* config/powerpcspe/powerpcspe.h (TARGET_D_CPU_VERSIONS): Define.
* config/powerpcspe/t-powerpcspe (powerpcspe-d.o): New rule.
* config/riscv/riscv-d.c: New file.
* config/riscv/riscv-protos.h (riscv_d_target_versions): New
prototype.
* config/riscv/riscv.h (TARGET_D_CPU_VERSIONS): Define.
* config/riscv/t-riscv (riscv-d.o): New rule.
* config/rs6000/linux.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/rs6000/linux64.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/rs6000/rs6000-d.c: New file.
* config/rs6000/rs6000-protos.h (rs6000_d_target_versions): New
prototype.
* config/rs6000/rs6000.c (rs6000_output_function_epilogue):
Support GNU D by using 0 as the language type.
* config/rs6000/rs6000.h (TARGET_D_CPU_VERSIONS): Define.
* config/rs6000/t-rs6000 (rs6000-d.o): New rule.
* config/s390/s390-d.c: New file.
* config/s390/s390-protos.h (s390_d_target_versions): New prototype.
* config/s390/s390.h (TARGET_D_CPU_VERSIONS): Define.
* config/s390/t-s390 (s390-d.o): New rule.
* config/sparc/sparc-d.c: New file.
* config/sparc/sparc-protos.h (sparc_d_target_versions): New
prototype.
* config/sparc/sparc.h (TARGET_D_CPU_VERSIONS): Define.
* config/sparc/t-sparc (sparc-d.o): New rule.
* config/t-glibc (glibc-d.o): New rule.
* configure: Regenerated.
* configure.ac (tm_d_file): New variable.
(tm_d_file_list, tm_d_include_list, d_target_objs): Add substitutes.
* doc/contrib.texi (Contributors): Add self for the D frontend.
* doc/frontends.texi (G++ and GCC): Mention D as a supported language.
* doc/install.texi (Configuration): Mention libphobos as an option for
--enable-shared. Mention d as an option for --enable-languages.
(Testing): Mention check-d as a target.
* doc/invoke.texi (Overall Options): Mention .d, .dd, and .di as file
name suffixes. Mention d as a -x option.
* doc/sourcebuild.texi (Top Level): Mention libphobos.
* doc/standards.texi (Standards): Add section on D language.
* doc/tm.texi: Regenerated.
* doc/tm.texi.in: Add @node for D language and ABI, and @hook for
TARGET_CPU_VERSIONS, TARGET_D_OS_VERSIONS, and TARGET_D_CRITSEC_SIZE.
* dwarf2out.c (is_dlang): New function.
(gen_compile_unit_die): Use DW_LANG_D for D.
(declare_in_namespace): Return module die for D, instead of adding
extra declarations into the namespace.
(gen_namespace_die): Generate DW_TAG_module for D.
(gen_decl_die): Handle CONST_DECLSs for D.
(dwarf2out_decl): Likewise.
(prune_unused_types_walk_local_classes): Handle DW_tag_interface_type.
(prune_unused_types_walk): Handle DW_tag_interface_type same as other
kinds of aggregates.
* gcc.c (default_compilers): Add entries for .d, .dd and .di.
* genhooks.c: Include d/d-target.def.
gcc/po/ChangeLog:
* EXCLUDES: Add sources from d/dmd.
gcc/testsuite/ChangeLog:
* gcc.misc-tests/help.exp: Add D to option descriptions check.
* gdc.dg/asan/asan.exp: New file.
* gdc.dg/asan/gdc272.d: New test.
* gdc.dg/compilable.d: New test.
* gdc.dg/dg.exp: New file.
* gdc.dg/gdc254.d: New test.
* gdc.dg/gdc260.d: New test.
* gdc.dg/gdc270a.d: New test.
* gdc.dg/gdc270b.d: New test.
* gdc.dg/gdc282.d: New test.
* gdc.dg/gdc283.d: New test.
* gdc.dg/imports/gdc170.d: New test.
* gdc.dg/imports/gdc231.d: New test.
* gdc.dg/imports/gdc239.d: New test.
* gdc.dg/imports/gdc241a.d: New test.
* gdc.dg/imports/gdc241b.d: New test.
* gdc.dg/imports/gdc251a.d: New test.
* gdc.dg/imports/gdc251b.d: New test.
* gdc.dg/imports/gdc253.d: New test.
* gdc.dg/imports/gdc254a.d: New test.
* gdc.dg/imports/gdc256.d: New test.
* gdc.dg/imports/gdc27.d: New test.
* gdc.dg/imports/gdcpkg256/package.d: New test.
* gdc.dg/imports/runnable.d: New test.
* gdc.dg/link.d: New test.
* gdc.dg/lto/lto.exp: New file.
* gdc.dg/lto/ltotests_0.d: New test.
* gdc.dg/lto/ltotests_1.d: New test.
* gdc.dg/runnable.d: New test.
* gdc.dg/simd.d: New test.
* gdc.test/gdc-test.exp: New file.
* lib/gdc-dg.exp: New file.
* lib/gdc.exp: New file.
libphobos/ChangeLog:
* Makefile.am: New file.
* Makefile.in: New file.
* acinclude.m4: New file.
* aclocal.m4: New file.
* config.h.in: New file.
* configure: New file.
* configure.ac: New file.
* d_rules.am: New file.
* libdruntime/Makefile.am: New file.
* libdruntime/Makefile.in: New file.
* libdruntime/__entrypoint.di: New file.
* libdruntime/__main.di: New file.
* libdruntime/gcc/attribute.d: New file.
* libdruntime/gcc/backtrace.d: New file.
* libdruntime/gcc/builtins.d: New file.
* libdruntime/gcc/config.d.in: New file.
* libdruntime/gcc/deh.d: New file.
* libdruntime/gcc/libbacktrace.d.in: New file.
* libdruntime/gcc/unwind/arm.d: New file.
* libdruntime/gcc/unwind/arm_common.d: New file.
* libdruntime/gcc/unwind/c6x.d: New file.
* libdruntime/gcc/unwind/generic.d: New file.
* libdruntime/gcc/unwind/package.d: New file.
* libdruntime/gcc/unwind/pe.d: New file.
* m4/autoconf.m4: New file.
* m4/druntime.m4: New file.
* m4/druntime/cpu.m4: New file.
* m4/druntime/libraries.m4: New file.
* m4/druntime/os.m4: New file.
* m4/gcc_support.m4: New file.
* m4/gdc.m4: New file.
* m4/libtool.m4: New file.
* src/Makefile.am: New file.
* src/Makefile.in: New file.
* src/libgphobos.spec.in: New file.
* testsuite/Makefile.am: New file.
* testsuite/Makefile.in: New file.
* testsuite/config/default.exp: New file.
* testsuite/lib/libphobos-dg.exp: New file.
* testsuite/lib/libphobos.exp: New file.
* testsuite/testsuite_flags.in: New file.
From-SVN: r265573
Diffstat (limited to 'libphobos/src/std/uuid.d')
-rw-r--r-- | libphobos/src/std/uuid.d | 1731 |
1 files changed, 1731 insertions, 0 deletions
diff --git a/libphobos/src/std/uuid.d b/libphobos/src/std/uuid.d new file mode 100644 index 0000000..c804e8e --- /dev/null +++ b/libphobos/src/std/uuid.d @@ -0,0 +1,1731 @@ +/** + * 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 + * $(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. + * + * 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">$(D $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 + * $(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 + } + + 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 in ubyte[16] uuidData) + { + data = uuidData; + } + /// ditto + @safe pure nothrow @nogc this(in 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; + + 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(in 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 in UUID s) const + { + return ulongs[0] == s.ulongs[0] && ulongs[1] == s.ulongs[1]; + } + + /** + * ditto + */ + @safe pure nothrow @nogc int opCmp(in UUID s) const + { + import std.algorithm.comparison : cmp; + return cmp(this.data[], s.data[]); + } + + /** + * ditto + */ + @safe pure nothrow @nogc int opCmp(ref in UUID s) const + { + import std.algorithm.comparison : cmp; + return cmp(this.data[], s.data[]); + } + + /** + * ditto + */ + @safe pure nothrow @nogc UUID opAssign(in UUID s) + { + ulongs[0] = s.ulongs[0]; + ulongs[1] = s.ulongs[1]; + return this; + } + + /** + * ditto + */ + @safe pure nothrow @nogc UUID opAssign(ref in 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); + } + foreach (i, c; result) + { + static if (__traits(compiles, put(sink, c))) + put(sink, c); + else + sink[i] = cast(typeof(sink[i]))c; + } + } + + /** + * 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; + 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(const(char)[] data) + { + buf ~= data; + } + u1.toString(&sink); + assert(buf == "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 $(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 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 $(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!). + */ +@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 $(D 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 $(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!). + */ +@safe pure nothrow @nogc UUID sha1UUID(in char[] name, const UUID namespace = UUID.init) +{ + return sha1UUID(cast(const(ubyte[]))name, namespace); +} + +/// ditto +@safe pure nothrow @nogc UUID sha1UUID(in ubyte[] data, 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; + return randomUUID(rndGen); +} + +/// 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); +} + +/* + * 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. + */ +@safe unittest +{ + import std.random : rndGen, Mt19937; + static assert(is(typeof(rndGen) == Mt19937)); +} + +@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 + && is(Unqual!(ElementType!Range) == dchar)) +{ + 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)); + } + + 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")); + } +} + +/** + * 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); +} |