]>
Commit | Line | Data |
---|---|---|
87090d8a | 1 | <?php |
2 | /* | |
3 | DrUUID RFC4122 library for PHP5 | |
4 | by J. King (http://jkingweb.ca/) | |
5 | Licensed under MIT license | |
6 | ||
7 | See http://jkingweb.ca/code/php/lib.uuid/ | |
8 | for documentation | |
9 | ||
10 | Last revised 2010-02-15 | |
11 | */ | |
12 | ||
13 | /* | |
14 | Copyright (c) 2009 J. King | |
15 | ||
16 | Permission is hereby granted, free of charge, to any person | |
17 | obtaining a copy of this software and associated documentation | |
18 | files (the "Software"), to deal in the Software without | |
19 | restriction, including without limitation the rights to use, | |
20 | copy, modify, merge, publish, distribute, sublicense, and/or sell | |
21 | copies of the Software, and to permit persons to whom the | |
22 | Software is furnished to do so, subject to the following | |
23 | conditions: | |
24 | ||
25 | The above copyright notice and this permission notice shall be | |
26 | included in all copies or substantial portions of the Software. | |
27 | ||
28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
29 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | |
30 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
31 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |
32 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
33 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
34 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
35 | OTHER DEALINGS IN THE SOFTWARE. | |
36 | */ | |
37 | ||
38 | ||
39 | class UUID { | |
40 | const MD5 = 3; | |
41 | const SHA1 = 5; | |
42 | const clearVer = 15; // 00001111 Clears all bits of version byte with AND | |
43 | const clearVar = 63; // 00111111 Clears all relevant bits of variant byte with AND | |
44 | const varRes = 224; // 11100000 Variant reserved for future use | |
45 | const varMS = 192; // 11000000 Microsft GUID variant | |
46 | const varRFC = 128; // 10000000 The RFC 4122 variant (this variant) | |
47 | const varNCS = 0; // 00000000 The NCS compatibility variant | |
48 | const version1 = 16; // 00010000 | |
49 | const version3 = 48; // 00110000 | |
50 | const version4 = 64; // 01000000 | |
51 | const version5 = 80; // 01010000 | |
52 | const interval = 0x01b21dd213814000; // Time (in 100ns steps) between the start of the UTC and Unix epochs | |
53 | const nsDNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; | |
54 | const nsURL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; | |
55 | const nsOID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8'; | |
56 | const nsX500 = '6ba7b814-9dad-11d1-80b4-00c04fd430c8'; | |
57 | protected static $randomFunc = 'randomTwister'; | |
58 | protected static $randomSource = NULL; | |
59 | //instance properties | |
60 | protected $bytes; | |
61 | protected $hex; | |
62 | protected $string; | |
63 | protected $urn; | |
64 | protected $version; | |
65 | protected $variant; | |
66 | protected $node; | |
67 | protected $time; | |
68 | ||
69 | public static function mint($ver = 1, $node = NULL, $ns = NULL) { | |
70 | /* Create a new UUID based on provided data. */ | |
71 | switch((int) $ver) { | |
72 | case 1: | |
73 | return new self(self::mintTime($node)); | |
74 | case 2: | |
75 | // Version 2 is not supported | |
76 | throw new UUIDException("Version 2 is unsupported."); | |
77 | case 3: | |
78 | return new self(self::mintName(self::MD5, $node, $ns)); | |
79 | case 4: | |
80 | return new self(self::mintRand()); | |
81 | case 5: | |
82 | return new self(self::mintName(self::SHA1, $node, $ns)); | |
83 | default: | |
84 | throw new UUIDException("Selected version is invalid or unsupported."); | |
85 | } | |
86 | } | |
87 | ||
88 | public static function import($uuid) { | |
89 | /* Import an existing UUID. */ | |
90 | return new self(self::makeBin($uuid, 16)); | |
91 | } | |
92 | ||
93 | public static function compare($a, $b) { | |
94 | /* Compares the binary representations of two UUIDs. | |
95 | The comparison will return true if they are bit-exact, | |
96 | or if neither is valid. */ | |
97 | if (self::makeBin($a, 16)==self::makeBin($b, 16)) { | |
98 | return TRUE; | |
99 | } else { | |
100 | return FALSE; | |
101 | } | |
102 | } | |
103 | ||
104 | public function __toString() { | |
105 | return $this->string; | |
106 | } | |
107 | ||
108 | public function __get($var) { | |
109 | switch($var) { | |
110 | case "bytes": | |
111 | return $this->bytes; | |
112 | case "hex": | |
113 | return bin2hex($this->bytes); | |
114 | case "string": | |
115 | return $this->__toString(); | |
116 | case "urn": | |
117 | return "urn:uuid:".$this->__toString(); | |
118 | case "version": | |
119 | return ord($this->bytes[6]) >> 4; | |
120 | case "variant": | |
121 | $byte = ord($this->bytes[8]); | |
122 | if ($byte >= self::varRes) { | |
123 | return 3; | |
124 | } | |
125 | if ($byte >= self::varMS) { | |
126 | return 2; | |
127 | } | |
128 | if ($byte >= self::varRFC) { | |
129 | return 1; | |
130 | } | |
131 | return 0; | |
132 | case "node": | |
133 | if (ord($this->bytes[6])>>4==1) { | |
134 | return bin2hex(substr($this->bytes,10)); | |
135 | } else { | |
136 | return NULL; | |
137 | } | |
138 | case "time": | |
139 | if (ord($this->bytes[6])>>4==1) { | |
140 | // Restore contiguous big-endian byte order | |
141 | $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]); | |
142 | // Clear version flag | |
143 | $time[0] = "0"; | |
144 | // Do some reverse arithmetic to get a Unix timestamp | |
145 | $time = (hexdec($time) - self::interval) / 10000000; | |
146 | return $time; | |
147 | } else { | |
148 | return NULL; | |
149 | } | |
150 | default: | |
151 | return NULL; | |
152 | } | |
153 | } | |
154 | ||
155 | protected function __construct($uuid) { | |
156 | if (strlen($uuid) != 16) { | |
157 | throw new UUIDException("Input must be a 128-bit integer."); | |
158 | } | |
159 | $this->bytes = $uuid; | |
160 | // Optimize the most common use | |
161 | $this->string = | |
162 | bin2hex(substr($uuid,0,4))."-". | |
163 | bin2hex(substr($uuid,4,2))."-". | |
164 | bin2hex(substr($uuid,6,2))."-". | |
165 | bin2hex(substr($uuid,8,2))."-". | |
166 | bin2hex(substr($uuid,10,6)); | |
167 | } | |
168 | ||
169 | protected static function mintTime($node = NULL) { | |
170 | /* Generates a Version 1 UUID. | |
171 | These are derived from the time at which they were generated. */ | |
172 | // Get time since Gregorian calendar reform in 100ns intervals | |
173 | // This is exceedingly difficult because of PHP's (and pack()'s) | |
174 | // integer size limits. | |
175 | // Note that this will never be more accurate than to the microsecond. | |
176 | $time = microtime(1) * 10000000 + self::interval; | |
177 | // Convert to a string representation | |
178 | $time = sprintf("%F", $time); | |
179 | preg_match("/^\d+/", $time, $time); //strip decimal point | |
180 | // And now to a 64-bit binary representation | |
181 | $time = base_convert($time[0], 10, 16); | |
182 | $time = pack("H*", str_pad($time, 16, "0", STR_PAD_LEFT)); | |
183 | // Reorder bytes to their proper locations in the UUID | |
184 | $uuid = $time[4].$time[5].$time[6].$time[7].$time[2].$time[3].$time[0].$time[1]; | |
185 | // Generate a random clock sequence | |
186 | $uuid .= self::randomBytes(2); | |
187 | // set variant | |
188 | $uuid[8] = chr(ord($uuid[8]) & self::clearVar | self::varRFC); | |
189 | // set version | |
190 | $uuid[6] = chr(ord($uuid[6]) & self::clearVer | self::version1); | |
191 | // Set the final 'node' parameter, a MAC address | |
192 | if ($node) { | |
193 | $node = self::makeBin($node, 6); | |
194 | } | |
195 | if (!$node) { | |
196 | // If no node was provided or if the node was invalid, | |
197 | // generate a random MAC address and set the multicast bit | |
198 | $node = self::randomBytes(6); | |
199 | $node[0] = pack("C", ord($node[0]) | 1); | |
200 | } | |
201 | $uuid .= $node; | |
202 | return $uuid; | |
203 | } | |
204 | ||
205 | protected static function mintRand() { | |
206 | /* Generate a Version 4 UUID. | |
207 | These are derived soly from random numbers. */ | |
208 | // generate random fields | |
209 | $uuid = self::randomBytes(16); | |
210 | // set variant | |
211 | $uuid[8] = chr(ord($uuid[8]) & self::clearVar | self::varRFC); | |
212 | // set version | |
213 | $uuid[6] = chr(ord($uuid[6]) & self::clearVer | self::version4); | |
214 | return $uuid; | |
215 | } | |
216 | ||
217 | protected static function mintName($ver, $node, $ns) { | |
218 | /* Generates a Version 3 or Version 5 UUID. | |
219 | These are derived from a hash of a name and its namespace, in binary form. */ | |
220 | if (!$node) { | |
221 | throw new UUIDException("A name-string is required for Version 3 or 5 UUIDs."); | |
222 | } | |
223 | // if the namespace UUID isn't binary, make it so | |
224 | $ns = self::makeBin($ns, 16); | |
225 | if (!$ns) { | |
226 | throw new UUIDException("A binary namespace is required for Version 3 or 5 UUIDs."); | |
227 | } | |
228 | $uuid = null; | |
229 | $version = self::version3; | |
230 | switch($ver) { | |
231 | case self::MD5: | |
232 | $version = self::version3; | |
233 | $uuid = md5($ns.$node,1); | |
234 | break; | |
235 | case self::SHA1: | |
236 | $version = self::version5; | |
237 | $uuid = substr(sha1($ns.$node,1),0, 16); | |
238 | break; | |
239 | } | |
240 | // set variant | |
241 | $uuid[8] = chr(ord($uuid[8]) & self::clearVar | self::varRFC); | |
242 | // set version | |
243 | $uuid[6] = chr(ord($uuid[6]) & self::clearVer | $version); | |
244 | return ($uuid); | |
245 | } | |
246 | ||
247 | protected static function makeBin($str, $len) { | |
248 | /* Insure that an input string is either binary or hexadecimal. | |
249 | Returns binary representation, or false on failure. */ | |
250 | if ($str instanceof self) { | |
251 | return $str->bytes; | |
252 | } | |
253 | if (strlen($str)==$len) { | |
254 | return $str; | |
255 | } else { | |
256 | $str = preg_replace("/^urn:uuid:/is", "", $str); // strip URN scheme and namespace | |
257 | } | |
258 | $str = preg_replace("/[^a-f0-9]/is", "", $str); // strip non-hex characters | |
259 | if (strlen($str) != ($len * 2)) { | |
260 | return FALSE; | |
261 | } else { | |
262 | return pack("H*", $str); | |
263 | } | |
264 | } | |
265 | ||
266 | public static function initRandom() { | |
267 | /* Look for a system-provided source of randomness, which is usually crytographically secure. | |
268 | /dev/urandom is tried first simply out of bias for Linux systems. */ | |
269 | if (is_readable('/dev/urandom')) { | |
270 | self::$randomSource = fopen('/dev/urandom', 'rb'); | |
271 | self::$randomFunc = 'randomFRead'; | |
272 | } | |
273 | else if (class_exists('COM', 0)) { | |
274 | try { | |
275 | self::$randomSource = new COM('CAPICOM.Utilities.1'); // See http://msdn.microsoft.com/en-us/library/aa388182(VS.85).aspx | |
276 | self::$randomFunc = 'randomCOM'; | |
277 | } | |
278 | catch(Exception $e) { | |
279 | } | |
280 | } | |
281 | return self::$randomFunc; | |
282 | } | |
283 | ||
284 | public static function randomBytes($bytes) { | |
285 | return call_user_func(array('self', self::$randomFunc), $bytes); | |
286 | } | |
287 | ||
288 | protected static function randomTwister($bytes) { | |
289 | /* Get the specified number of random bytes, using mt_rand(). | |
290 | Randomness is returned as a string of bytes. */ | |
291 | $rand = ""; | |
292 | for ($a = 0; $a < $bytes; $a++) { | |
293 | $rand .= chr(mt_rand(0, 255)); | |
294 | } | |
295 | return $rand; | |
296 | } | |
297 | ||
298 | protected static function randomFRead($bytes) { | |
299 | /* Get the specified number of random bytes using a file handle | |
300 | previously opened with UUID::initRandom(). | |
301 | Randomness is returned as a string of bytes. */ | |
302 | return fread(self::$randomSource, $bytes); | |
303 | } | |
304 | ||
305 | protected static function randomCOM($bytes) { | |
306 | /* Get the specified number of random bytes using Windows' | |
307 | randomness source via a COM object previously created by UUID::initRandom(). | |
308 | Randomness is returned as a string of bytes. */ | |
309 | return base64_decode(self::$randomSource->GetRandom($bytes,0)); // straight binary mysteriously doesn't work, hence the base64 | |
310 | } | |
311 | } | |
312 | ||
313 | class UUIDException extends Exception { | |
314 | } |