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; } 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 UUIDException("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)); } protected static function mintTime($node = NULL) { /* Generates a Version 1 UUID. These are derived from the time at which they were generated. */ // 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(1) * 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 .= self::randomBytes(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); } if (!$node) { // If no node was provided or if the node was invalid, // generate a random MAC address and set the multicast bit $node = self::randomBytes(6); $node[0] = pack("C", ord($node[0]) | 1); } $uuid .= $node; return $uuid; } protected static function mintRand() { /* Generate a Version 4 UUID. These are derived soly from random numbers. */ // generate random fields $uuid = self::randomBytes(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; } protected static function mintName($ver, $node, $ns) { /* Generates a Version 3 or Version 5 UUID. These are derived from a hash of a name and its namespace, in binary form. */ if (!$node) { throw new UUIDException("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 UUIDException("A binary namespace is required for Version 3 or 5 UUIDs."); } $uuid = null; $version = self::version3; 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); } protected static function makeBin($str, $len) { /* Insure that an input string is either binary or hexadecimal. Returns binary representation, or false on failure. */ 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); } } public static function initRandom() { /* Look for a system-provided source of randomness, which is usually crytographically secure. /dev/urandom is tried first simply out of bias for Linux systems. */ if (is_readable('/dev/urandom')) { self::$randomSource = fopen('/dev/urandom', 'rb'); self::$randomFunc = 'randomFRead'; } else if (class_exists('COM', 0)) { try { self::$randomSource = new COM('CAPICOM.Utilities.1'); // See http://msdn.microsoft.com/en-us/library/aa388182(VS.85).aspx self::$randomFunc = 'randomCOM'; } catch(Exception $e) { } } return self::$randomFunc; } public static function randomBytes($bytes) { return call_user_func(array('self', self::$randomFunc), $bytes); } protected static function randomTwister($bytes) { /* Get the specified number of random bytes, using mt_rand(). Randomness is returned as a string of bytes. */ $rand = ""; for ($a = 0; $a < $bytes; $a++) { $rand .= chr(mt_rand(0, 255)); } return $rand; } protected static function randomFRead($bytes) { /* Get the specified number of random bytes using a file handle previously opened with UUID::initRandom(). Randomness is returned as a string of bytes. */ return fread(self::$randomSource, $bytes); } protected static function randomCOM($bytes) { /* Get the specified number of random bytes using Windows' randomness source via a COM object previously created by UUID::initRandom(). Randomness is returned as a string of bytes. */ return base64_decode(self::$randomSource->GetRandom($bytes,0)); // straight binary mysteriously doesn't work, hence the base64 } } class UUIDException extends Exception { }