From 9bec75951e7503e087553e7c49a606fde47a1066 Mon Sep 17 00:00:00 2001 From: Dave Hall Date: Sun, 28 Aug 2011 00:59:42 +1000 Subject: [PATCH 3/3] more RFC compliant UUID generator with better docs and tests --- includes/uuid.inc | 121 ++++++++++++++++++++++++++++++++++++++++++++ modules/system/system.test | 43 ++++++++++++++++ 2 files changed, 164 insertions(+), 0 deletions(-) create mode 100644 includes/uuid.inc diff --git a/includes/uuid.inc b/includes/uuid.inc new file mode 100644 index 0000000..899f5a6 --- /dev/null +++ b/includes/uuid.inc @@ -0,0 +1,121 @@ +plugin = new $class(); + } + + /** + * Generates an universally unique identifier. + * + * @see UuidInterface::generate() + */ + public function generate() { + return $this->plugin->generate(); + } + + /** + * Check that a string appears to be in the format of a UUID. + * + * Plugins should not implement validation, since UUIDs should be in a + * consistent format across all plugins. + * + * @param $uuid + * The string to test. + * @return + * TRUE if the string is well formed. + */ + public function isValid($uuid) { + return preg_match("/^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/", $uuid); + } +} + +/** + * UUID implementation using the PECL extension. + */ +class UuidPecl implements UuidInterface { + public function generate() { + return uuid_create(UUID_TYPE_DEFAULT); + } +} + +/** + * UUID implementation using the Windows internal GUID extension. + * + * @see http://php.net/com_create_guid + */ +class UuidCom implements UuidInterface { + public function generate() { + // Remove {} wrapper and make lower case to keep result consistent. + return drupal_strtolower(trim(com_create_guid(), '{}')); + } +} + +/** + * Generates an UUID v4 using PHP code. + * + * Loosely based on Ruby's UUIDTools generate_random logic. + * + * @see http://uuidtools.rubyforge.org/api/classes/UUIDTools/UUID.html + */ +class UuidPhp implements UuidInterface { + public function generate() { + $hex = substr(hash('sha256', drupal_random_bytes(16)), 0, 32); + + // The field names refer to RFC 4122 section 4.1.2. + $time_low = substr($hex, 0, 8); + $time_mid = substr($hex, 8, 4); + + $time_hi_and_version = base_convert(substr($hex, 12, 4), 16, 10); + $time_hi_and_version &= 0x0FFF; + $time_hi_and_version |= (4 << 12); + + $clock_seq_hi_and_reserved = base_convert(substr($hex, 16, 4), 16, 10); + $clock_seq_hi_and_reserved &= 0x3F; + $clock_seq_hi_and_reserved |= 0x80; + + $clock_seq_low = substr($hex, 20, 2); + $nodes = substr($hex, 20); + + $uuid = sprintf('%s-%s-%04x-%02x%02x-%s', + $time_low, $time_mid, + $time_hi_and_version, $clock_seq_hi_and_reserved, + $clock_seq_low, $nodes); + + return $uuid; + } +} diff --git a/modules/system/system.test b/modules/system/system.test index 9944619..ec5b462 100644 --- a/modules/system/system.test +++ b/modules/system/system.test @@ -2466,3 +2466,46 @@ class SystemIndexPhpTest extends DrupalWebTestCase { } } +/** + * Tests uuid.inc and related functions. + */ +class UuidUnitTestCase extends DrupalUnitTestCase { + public static function getInfo() { + return array( + 'name' => 'UUID handling', + 'description' => "Test the handling of Universally Unique IDentifiers (UUIDs).", + 'group' => 'System', + ); + } + + /** + * Test UUID handling. + */ + function testUuidHandling() { + // Initiate the generator. This will lazy-load uuid.inc. + $uuid = new Uuid(); + + // This is a valid UUID, we know that. + $uuid_fqdn = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; + $uuid_min = '00000000-0000-0000-0000-000000000000'; + $uuid_max = 'ffffffff-ffff-ffff-ffff-ffffffffffff'; + $invalid_format = '0ab26e6b-f074-4e44-9da-601205fa0e976'; + $invalid_length = '0ab26e6b-f074-4e44-9daf-1205fa0e9761f'; + + $this->assertTrue($uuid->isValid($uuid_fqdn), t('FQDN namespace UUID (@uuid) is valid', array('@uuid' => $uuid_fqdn))); + $this->assertTrue($uuid->isValid($uuid_min), t('Minimum UUID value (@uuid) is valid', array('@uuid', $uuid_min))); + $this->assertTrue($uuid->isValid($uuid_max), t('Maximum UUID value (@uuid) is valid', array('@uuid', $uuid_max))); + + // Invalid UUIDs. + $this->assertFalse($uuid->isValid($invalid_format), t('@uuid is not a valid UUID', array('uuid' => $invalid_format))); + $this->assertFalse($uuid->isValid($invalid_length), t('@uuid is not a valid UUID', array('uuid' => $invalid_length))); + + // Test generating a UUID. + $uuid1 = $uuid->generate(); + $this->assertTrue($uuid->isValid($uuid1), 'UUID generation works.'); + + // Verify that 2 UUID are in fact unique. + $uuid2 = $uuid->generate(); + $this->assertNotEqual($uuid1, $uuid2, 'Same UUID was not generated twice.'); + } +} -- 1.7.4.1