123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445 |
|
/**
* A UUID is a Universally Unique Identifier.
* It is a 128-bit number generated either randomly or according to some
* inscrutable algorithm, depending on the UUID version used.
*
* Here, we implement a data structure for holding and formatting UUIDs.
* To generate a UUID, use one of the other modules in the UUID package.
* You can also create a UUID by parsing a string containing a textual
* representation of a UUID, or by providing the constituent bytes.
*/
module tango.util.uuid.Uuid;
import tango.core.Exception;
import Integer = tango.text.convert.Integer;
private union UuidData
{
uint[4] ui;
ubyte[16] ub;
}
/** This struct represents a UUID. It offers static members for creating and
* parsing UUIDs.
*
* This struct treats a UUID as an opaque type. The specification has fields
* for time, version, client MAC address, and several other data points, but
* these are meaningless for most applications and means of generating a UUID.
*
* There are versions of UUID generation involving the system time and MAC
* address. These are not used for several reasons:
* - One version contains identifying information, which is undesirable.
* - Ensuring uniqueness between processes requires inter-process
* communication. This would be unreasonably slow and complex.
* - Obtaining the MAC address is a system-dependent operation and beyond
* the scope of this module.
* - Using Java and .NET as a guide, they only implement randomized creation
* of UUIDs, not the MAC address/time based generation.
*
* When generating a random UUID, use a carefully seeded random number
* generator. A poorly chosen seed may produce undesirably consistent results.
*/
struct Uuid
{
private UuidData _data;
/** Copy the givent bytes into a UUID. If you supply more or fewer than
* 16 bytes, throws an IllegalArgumentException. */
public static Uuid opCall(const(ubyte[]) data)
{
if (data.length != 16)
{
throw new IllegalArgumentException("A UUID is 16 bytes long.");
}
Uuid u;
u._data.ub[] = data[];
return u;
}
/** Attempt to parse the representation of a UUID given in value. If the
* value is not in the correct format, throw IllegalArgumentException.
* If the value is in the correct format, return a UUID representing the
* given value.
*
* The following is an example of a UUID in the expected format:
* 67e55044-10b1-426f-9247-bb680e5fe0c8
*/
public static Uuid parse(const(char[]) value)
{
Uuid u;
if (!tryParse(value, u))
throw new IllegalArgumentException("'" ~ value.idup ~ "' is not in the correct format for a UUID");
return u;
}
/** Attempt to parse the representation of a UUID given in value. If the
* value is not in the correct format, return false rather than throwing
* an exception. If the value is in the correct format, set uuid to
* represent the given value.
*
* The following is an example of a UUID in the expected format:
* 67e55044-10b1-426f-9247-bb680e5fe0c8
*/
public static bool tryParse(const(char[]) value, out Uuid uuid)
{
if (value.length != 36 ||
value[8] != '-' ||
value[13] != '-' ||
value[18] != '-' ||
value[23] != '-')
{
return false;
}
int hyphens = 0;
foreach (i, v; value)
{
if ('a' <= v && 'f' >= v) continue;
if ('A' <= v && 'F' >= v) continue;
if ('0' <= v && '9' >= v) continue;
if (v == '-')
{
hyphens++;
continue;
}
// illegal character
return false;
}
if (hyphens != 4)
{
return false;
}
with (uuid._data)
{
// This is verbose, but it's simple, and it gets around endian
// issues if you try parsing an integer at a time.
ub[0] = cast(ubyte) Integer.parse(value[0..2], 16);
ub[1] = cast(ubyte) Integer.parse(value[2..4], 16);
ub[2] = cast(ubyte) Integer.parse(value[4..6], 16);
ub[3] = cast(ubyte) Integer.parse(value[6..8], 16);
ub[4] = cast(ubyte) Integer.parse(value[9..11], 16);
ub[5] = cast(ubyte) Integer.parse(value[11..13], 16);
ub[6] = cast(ubyte) Integer.parse(value[14..16], 16);
ub[7] = cast(ubyte) Integer.parse(value[16..18], 16);
ub[8] = cast(ubyte) Integer.parse(value[19..21], 16);
ub[9] = cast(ubyte) Integer.parse(value[21..23], 16);
ub[10] = cast(ubyte) Integer.parse(value[24..26], 16);
ub[11] = cast(ubyte) Integer.parse(value[26..28], 16);
ub[12] = cast(ubyte) Integer.parse(value[28..30], 16);
ub[13] = cast(ubyte) Integer.parse(value[30..32], 16);
ub[14] = cast(ubyte) Integer.parse(value[32..34], 16);
ub[15] = cast(ubyte) Integer.parse(value[34..36], 16);
}
return true;
}
/** Generate a UUID based on the given random number generator.
* The generator must have a method 'uint natural()' that returns
* a random number. The generated UUID conforms to version 4 of the
* specification. */
public static Uuid random(Random)(Random generator)
{
Uuid u;
with (u)
{
_data.ui[0] = generator.natural();
_data.ui[1] = generator.natural();
_data.ui[2] = generator.natural();
_data.ui[3] = generator.natural();
// v4: 7th bytes' first half is 0b0100: 4 in hex
_data.ub[6] &= 0b01001111;
_data.ub[6] |= 0b01000000;
// v4: 9th byte's 1st half is 0b1000 to 0b1011: 8, 9, A, B in hex
_data.ub[8] &= 0b10111111;
_data.ub[8] |= 0b10000000;
}
return u;
}
/* Generate a UUID based on the given namespace and name. This conforms to
* versions 3 and 5 of the standard -- version 3 if you use MD5, or version
* 5 if you use SHA1.
*
* You should pass 3 as the value for uuidVersion if you are using the
* MD5 hash, and 5 if you are using the SHA1 hash. To do otherwise is an
* Abomination Unto Nuggan.
*
* This method is exposed mainly for the convenience methods in
* tango.util.uuid.*. You can use this method directly if you prefer.
*/
public static Uuid byName(Digest)(Uuid namespace, const(char[]) name, Digest digest,
ubyte uuidVersion)
{
/* o Compute the hash of the name space ID concatenated with the name.
o Set octets zero through 15 to octets zero through 15 of the hash.
o Set the four most significant bits (bits 12 through 15) of octet
6 to the appropriate 4-bit version number from Section 4.1.3.
o Set the two most significant bits (bits 6 and 7) of octet 8 to
zero and one, respectively. */
auto nameBytes = namespace.toBytes();
nameBytes ~= cast(ubyte[])name;
digest.update(nameBytes);
nameBytes = digest.binaryDigest();
nameBytes[6] = cast(ubyte)((uuidVersion << 4) | (nameBytes[6] & 0b1111));
nameBytes[8] |= 0b1000_0000;
nameBytes[8] &= 0b1011_1111;
return Uuid(nameBytes[0..16]);
}
/** Return an empty UUID (with all bits set to 0). This doesn't conform
* to any particular version of the specification. It's equivalent to
* using an uninitialized UUID. This method is provided for clarity. */
@property public static Uuid empty()
{
Uuid uuid;
uuid._data.ui[] = 0;
return uuid;
}
/** Get a copy of this UUID's value as an array of unsigned bytes. */
public const ubyte[] toBytes()
{
return _data.ub.dup;
}
/** Gets the version of this UUID.
* RFC 4122 defines five types of UUIDs:
* - Version 1 is based on the system's MAC address and the current time.
* - Version 2 uses the current user's userid and user domain in
* addition to the time and MAC address.
* - Version 3 is namespace-based, as generated by the NamespaceGenV3
* module. It uses MD5 as a hash algorithm. RFC 4122 states that
* version 5 is preferred over version 3.
* - Version 4 is generated randomly.
* - Version 5 is like version 3, but uses SHA-1 rather than MD5. Use
* the NamespaceGenV5 module to create UUIDs like this.
*
* The following additional versions exist:
* - Version 0 is reserved for backwards compatibility.
* - Version 6 is a non-standard Microsoft extension.
* - Version 7 is reserved for future use.
*/
public const ubyte format()
{
return cast(ubyte) (_data.ub[6] >> 4);
}
/** Get the canonical string representation of a UUID.
* The canonical representation is in hexidecimal, with hyphens inserted
* after the eighth, twelfth, sixteenth, and twentieth digits. For example:
* 67e55044-10b1-426f-9247-bb680e5fe0c8
* This is the format used by the parsing functions.
*/
public const(char[]) toString()
{
// Look, only one allocation.
char[] buf = new char[36];
buf[8] = '-';
buf[13] = '-';
buf[18] = '-';
buf[23] = '-';
with (_data)
{
// See above with tryParse: this ignores endianness.
// Technically, it's sufficient that the conversion to string
// matches the conversion from string and from byte array. But
// this is the simplest way to make sure of that. Plus you can
// serialize and deserialize on machines with different endianness
// without a bunch of strange conversions, and with consistent
// string representations.
Integer.format(buf[0..2], ub[0], "x2");
Integer.format(buf[2..4], ub[1], "x2");
Integer.format(buf[4..6], ub[2], "x2");
Integer.format(buf[6..8], ub[3], "x2");
Integer.format(buf[9..11], ub[4], "x2");
Integer.format(buf[11..13], ub[5], "x2");
Integer.format(buf[14..16], ub[6], "x2");
Integer.format(buf[16..18], ub[7], "x2");
Integer.format(buf[19..21], ub[8], "x2");
Integer.format(buf[21..23], ub[9], "x2");
Integer.format(buf[24..26], ub[10], "x2");
Integer.format(buf[26..28], ub[11], "x2");
Integer.format(buf[28..30], ub[12], "x2");
Integer.format(buf[30..32], ub[13], "x2");
Integer.format(buf[32..34], ub[14], "x2");
Integer.format(buf[34..36], ub[15], "x2");
}
return buf;
}
/** Determines if this UUID has the same value as another. */
public const bool opEquals(ref const(Uuid) other)
{
return
_data.ui[0] == other._data.ui[0] &&
_data.ui[1] == other._data.ui[1] &&
_data.ui[2] == other._data.ui[2] &&
_data.ui[3] == other._data.ui[3];
}
/** Get a hash code representing this UUID. */
public const hash_t toHash() nothrow @safe
{
with (_data)
{
// 29 is just a convenient prime number
return (((((ui[0] * 29) ^ ui[1]) * 29) ^ ui[2]) * 29) ^ ui[3];
}
}
}
debug (UnitTest)
{
import tango.math.random.Kiss;
unittest
{
// Generate them in the correct format
for (int i = 0; i < 20; i++)
{
auto uu = Uuid.random(&Kiss.instance).toString();
auto c = uu[19];
assert (c == '9' || c == '8' || c == 'a' || c == 'b', uu);
auto d = uu[14];
assert (d == '4', uu);
}
// empty
assert (Uuid.empty.toString() == "00000000-0000-0000-0000-000000000000", Uuid.empty.toString());
ubyte[] bytes = [0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1,
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8];
Uuid u = Uuid(bytes.dup);
auto str = "64f2ad82-5182-4c6a-ade5-59728ca0567b";
auto u2 = Uuid.parse(str);
// toString
assert (Uuid(bytes) == u);
assert (u2 != u);
assert (u2.format() == 4);
// tryParse
Uuid u3;
assert (Uuid.tryParse(str, u3));
assert (u3 == u2);
}
unittest
{
Uuid fail;
// contains 'r'
assert (!Uuid.tryParse("fecr0a9b-4d5a-439e-8e4b-9d087ff49ba7", fail));
// too short
assert (!Uuid.tryParse("fec70a9b-4d5a-439e-8e4b-9d087ff49ba", fail));
// hyphens matter
assert (!Uuid.tryParse("fec70a9b 4d5a-439e-8e4b-9d087ff49ba7", fail));
// hyphens matter (2)
assert (!Uuid.tryParse("fec70a9b-4d5a-439e-8e4b-9d08-7ff49ba7", fail));
// hyphens matter (3)
assert (!Uuid.tryParse("fec70a9b-4d5a-439e-8e4b-9d08-ff49ba7", fail));
}
unittest
{
// contains 'r'
try
{
Uuid.parse("fecr0a9b-4d5a-439e-8e4b-9d087ff49ba7"); assert (false);
}
catch (IllegalArgumentException) {}
// too short
try
{
Uuid.parse("fec70a9b-4d5a-439e-8e4b-9d087ff49ba"); assert (false);
}
catch (IllegalArgumentException) {}
// hyphens matter
try
{
Uuid.parse("fec70a9b 4d5a-439e-8e4b-9d087ff49ba7"); assert (false);
}
catch (IllegalArgumentException) {}
// hyphens matter (2)
try
{
Uuid.parse("fec70a9b-4d5a-439e-8e4b-9d08-7ff49ba7"); assert (false);
}
catch (IllegalArgumentException) {}
// hyphens matter (3)
try
{
Uuid.parse("fec70a9b-4d5a-439e-8e4b-9d08-ff49ba7"); assert (false);
}
catch (IllegalArgumentException) {}
}
import tango.util.digest.Sha1;
unittest
{
auto namespace = Uuid.parse("15288517-c402-4057-9fc5-05711726df41");
auto name = "hello";
// This was generated with the uuid utility on linux/amd64. It might have different results on
// a ppc processor -- the spec says something about network byte order, but it's using an array
// of bytes at that point, so converting to NBO is a noop...
auto expected = Uuid.parse("2b1c6704-a43f-5d43-9abb-b13310b4458a");
auto generated = Uuid.byName(namespace, name, new Sha1, cast(ubyte)5);
assert (generated == expected, "\nexpected: " ~ expected.toString() ~ "\nbut was: " ~ generated.toString());
}
import tango.util.digest.Md5;
unittest
{
auto namespace = Uuid.parse("15288517-c402-4057-9fc5-05711726df41");
auto name = "hello";
auto expected = Uuid.parse("31a2b702-85a8-349a-9b0e-213b1bd753b8");
auto generated = Uuid.byName(namespace, name, new Md5, cast(ubyte)3);
assert (generated == expected, "\nexpected: " ~ expected.toString() ~ "\nbut was: " ~ generated.toString());
}
//void main(){}
}
/** A base interface for any UUID generator for UUIDs. That is,
* this interface is specified so that you write your code dependent on a
* UUID generator that takes an arbitrary random source, and easily switch
* to a different random source. Since the default uses KISS, if you find
* yourself needing more secure random numbers, you could trivially switch
* your code to use the Mersenne twister, or some other PRNG.
*
* You could also, if you wish, use this to switch to deterministic UUID
* generation, if your needs require it.
*/
interface UuidGen
{
Uuid next();
}
/** Given a random number generator conforming to Tango's standard random
* interface, this will generate random UUIDs according to version 4 of
* RFC 4122. */
class RandomGen(TRandom) : UuidGen
{
TRandom random;
this (TRandom random)
{
this.random = random;
}
Uuid next()
{
return Uuid.random(random);
}
}
|