. */ namespace Volkszaehler\Util; /** * DrUUID RFC4122 library for PHP5 * * @author J. King * @package util * @link http://jkingweb.ca/code/php/lib.uuid/ * @license Licensed under MIT license * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ class UUID { const MD5 = 3; const SHA1 = 5; const clearVer = 15; // 00001111 Clears all bits of version byte with AND const clearVar = 63; // 00111111 Clears all relevant bits of variant byte with AND const varRes = 224; // 11100000 Variant reserved for future use const varMS = 192; // 11000000 Microsft GUID variant const varRFC = 128; // 10000000 The RFC 4122 variant (this variant) const varNCS = 0; // 00000000 The NCS compatibility variant const version1 = 16; // 00010000 const version3 = 48; // 00110000 const version4 = 64; // 01000000 const version5 = 80; // 01010000 const interval = 0x01b21dd213814000; // Time (in 100ns steps) between the start of the UTC and Unix epochs const nsDNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; const nsURL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; const nsOID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8'; const nsX500 = '6ba7b814-9dad-11d1-80b4-00c04fd430c8'; //instance properties protected $bytes; protected $hex; protected $string; protected $urn; protected $version; protected $variant; protected $node; protected $time; /** * Create a new UUID based on provided data * * @param integer $ver * @param string $node * @param string $ns */ public static function mint($ver = 1, $node = NULL, $ns = NULL) { switch((int) $ver) { case 1: return new self(self::mintTime($node)); case 2: // Version 2 is not supported throw new \Exception('Version 2 is unsupported.'); case 3: return new self(self::mintName(self::MD5, $node, $ns)); case 4: return new self(self::mintRand()); case 5: return new self(self::mintName(self::SHA1, $node, $ns)); default: throw new \Exception('Selected version is invalid or unsupported.'); } } /** * Import an existing UUID * * @param unknown_type $uuid */ public static function import($uuid) { return new self(self::makeBin($uuid, 16)); } /** * Performant validation of UUID's * * Replaces preg_match('/[a-f0-9\-]{36}/', $uuid); * * @param string $uuid * @param boolen $short whether to allow abbreviated form of UUID's or not */ public static function validate($uuid, $short = FALSE) { $len = strlen($uuid); for ($i = 0; $i < $len; $i++) { $char = $uuid[$i]; $ord = ord($char); if (($ord > 57 || $ord < 48) && ($ord > 70 || $ord < 65) && ($ord > 102 || $ord < 97) && $ord != 45) { return FALSE; // char not allowed } } return ($short) ? $len <= 36 : $len == 36; // check for strlen } /** * Compares the binary representations of two UUIDs. * The comparison will return TRUE if they are bit-exact, * or if neither is valid * * @param unknown_type $a * @param unknown_type $b */ public static function compare($a, $b) { return (self::makeBin($a, 16) == self::makeBin($b, 16)); } public function __toString() { return $this->string; } public function __get($var) { switch($var) { case 'bytes': return $this->bytes; case 'hex': return bin2hex($this->bytes); case 'string': return $this->__toString(); case 'urn': return 'urn:uuid:' . $this->__toString(); case 'version': return ord($this->bytes[6]) >> 4; case 'variant': $byte = ord($this->bytes[8]); if ($byte >= self::varRes) return 3; if ($byte >= self::varMS) return 2; if ($byte >= self::varRFC) return 1; else return 0; case 'node': if (ord($this->bytes[6])>>4==1) { return bin2hex(substr($this->bytes,10)); } else { return NULL; } case 'time': if (ord($this->bytes[6])>>4==1) { // Restore contiguous big-endian byte order $time = bin2hex($this->bytes[6].$this->bytes[7].$this->bytes[4].$this->bytes[5].$this->bytes[0].$this->bytes[1].$this->bytes[2].$this->bytes[3]); // Clear version flag $time[0] = '0'; // Do some reverse arithmetic to get a Unix timestamp $time = (hexdec($time) - self::interval) / 10000000; return $time; } else { return NULL; } default: return NULL; } } protected function __construct($uuid) { if (strlen($uuid) != 16) { throw new \Exception('Input must be a 128-bit integer.'); } $this->bytes = $uuid; // Optimize the most common use $this->string = bin2hex(substr($uuid,0,4)).'-'. bin2hex(substr($uuid,4,2)).'-'. bin2hex(substr($uuid,6,2)).'-'. bin2hex(substr($uuid,8,2)).'-'. bin2hex(substr($uuid,10,6)); } /** * Generates a Version 1 UUID. * These are derived from the time at which they were generated. * * @param $node */ protected static function mintTime($node = NULL) { // Get time since Gregorian calendar reform in 100ns intervals // This is exceedingly difficult because of PHP's (and pack()'s) // integer size limits. // Note that this will never be more accurate than to the microsecond. $time = microtime(TRUE) * 10000000 + self::interval; // Convert to a string representation $time = sprintf('%F', $time); preg_match('/^\d+/', $time, $time); //strip decimal point // And now to a 64-bit binary representation $time = base_convert($time[0], 10, 16); $time = pack('H*', str_pad($time, 16, '0', STR_PAD_LEFT)); // Reorder bytes to their proper locations in the UUID $uuid = $time[4].$time[5].$time[6].$time[7].$time[2].$time[3].$time[0].$time[1]; // Generate a random clock sequence $uuid .= Random::getBytes(2); // set variant $uuid[8] = chr(ord($uuid[8]) & self::clearVar | self::varRFC); // set version $uuid[6] = chr(ord($uuid[6]) & self::clearVer | self::version1); // Set the final 'node' parameter, a MAC address if ($node) { $node = self::makeBin($node, 6); } else { // If no node was provided or if the node was invalid, // generate a random MAC address and set the multicast bit $node = Random::getBytes(6); $node[0] = pack('C', ord($node[0]) | 1); } $uuid .= $node; return $uuid; } /** * Generate a Version 4 UUID. * These are derived soly from random numbers. */ protected static function mintRand() { // generate random fields $uuid = Random::getBytes(16); // set variant $uuid[8] = chr(ord($uuid[8]) & self::clearVar | self::varRFC); // set version $uuid[6] = chr(ord($uuid[6]) & self::clearVer | self::version4); return $uuid; } /** * Generates a Version 3 or Version 5 UUID. * These are derived from a hash of a name and its namespace, in binary form. * * @param integer $ver the version (MD5 or SHA1) * @param string $node the name string * $param string $ns the namespace */ protected static function mintName($ver, $node, $ns) { if (!$node) { throw new \Exception('A name-string is required for Version 3 or 5 UUIDs.'); } // if the namespace UUID isn't binary, make it so $ns = self::makeBin($ns, 16); if (!$ns) { throw new \Exception('A binary namespace is required for Version 3 or 5 UUIDs.'); } switch($ver) { case self::MD5: $version = self::version3; $uuid = md5($ns.$node,1); break; case self::SHA1: $version = self::version5; $uuid = substr(sha1($ns.$node,1),0, 16); break; } // set variant $uuid[8] = chr(ord($uuid[8]) & self::clearVar | self::varRFC); // set version $uuid[6] = chr(ord($uuid[6]) & self::clearVer | $version); return ($uuid); } /** * Insure that an input string is either binary or hexadecimal. * Returns binary representation, or FALSE on failure. * * @param unkown_type $str * @param integer $len */ protected static function makeBin($str, $len) { if ($str instanceof self) { return $str->bytes; } if (strlen($str) == $len) { return $str; } else { $str = preg_replace('/^urn:uuid:/is', '', $str); // strip URN scheme and namespace } $str = preg_replace('/[^a-f0-9]/is', '', $str); // strip non-hex characters if (strlen($str) != ($len * 2)) { return FALSE; } else { return pack('H*', $str); } } } ?>