-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuuid.php
More file actions
481 lines (451 loc) · 14.3 KB
/
Copy pathuuid.php
File metadata and controls
481 lines (451 loc) · 14.3 KB
1
2
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
<?php
/* Copyright 2010-2012 Mo McRoberts.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* UUID generation and manipulation facilities
*
* The \class{UUID} class contains facilities for generating and manipulating
* Universally Unique Identifiers (UUIDs), according to
* \link{http://www.ietf.org/rfc/rfc4122.txt|RFC 4122} (equivalent to
* ITU-T Rec. X.667, ISO/IEC 9834-8:2005).
*/
class UUID implements ArrayAccess
{
const UNKNOWN = -1; /**< Unknown UUID version or variant */
const NONE = 0; /**< Version 0 (NULL UUID) */
const DCE_TIME = 1; /**< Version 1 (MAC address) */
const DCE_SECURITY = 2; /**< Version 2 (DCE Security) */
const HASH_MD5 = 3; /**< Version 3 (MD5 hash) */
const RANDOM = 4; /**< Version 4 (Random) */
const HASH_SHA1 = 5; /**< Version 5 (SHA1 hash) */
const NCS = 0; /**< Apollo NCS variant UUID */
const DCE = 1; /**< OSF DCE variant UUID */
const MICROSOFT = 2; /**< Microsoft variant GUID */
const RESERVED = 3; /**< Reserved for future use */
const DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; /**< Namespace UUID for DNS identifiers */
const URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; /**< Namespace UUID for URL identifiers */
const OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8'; /**< Namespace UUID for ISO OID identifiers */
const DN = '6ba7b814-9dad-11d1-80b4-00c04fd430c8'; /**< Namespace UUID for X.500 DNs */
const UUID_NULL = '00000000-0000-0000-0000-000000000000';
protected $info;
/**
* @brief Generate a new UUID
* @task Generating UUIDs
*
* \m{UUID::generate} generates a new UUID according to \link{http://www.ietf.org/rfc/rfc4122.txt|RFC 4122} (equivalent to
* ITU-T Rec. X.667, ISO/IEC 9834-8:2005).
*
* If the kind of UUID specified by \p{$kind} cannot be generated
* because it is not supported, a random (v4) UUID will be generated instead (in other
* words, the \p{$kind} parameter is a hint).
*
* If the kind of UUID specified by \p{$kind} cannot be generated
* because one or both of \p{$namespace} and \p{$name}
* are not valid, an error occurs and \c{null} is returned.
*
* @type UUID
* @param[in,optional] int $kind The kind of UUID to generate.
* @param[in,optional] string $namespace For MD5 (v3) and SHA1 (v5) UUIDs, the namespace which contains \p{$name}.
* @param[in,optional] string $name For MD5 (v3) and SHA1 (v5) UUIDs, the identifier used to generate the UUID.
* @return A new UUID, or \c{null} if an error occurs.
*/
public static function generate($kind = self::RANDOM, $namespace = null, $name = null)
{
switch($kind)
{
case self::UNKNOWN:
return null;
case self::NONE:
return self::nil();
case self::HASH_MD5:
case self::HASH_SHA1:
if($namespace !== null && $name !== null)
{
return self::hash($namespace, $name, $kind);
}
default:
return self::random();
}
}
protected static function random()
{
/* Generate a random (version 4) UUID if all else fails */
return new UUID(sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)));
}
protected static function hash($namespace, $name, $version)
{
$namespace = self::canonical($namespace);
$nsdata = pack('H*', $namespace);
if($version == self::HASH_MD5)
{
$hash = md5($nsdata . $name, true);
}
else
{
$hash = sha1($nsdata . $name, true);
}
$result = unpack('Ntime_low/ntime_mid/ntime_hi_and_version/Cclock_seq_hi_and_reserved/Cclock_seq_low/C*', $hash);
$result['time_hi_and_version'] &= 0x0FFF;
$result['time_hi_and_version'] |= ($version << 12);
$result['clock_seq_hi_and_reserved'] &= 0x3F;
$result['clock_seq_hi_and_reserved'] |= 0x80;
$out = sprintf('%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x',
$result['time_low'], $result['time_mid'],
$result['time_hi_and_version'],
$result['clock_seq_hi_and_reserved'], $result['clock_seq_low'],
$result[1], $result[2], $result[3], $result[4],
$result[5], $result[6]);
return new UUID($out);
}
/**
* @brief Return the null UUID as a string
* @task Generating UUIDs
*
* \m{UUID::nil} returns a string containing the null UUID.
*
* It is the equivalent of calling <code>\m{UUID::generate}(\c{UUID::NONE});</code>
*
* @return string The null UUID. i.e., \x{00000000-0000-0000-0000-000000000000}.
*/
public static function nil()
{
return new UUID(self::UUID_NULL);
}
/**
* @brief Determine whether a string is a valid UUID or not
* @task Manipulating UUIDs
*
* \m{UUID::isUUID} tests whether a string consists of a valid UUID.
*
* @type string
* @param[in] string $str The string that is potentially a UUID.
* @return If \p{$str} is a UUID, then the return value is \p{$str},
* otherwise \c{null} is returned.
*/
public static function isUUID($str)
{
if($str instanceof UUID)
{
return $str;
}
if(preg_match('/^(urn:uuid:)?\{?[a-f0-9]{8}-?[a-f0-9]{4}-?[a-f0-9]{4}-?[a-f0-9]{4}-?[a-f0-9]{12}\}?$/i', $str))
{
return $str;
}
return null;
}
/**
* @brief Return the canonical form of a UUID string (i.e., no braces, no dashes, all lower-case)
* @task Manipulating UUIDs
*
* \m{UUID::canonical} accepts a string representation of a UUID (for example, as returned by
* \m{UUID::generate}) and returns the canonical form of the UUID: that is, all-lowercase, and with
* any braces and dashes removed.
*
* For example, the canonical form of the UUID string \x{|{EAE58635-B826-42A9-9B03-3A3AC8A2CC29}|}
* would be \x{'eae58635b82642a99b033a3ac8a2cc29'}.
*
* @type string
* @param[in] string $uuid A string representation of a UUID.
* @return The canonical form of the UUID, or \c{null} if \p{$uuid} is not a valid UUID string.
*/
public static function canonical($uuid)
{
if($uuid instanceof UUID)
{
return $uuid->info['canonical'];
}
$uuid = strtolower(trim(str_replace(array('-','{','}'), '', $uuid)));
if(!strncmp($uuid, 'urn:uuid:', 9)) $uuid = substr($uuid, 9);
if(strlen($uuid) != 32) return null;
if(!ctype_xdigit($uuid)) return null;
return $uuid;
}
/**
* @brief Formats a UUID as an IRI
* @task Manipulating UUIDs
*
* \m{UUID::iri} converts a string representation of a UUID to an IRI
* (Internationalized Resource Identifier), specifically a UUID URN.
*
* For example, the null UUID converted to an IRI would be \x{urn:uuid:00000000-0000-0000-0000-000000000000}.
*
* @type string
* @param[in] string $uuid A string representation of a UUID
* @return The IRI representation of \p{$uuid}, or \c{null} if \p{$uuid} is not a valid UUID string.
*/
public static function iri($uuid)
{
if($uuid instanceof UUID)
{
return $uuid->info['iri'];
}
if(!($uuid = self::canonical($uuid)))
{
return null;
}
return 'urn:uuid:' . substr($uuid, 0, 8) . '-' . substr($uuid, 8, 4) . '-' . substr($uuid, 12, 4) . '-' . substr($uuid, 16, 4) . '-' . substr($uuid, 20, 12);
}
/**
* @brief Format a UUID in the traditional aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee form
* @task Manipulating UUIDs
*
* \m{UUID::iri} converts a string representation of a UUID in the
* traditional form.
*
* For example, the null UUID converted to an IRI would be \x{00000000-0000-0000-0000-000000000000}.
*
* @type string
* @param[in] string $uuid A string representation of a UUID
* @param[in,optional] $prefix An optional string to prepend to the formatted UUID.
* @param[in,optional] $suffix An optional string to append to the formatted UUID.
* @return The IRI representation of \p{$uuid}, or \c{null} if \p{$uuid} is not a valid UUID string.
*/
public static function formatted($uuid, $prefix = null, $suffix = null)
{
if($uuid instanceof UUID)
{
return $uuid->format($prefix, $suffix);
}
if(!($uuid = self::canonical($uuid)))
{
return null;
}
return $prefix . substr($uuid, 0, 8) . '-' . substr($uuid, 8, 4) . '-' . substr($uuid, 12, 4) . '-' . substr($uuid, 16, 4) . '-' . substr($uuid, 20, 12) . $suffix;
}
/**
* @brief Parse a string containing a UUID and return an array representing its value.
* @task Manipulating UUIDs
*
* \m{UUID::parse} converts a string representation of a UUID to an array. The
* array contains the following members:
*
* - \x{time_low}
* - \x{time_mid}
* - \x{time_hi_and_version}
* - \x{clock_seq_hi_and_reserved}
* - \x{clock_seq_low}
* - \x{node}
* - \x{version}
* - \x{variant}
*
* The \x{version} member contains a UUID version number, for example \c{UUID::RANDOM}.
* The \x{variant} member specifies the UUID variant, for example \c{UUID::DCE}.
*
* @type array
* @param[in] string $uuid A string representation of a UUID.
* @param[in] boolean $returnArray if \c{true}, return an array, otherwise return a \class{UUID} instance.
* @return A \class{UUID} instance representing the supplied UUID, or \c{null} if an error occurs.
*/
public static function parse($uuid, $returnArray = false)
{
if($uuid instanceof UUID)
{
if($returnArray)
{
return $uuid->info;
}
return $uuid;
}
$info = array();
if(!self::updateInfo($info, trim($uuid)))
{
return null;
}
if($returnArray)
{
return $info;
}
return new UUID($info);
}
/**
* @brief Constructs a UUID string given an array as returned by UUID::parse()
* @task Manipulating UUIDs
*
* \m{UUID::unparse} accepts an array representation of a UUID as returned by
* \m{UUID::parse} and returns a string representation of the same UUID.
*
* @type string
* @param[in] array $info An array representation of a UUID
* @return A string representing the supplied UUID
*/
public static function unparse($info)
{
if($info instanceof UUID)
{
return $info->info['formatted'];
}
return sprintf('%08x-%04x-%04x-%02x%02x-%12s', $info['time_low'] & 0xFFFFFFFF, $info['time_mid'], $info['time_hi_and_version'], $info['clock_seq_hi_and_reserved'], $info['clock_seq_low'], strtolower($info['node']));
}
/* Update the version, variant, formatted, iri and canonical members
* of a UUID info block.
*/
protected static function updateInfo(&$info, $newUuid = null)
{
if($newUuid !== null)
{
$info = array(
'time_low' => '',
'time_mid' => '',
'time_hi_and_version' => '',
'clock_seq_hi_and_reserved' => '',
'clock_seq_low' => '',
'node' => '',
'version' => self::UNKNOWN,
'variant' => self::UNKNOWN,
'formatted' => null,
'iri' => null,
'canonical' => null,
);
$newUuid = self::canonical($newUuid);
if($newUuid === null)
{
return false;
}
sscanf($newUuid, '%8x%4x%4x%2x%2x%12s', $info['time_low'], $info['time_mid'], $info['time_hi_and_version'], $info['clock_seq_hi_and_reserved'], $info['clock_seq_low'], $info['node']);
}
$info['version'] = ($info['time_hi_and_version'] & 0xF000) >> 12;
if(($info['clock_seq_hi_and_reserved'] & 0xC0) == 0x80)
{
$info['variant'] = self::DCE;
}
else if(($info['clock_seq_hi_and_reserved'] & 0xE0) == 0xC0)
{
$info['variant'] = self::MICROSOFT;
}
else if(($info['clock_seq_hi_and_reserved'] & 0xE0) == 0xE0)
{
$info['variant'] = self::RESERVED;
}
$info['formatted'] = sprintf('%08x-%04x-%04x-%02x%02x-%12s',
$info['time_low'] & 0xFFFFFFFF,
$info['time_mid'],
$info['time_hi_and_version'],
$info['clock_seq_hi_and_reserved'],
$info['clock_seq_low'],
strtolower($info['node']));
$info['iri'] = 'urn:uuid:' . $info['formatted'];
$info['canonical'] = str_replace('-', '', $info['formatted']);
return true;
}
public function __construct($uuid = null)
{
if(!is_array($uuid))
{
$this->info = array();
if(!strlen($uuid))
{
$uuid = self::UUID_NULL;
}
if(!$this->updateInfo($this->info, $uuid))
{
throw new Exception('Failed to parse UUID string "' . $uuid . '"', E_USER_ERROR);
}
}
else
{
$this->info = $uuid;
$this->updateInfo($this->info);
}
}
public function __toString()
{
return $this->info['formatted'];
}
public function __get($name)
{
return $this->info[$name];
}
public function __set($name, $value)
{
if($name === 'formatted' || $name === 'canonical' || $name === 'iri')
{
$info = array();
if(!$this->updateInfo($info, $value))
{
throw new Exception('Failed to parse UUID string "' . $value . '"', E_USER_ERROR);
}
$this->info = $info;
return;
}
if(array_key_exists($name, $this->info))
{
$this->info[$name] = $value;
$this->updateInfo($this->info);
return;
}
$this->{$name} = $value;
}
public function __isset($name)
{
return isset($this->info[$name]) || isset($this->{$name});
}
public function __unset($name)
{
if(array_key_exists($name, $this->info) || $name === 'info')
{
trigger_error('Attempt to unset read-only property UUID::$' . $name, E_USER_NOTICE);
return;
}
unset($this->{$name});
}
/* Format a UUID instance */
public function format($prefix = null, $suffix = null)
{
return $prefix . $this->info['formatted'] . $suffix;
}
public function offsetGet($name)
{
return $this->info[$name];
}
public function offsetSet($name, $value)
{
if($name === 'formatted' || $name === 'canonical' || $name === 'iri')
{
$info = array();
if(!$this->updateInfo($info, $value))
{
throw new Exception('Failed to parse UUID string "' . $value . '"', E_USER_ERROR);
}
$this->info = $info;
return;
}
if(array_key_exists($name, $this->info))
{
$this->info[$name] = $value;
$this->updateInfo($this->info);
return;
}
$this->{$name} = $value;
}
public function offsetExists($name)
{
return isset($this->info[$name]) || isset($this->{$name});
}
public function offsetUnset($name)
{
if(array_key_exists($name, $this->info) || $name === 'info')
{
trigger_error('Attempt to unset read-only property UUID::$' . $name, E_USER_NOTICE);
return;
}
unset($this->{$name});
}
}