vzlogger/backend/lib/Util/UUID.php
2010-09-19 20:48:19 +02:00

351 lines
9.9 KiB
PHP

<?php
/**
* @copyright Copyright (c) 2010, The volkszaehler.org project
* @package util
* @license http://www.opensource.org/licenses/gpl-license.php GNU Public License
*/
/*
* This file is part of volkzaehler.org
*
* volkzaehler.org is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* volkzaehler.org is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with volkszaehler.org. If not, see <http://www.gnu.org/licenses/>.
*/
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);
}
}
}
?>