1: <?php
2: namespace phpcassa;
3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
13:
14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37:
38:
39: use phpcassa\UUID\UUIDException;
40: use phpcassa\Util\Clock;
41:
42: 43: 44:
45: class UUID {
46: const MD5 = 3;
47: const SHA1 = 5;
48: const clearVer = 15;
49: const clearVar = 63;
50: const varRes = 224;
51: const varMS = 192;
52: const varRFC = 128;
53: const varNCS = 0;
54: const version1 = 16;
55: const version3 = 48;
56: const version4 = 64;
57: const version5 = 80;
58: const interval = 0x01b21dd213814000;
59: const nsDNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
60: const nsURL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
61: const nsOID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8';
62: const nsX500 = '6ba7b814-9dad-11d1-80b4-00c04fd430c8';
63: protected static $randomFunc = 'randomTwister';
64: protected static $randomSource = NULL;
65:
66:
67: protected $bytes;
68: protected $hex;
69: protected $string;
70: protected $urn;
71: protected $version;
72: protected $variant;
73: protected $node;
74: protected $time;
75:
76: 77: 78: 79: 80: 81: 82: 83:
84: public static function uuid1($node=null, $time=null) {
85: return UUID::mint(1, $node, null, $time);
86: }
87:
88: 89: 90: 91:
92: static public function uuid3($node=null, $namespace=null) {
93: return UUID::mint(3, $node, $namespace);
94: }
95:
96: 97: 98: 99:
100: static public function uuid4() {
101: return UUID::mint(4);
102: }
103:
104: 105: 106: 107:
108: static public function uuid5($node, $namespace=null) {
109: return UUID::mint(5, $node, $namespace);
110: }
111:
112: 113: 114:
115: public static function import($uuid) {
116: return new self(self::makeBin($uuid, 16));
117: }
118:
119: 120: 121: 122:
123: public static function mint($ver = 1, $node = NULL, $ns = NULL, $time = NULL) {
124: switch((int) $ver) {
125: case 1:
126: return new self(self::mintTime($node, $time));
127: case 2:
128:
129: throw new UUIDException("Version 2 is unsupported.");
130: case 3:
131: return new self(self::mintName(self::MD5, $node, $ns));
132: case 4:
133: return new self(self::mintRand());
134: case 5:
135: return new self(self::mintName(self::SHA1, $node, $ns));
136: default:
137: throw new UUIDException("Selected version is invalid or unsupported.");
138: }
139: }
140:
141: 142: 143: 144: 145:
146: public static function compare($a, $b) {
147: if (self::makeBin($a, 16)==self::makeBin($b, 16))
148: return TRUE;
149: else
150: return FALSE;
151: }
152:
153: public function __toString() {
154: return $this->string;
155: }
156:
157: public function __get($var) {
158: switch($var) {
159: case "bytes":
160: return $this->bytes;
161: case "hex":
162: return bin2hex($this->bytes);
163: case "string":
164: return $this->__toString();
165: case "urn":
166: return "urn:uuid:".$this->__toString();
167: case "version":
168: return ord($this->bytes[6]) >> 4;
169: case "variant":
170: $byte = ord($this->bytes[8]);
171: if ($byte >= self::varRes)
172: return 3;
173: if ($byte >= self::varMS)
174: return 2;
175: if ($byte >= self::varRFC)
176: return 1;
177: else
178: return 0;
179: case "node":
180: if (ord($this->bytes[6])>>4==1)
181: return bin2hex(substr($this->bytes,10));
182: else
183: return NULL;
184: case "time":
185: if (ord($this->bytes[6])>>4==1) {
186:
187: $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]);
188:
189: $time[0] = "0";
190:
191: $time = (hexdec($time) - self::interval) / 10000000;
192: return $time;
193: }
194: else
195: return NULL;
196: default:
197: return NULL;
198: }
199: }
200:
201: protected function __construct($uuid) {
202: if (strlen($uuid) != 16)
203: throw new UUIDException("Input must be a 128-bit integer.");
204: $this->bytes = $uuid;
205:
206: $this->string =
207: bin2hex(substr($uuid,0,4))."-".
208: bin2hex(substr($uuid,4,2))."-".
209: bin2hex(substr($uuid,6,2))."-".
210: bin2hex(substr($uuid,8,2))."-".
211: bin2hex(substr($uuid,10,6));
212: }
213:
214: protected static function get_time() {
215: $time1 = microtime();
216: settype($time1, 'string');
217: $time2 = explode(" ", $time1);
218: $sub_secs = preg_replace('/0./', '', $time2[0], 1);
219: $time3 = ($time2[1].$sub_secs)/100;
220: return $time3;
221: }
222:
223:
224: protected static function mintTime($node = NULL, $time_arg = NULL) {
225: 226:
227:
228:
229:
230:
231: if ($time_arg == NULL) {
232: $time = Clock::get_time() * 10 + self::interval;
233: } else {
234: $time = $time_arg * 10 + self::interval;
235: }
236:
237: $time = sprintf("%F", $time);
238: preg_match("/^\d+/", $time, $time);
239:
240: $time = base_convert($time[0], 10, 16);
241: $time = pack("H*", str_pad($time, 16, "0", STR_PAD_LEFT));
242:
243: $uuid = $time[4].$time[5].$time[6].$time[7].$time[2].$time[3].$time[0].$time[1];
244:
245: $uuid .= self::randomBytes(2);
246:
247: $uuid[8] = chr(ord($uuid[8]) & self::clearVar | self::varRFC);
248:
249: $uuid[6] = chr(ord($uuid[6]) & self::clearVer | self::version1);
250:
251: if ($node)
252: $node = self::makeBin($node, 6);
253: if (!$node) {
254:
255:
256: $node = self::randomBytes(6);
257: $node[0] = pack("C", ord($node[0]) | 1);
258: }
259: $uuid .= $node;
260: return $uuid;
261: }
262:
263: protected static function mintRand() {
264: 265:
266:
267: $uuid = self::randomBytes(16);
268:
269: $uuid[8] = chr(ord($uuid[8]) & self::clearVar | self::varRFC);
270:
271: $uuid[6] = chr(ord($uuid[6]) & self::clearVer | self::version4);
272: return $uuid;
273: }
274:
275: protected static function mintName($ver, $node, $ns) {
276: 277:
278: if (!$node)
279: throw new UUIDException("A name-string is required for Version 3 or 5 UUIDs.");
280:
281: $ns = self::makeBin($ns, 16);
282: if (!$ns)
283: throw new UUIDException("A binary namespace is required for Version 3 or 5 UUIDs.");
284: switch($ver) {
285: case self::MD5:
286: $version = self::version3;
287: $uuid = md5($ns.$node,1);
288: break;
289: case self::SHA1:
290: $version = self::version5;
291: $uuid = substr(sha1($ns.$node,1),0, 16);
292: break;
293: }
294:
295: $uuid[8] = chr(ord($uuid[8]) & self::clearVar | self::varRFC);
296:
297: $uuid[6] = chr(ord($uuid[6]) & self::clearVer | $version);
298: return ($uuid);
299: }
300:
301: protected static function makeBin($str, $len) {
302: 303:
304: if ($str instanceof self)
305: return $str->bytes;
306: if (strlen($str)==$len)
307: return $str;
308: else
309: $str = preg_replace("/^urn:uuid:/is", "", $str);
310: $str = preg_replace("/[^a-f0-9]/is", "", $str);
311: if (strlen($str) != ($len * 2))
312: return FALSE;
313: else
314: return pack("H*", $str);
315: }
316:
317: public static function initRandom() {
318: 319:
320: if (is_readable('/dev/urandom')) {
321: self::$randomSource = fopen('/dev/urandom', 'rb');
322: self::$randomFunc = 'randomFRead';
323: }
324: else if (class_exists('COM', 0)) {
325: try {
326: self::$randomSource = new COM('CAPICOM.Utilities.1');
327: self::$randomFunc = 'randomCOM';
328: }
329: catch(Exception $e) {}
330: }
331: return self::$randomFunc;
332: }
333:
334: public static function randomBytes($bytes) {
335: return call_user_func(array('self', self::$randomFunc), $bytes);
336: }
337:
338: protected static function randomTwister($bytes) {
339: 340:
341: $rand = "";
342: for ($a = 0; $a < $bytes; $a++) {
343: $rand .= chr(mt_rand(0, 255));
344: }
345: return $rand;
346: }
347:
348: protected static function randomFRead($bytes) {
349: 350: 351:
352: return fread(self::$randomSource, $bytes);
353: }
354:
355: protected static function randomCOM($bytes) {
356: 357: 358:
359: return base64_decode(self::$randomSource->GetRandom($bytes,0));
360: }
361: }
362: