From ab72745a8d089f4ad0d59c3a2e5c78f6c9144086 Mon Sep 17 00:00:00 2001 From: Chalkias Tasos Date: Thu, 21 Nov 2013 13:47:08 +0200 Subject: [PATCH 1/4] Ignore PhpStorm files. Signed-off-by: Chalkias Tasos --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5657f6e..cade4c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -vendor \ No newline at end of file +vendor +/.idea From b797766bbc7f6b931ee9a24e5c4a0ebd34d3f7d5 Mon Sep 17 00:00:00 2001 From: Chalkias Tasos Date: Thu, 21 Nov 2013 13:47:20 +0200 Subject: [PATCH 2/4] Update Composer package dependencies. Signed-off-by: Chalkias Tasos --- composer.lock | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/composer.lock b/composer.lock index 29ebc34..ccc08da 100644 --- a/composer.lock +++ b/composer.lock @@ -1,5 +1,9 @@ { - "hash": "bc20a38645558f91ae19a28e2e9950d4", + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" + ], + "hash": "66ced95c19f95960857667c11709849a", "packages": [ ], @@ -9,19 +13,18 @@ "version": "v1.1.0", "source": { "type": "git", - "url": "https://github.com/mikey179/vfsStream", - "reference": "v1.1.0" + "url": "https://github.com/mikey179/vfsStream.git", + "reference": "fc0fe8f4d0b527254a2dc45f0c265567c881d07e" }, "dist": { "type": "zip", - "url": "https://github.com/mikey179/vfsStream/zipball/v1.1.0", - "reference": "v1.1.0", + "url": "https://api.github.com/repos/mikey179/vfsStream/zipball/fc0fe8f4d0b527254a2dc45f0c265567c881d07e", + "reference": "fc0fe8f4d0b527254a2dc45f0c265567c881d07e", "shasum": "" }, "require": { "php": ">=5.3.0" }, - "time": "2012-08-25 05:49:29", "type": "library", "autoload": { "psr-0": { @@ -32,7 +35,8 @@ "license": [ "BSD" ], - "homepage": "http://vfs.bovigo.org/" + "homepage": "http://vfs.bovigo.org/", + "time": "2012-08-25 12:49:29" } ], "aliases": [ @@ -41,5 +45,11 @@ "minimum-stability": "stable", "stability-flags": [ + ], + "platform": { + "php": ">=5.3.2" + }, + "platform-dev": [ + ] } From c7f2ae03adc67427bca04d300f096af9c401642b Mon Sep 17 00:00:00 2001 From: Chalkias Tasos Date: Thu, 21 Nov 2013 13:47:31 +0200 Subject: [PATCH 3/4] Add UUID v4 generation capabilities. Signed-off-by: Chalkias Tasos --- lib/PasswordLib/Uuid/UuidGenerator.php | 339 +++++++++++++++++++++++++ test/Unit/Uuid/UuidGeneratorTest.php | 98 +++++++ 2 files changed, 437 insertions(+) create mode 100644 lib/PasswordLib/Uuid/UuidGenerator.php create mode 100644 test/Unit/Uuid/UuidGeneratorTest.php diff --git a/lib/PasswordLib/Uuid/UuidGenerator.php b/lib/PasswordLib/Uuid/UuidGenerator.php new file mode 100644 index 0000000..e2c83a9 --- /dev/null +++ b/lib/PasswordLib/Uuid/UuidGenerator.php @@ -0,0 +1,339 @@ + + */ +class UuidGenerator { + + /** + * The integer in hexadecimal notation that denotes the version of UUIDs that + * are comprised of random numbers. + * + * @var int + */ + const UUID_VERSION_4 = 0x40; + + /** + * A list of integers that correspond to the indices where the hyphens (-) + * are expected to be present in a formatted UUID. + * + * Notice that the indices are zero-based. + * + * @var int[] + */ + protected static $separatorsIdxs = array(8, 12, 16, 20); + + /** + * The size of the UUID in bytes. + * + * @var int + */ + protected static $uuidByteSize = 16; + + /**#@+ + * The integers denoting the index of the byte to be manipulated. + * + * A big-endian byte representation is assumed. + * + * @var int + */ + + /** + * The index of the byte that indicates the variant of the UUID. + * + */ + protected static $variantByteIndex = 9; + + /** + * The index of the byte that indicates the version of the UUID. + * + */ + protected static $versionByteIndex = 7; + /**#@-*/ + + /** + * The singleton instance of this class. + * + * @var UuidGenerator + */ + private static $instance = null; + + /** + * The random numbers factory. + * + * @var Factory + */ + private $factory; + + /** + * The random number generator utilized by this UuidGenerator. + * + * @var Generator + */ + private $rng = null; + + /** + * Returns the singleton UuidGenerator for the current process. + * + * @return UuidGenerator The singleton instance of this class. + */ + public static function getInstance() { + if (static::$instance === null) { + static::$instance = new static(new Factory()); + } + + return static::$instance; + } + + /** + * Generates a new version 4 UUID utilizing a medium strength generator of + * the PasswordLib. + * + * If the {@link Generator} has yet to be instantiated, then it is constructed + * here. Through this tactic we achieve [lazy loading][1] of the generator. + * + * + * [1]: http://en.wikipedia.org/wiki/Lazy_loading + * "Lazy Loading | Wikipedia" + * + * @return string The new version 4 UUID. + */ + public function generateVersion4Uuid() { + if ($this->rng === null) { + $this->rng = $this->factory->getMediumStrengthGenerator(); + } + + $randomString = $this->rng->generate(static::$uuidByteSize); + $bytes = $this->getBytesOf($randomString); + $this->setVersionToUUID($bytes, static::UUID_VERSION_4); + $this->setVariantToUUID($bytes); + $hexString = $this->getHexadecimalOf($bytes); + + return $this->formatUuid($hexString); + } + + /** + * Returns the factory utilized by this UuidGenerator for the creation of + * random numbers generators. + * + * @return Factory The random numbers generator factory. + */ + public function getFactory() { + return $this->factory; + } + + /** + * Returns the random numbers generator utilized by this UuidGenerator. + * + * This is a medium strength {@link Generator}. In addition, it is instantiated + * only once during the first call to the {@link #generateVersion4Uuid} method. + * + * @return Generator + */ + public function getRandomNumberGenerator() { + return $this->rng; + } + + /** + * Creates a new UuidGenerator. + * + * The {@link Factory factory} is utilized by this UuidGenerator for the + * construction of a medium strength {@link Generator}. + * + * @param Factory $factory The factory of random number. + * @throws \InvalidArgumentException if `$factory` is null. + */ + protected function __construct(Factory $factory) { + if ($factory === null) { + throw new \InvalidArgumentException('No factory was provided.'); + } + $this->factory = $factory; + } + + /** + * Formats the hexadecimal representation of a UUID to the official form as + * described in [RFC 4122][1]. + * + * The algorithm used can be described by the following code: + * + * $uuid = substr($hexString, 0, 8) . '-' + * . substr($hexString, 8, 4) . '-' + * . substr($hexString, 12, 4) . '-' + * . substr($hexString, 16, 4) . '-' + * . substr($hexString, 20); + * + * + * [1]: http://tools.ietf.org/html/rfc4122 + * "RFC 4122: A Universally Unique IDentifier (UUID) URN Namespace" + * + * @param string $hexString The string containing the hexadecimal representation + * of the UUID. + * @return string The string representation of the UUID in the official + * format. + */ + protected function formatUuid($hexString) { + $uuid = ''; + $lastIdx = 0; + foreach (static::$separatorsIdxs as $sepIdx) { + $numOfCharsToCut = $sepIdx - $lastIdx; + $uuid .= substr($hexString, $lastIdx, $numOfCharsToCut) . '-'; + $lastIdx = $sepIdx; + } + $uuid .= substr($hexString, $lastIdx); + + return $uuid; + } + + /** + * Calculates the byte representation of a string. + * + * This method uses the PHP [unpack][1] function in order to get the byte + * representation of string. + * + * Every character of string is expected to be one byte long. That + * goes to say that this method does not support multi-byte strings. + * + * Moreover, the format of the array returned is the following: + * + * array( + * index: int => byte representation: int + * ) + * + * + * The indices start from the number one. So, the first byte is given by + * code such as: + * + * $bytes[1] + * + * + * Furthermore, the representation is an integer in the space [0, 255]. + * + *

Example

+ * + * The code below: + * + * $bytes = getBytesOf("Hello, world!"); + * print_r($bytes); + * + * + * would result in the following output: + * + * Array ( + * [1] => 72 + * [2] => 101 + * [3] => 108 + * [4] => 108 + * [5] => 111 + * [6] => 32 + * [7] => 119 + * [8] => 111 + * [9] => 114 + * [10] => 108 + * [11] => 100 + * [12] => 33 + * ) + * + * + * + * [1]: http://php.net/manual/en/function.unpack.php "PHP unpack function" + * + * @param string $string The string whose byte representation this method + * calculates. + * @return array The array containing the bytes of the string. + * Pay attention to the fact that the array's first index is one (and not + * zero). + * @throws \InvalidArgumentException if `$string` is null. + */ + protected function getBytesOf($string) { + if ($string === null) { + throw new \InvalidArgumentException('No string was provided.'); + } + + return unpack('C*', $string); + } + + /** + * Calculates the hexadecimal string representation of an array of bytes. + * + * This method performs the reverse operation of the {@link #getBytesOf} + * method. + * + * @param array $bytes The array of bytes whose hexadecimal string representation + * this method creates. + * @return string The string representation of bytes in hexadecimal + * notation. + */ + protected function getHexadecimalOf(array $bytes) { + $binaryString = call_user_func_array( + 'pack', + array_merge( + array('C*'), + $bytes + ) + ); + + return bin2hex($binaryString); + } + + /** + * Sets the variant to the UUID provided. + * + * The UUID is expected to be given in big-endian byte representation. + * + * The variant is set by manipulating the uuidBytes with bitwise + * operations. + * + * @param array $uuidBytes The byte representation of the UUID. + * @return void + * @see http://tinyurl.com/bcd69xo + */ + protected function setVariantToUUID(array &$uuidBytes) { + $uuidBytes[self::$variantByteIndex] &= 0x3f; + $uuidBytes[self::$variantByteIndex] |= 0x80; + } + + /** + * Sets the version number to a UUID. + * + * The UUID is expected to be given in big-endian byte representation. + * + * The version is set by manipulating the uuidBytes + * with bitwise operations. + * + * It is due to the use of bitwise operators that version is + * expected in hexadecimal format. That is, version four should be given + * like this: + * 0x40 + * + * @param array $uuidBytes The byte representation of the UUID. + * @param int $version The integer in hexadecimal notation that denotes the + * UUID version to be set. + * @return void + */ + protected function setVersionToUUID(array &$uuidBytes, $version) { + $uuidBytes[self::$versionByteIndex] &= 0x0f; + $uuidBytes[self::$versionByteIndex] |= $version; + } +} \ No newline at end of file diff --git a/test/Unit/Uuid/UuidGeneratorTest.php b/test/Unit/Uuid/UuidGeneratorTest.php new file mode 100644 index 0000000..f8483f5 --- /dev/null +++ b/test/Unit/Uuid/UuidGeneratorTest.php @@ -0,0 +1,98 @@ + + */ +class Unit_Uuid_UuidGeneratorTest extends PHPUnit_Framework_TestCase { + + /** + * The string denoting the regular expression pattern for a character in + * hexadecimal notation. + * + * @var string + */ + private static $hexCharPattern = '[0-9a-f]'; + + /** + * The string denoting the regular expression pattern of the UUID variant. + * + * @var string + */ + private static $variantPattern = '[89ab]'; + + /** + * The string denoting the regular expression pattern of the UUID version. + * + * @var string + */ + private static $versionPattern = '4'; + + /** + * @var UuidGenerator + */ + private $uuidGenerator; + + /** + * Tests if the *generateVersion4Uuid* method produces any duplicates. + * + * It goes without saying that there should be zero tolerance for duplicates. + * + * The testing algorithm goes as follows: + * + *
    + *
  1. Create an empty list where the new UUIDs will be stored. Let's call + * it *list*.
  2. + *
  3. Repeat 10,000 times: + *
      + *
    1. Create a new UUID. Let's call it *uuid*.
    2. + *
    3. Check if *uuid* is in *list*. + *
        + *
      1. If *yes*, then declare failure.
      2. + *
      3. If *no*, then add *uuid* to *list*.
      4. + *
      + *
    4. + *
    + *
  4. + *
+ */ + function testGenerateVersion4UuidForUniqueness() { + $uuidArr = array(); + for ($i = 1; $i <= 10000; $i++) { + $uuid = $this->uuidGenerator->generateVersion4UUID(); + if ( in_array($uuid, $uuidArr) ) { + $this->fail("Duplicate UUID: {$uuid} (iteration: {$i})"); + } + + $uuidArr[] = $uuid; + } + } + + /** + * Tests the "generateVersion4Uuid" method for valid [version 4 UUIDs][1]. + * + * + * [1]: http://tinyurl.com/3znd239 "Version 4 UUID" + */ + function testGenerateVersion4UuidForValidity() { + $uuid = $this->uuidGenerator->generateVersion4UUID(); + $pattern = "/^" . static::$hexCharPattern . "{8}-" + . static::$hexCharPattern . "{4}-" + . static::$versionPattern . static::$hexCharPattern . "{3}-" + . static::$variantPattern . static::$hexCharPattern . "{3}-" + . static::$hexCharPattern . "{12}$/"; + $valid = (boolean) preg_match($pattern, $uuid); + $this->assertTrue($valid); + } + + /** + * @inheritdoc + */ + protected function setUp() { + $this->uuidGenerator = UuidGenerator::getInstance(); + } +} From 42c0ddbb28382eabc502173c3c8d86643729200d Mon Sep 17 00:00:00 2001 From: Chalkias Tasos Date: Thu, 21 Nov 2013 14:05:18 +0200 Subject: [PATCH 4/4] Replace HTML with Markdown notation in the UuidGenerator class documentation. Signed-off-by: Chalkias Tasos --- lib/PasswordLib/Uuid/UuidGenerator.php | 95 ++++++++++++-------------- 1 file changed, 43 insertions(+), 52 deletions(-) diff --git a/lib/PasswordLib/Uuid/UuidGenerator.php b/lib/PasswordLib/Uuid/UuidGenerator.php index e2c83a9..2d40f1c 100644 --- a/lib/PasswordLib/Uuid/UuidGenerator.php +++ b/lib/PasswordLib/Uuid/UuidGenerator.php @@ -127,8 +127,8 @@ public function generateVersion4Uuid() { $randomString = $this->rng->generate(static::$uuidByteSize); $bytes = $this->getBytesOf($randomString); - $this->setVersionToUUID($bytes, static::UUID_VERSION_4); - $this->setVariantToUUID($bytes); + $this->setVersionToUuid($bytes, static::UUID_VERSION_4); + $this->setVariantToUuid($bytes); $hexString = $this->getHexadecimalOf($bytes); return $this->formatUuid($hexString); @@ -210,60 +210,54 @@ protected function formatUuid($hexString) { * Calculates the byte representation of a string. * * This method uses the PHP [unpack][1] function in order to get the byte - * representation of string. + * representation of `$string`. * - * Every character of string is expected to be one byte long. That - * goes to say that this method does not support multi-byte strings. + * Every character of `$string` is expected to be one byte long. That goes + * to say that this method does not support multi-byte strings. * * Moreover, the format of the array returned is the following: - * - * array( - * index: int => byte representation: int - * ) - * - * - * The indices start from the number one. So, the first byte is given by - * code such as: - * - * $bytes[1] - * + * + * array( + * index: int => byte representation: int + * ) + * + * The indices start from one. So, the first byte is given by code such as: + * + * $bytes[1] * * Furthermore, the representation is an integer in the space [0, 255]. * - *

Example

+ * ### Example ### * * The code below: - * - * $bytes = getBytesOf("Hello, world!"); - * print_r($bytes); - * + * + * $bytes = getBytesOf("Hello, world!"); + * print_r($bytes); * * would result in the following output: - * - * Array ( - * [1] => 72 - * [2] => 101 - * [3] => 108 - * [4] => 108 - * [5] => 111 - * [6] => 32 - * [7] => 119 - * [8] => 111 - * [9] => 114 - * [10] => 108 - * [11] => 100 - * [12] => 33 - * ) - * + * + * Array ( + * [1] => 72 + * [2] => 101 + * [3] => 108 + * [4] => 108 + * [5] => 111 + * [6] => 32 + * [7] => 119 + * [8] => 111 + * [9] => 114 + * [10] => 108 + * [11] => 100 + * [12] => 33 + * ) * * * [1]: http://php.net/manual/en/function.unpack.php "PHP unpack function" * * @param string $string The string whose byte representation this method * calculates. - * @return array The array containing the bytes of the string. - * Pay attention to the fact that the array's first index is one (and not - * zero). + * @return array The array containing the bytes of the `$string`. Pay attention + * to the fact that the array's first index is one (and not zero). * @throws \InvalidArgumentException if `$string` is null. */ protected function getBytesOf($string) { @@ -282,8 +276,7 @@ protected function getBytesOf($string) { * * @param array $bytes The array of bytes whose hexadecimal string representation * this method creates. - * @return string The string representation of bytes in hexadecimal - * notation. + * @return string The string representation of `$bytes` in hexadecimal notation. */ protected function getHexadecimalOf(array $bytes) { $binaryString = call_user_func_array( @@ -302,14 +295,13 @@ protected function getHexadecimalOf(array $bytes) { * * The UUID is expected to be given in big-endian byte representation. * - * The variant is set by manipulating the uuidBytes with bitwise - * operations. + * The variant is set by manipulating the `$uuidBytes` with bitwise operations. * * @param array $uuidBytes The byte representation of the UUID. * @return void * @see http://tinyurl.com/bcd69xo */ - protected function setVariantToUUID(array &$uuidBytes) { + protected function setVariantToUuid(array &$uuidBytes) { $uuidBytes[self::$variantByteIndex] &= 0x3f; $uuidBytes[self::$variantByteIndex] |= 0x80; } @@ -319,20 +311,19 @@ protected function setVariantToUUID(array &$uuidBytes) { * * The UUID is expected to be given in big-endian byte representation. * - * The version is set by manipulating the uuidBytes - * with bitwise operations. + * `$version` is set by manipulating the `$uuidBytes` with bitwise operations. + * + * It is due to the use of bitwise operators that `$version` is expected in + * hexadecimal format. That is, version four should be given like this: * - * It is due to the use of bitwise operators that version is - * expected in hexadecimal format. That is, version four should be given - * like this: - * 0x40 + * 0x40 * * @param array $uuidBytes The byte representation of the UUID. * @param int $version The integer in hexadecimal notation that denotes the * UUID version to be set. * @return void */ - protected function setVersionToUUID(array &$uuidBytes, $version) { + protected function setVersionToUuid(array &$uuidBytes, $version) { $uuidBytes[self::$versionByteIndex] &= 0x0f; $uuidBytes[self::$versionByteIndex] |= $version; }