Subversion Repositories oidplus

Rev

Rev 1407 | Rev 1417 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
604 daniel-mar 1
<?php
2
 
3
/*
4
 * UUID utils for PHP
1248 daniel-mar 5
 * Copyright 2011 - 2023 Daniel Marschall, ViaThinkSoft
1411 daniel-mar 6
 * Version 2023-09-24
604 daniel-mar 7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *     http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
 
21
# This library requires either the GMP extension (or BCMath if gmp_supplement.inc.php is present)
1324 daniel-mar 22
// TODO: If we are on 64 bit PHP (PHP_INT_SIZE > 4), then replace GMP with normal PHP operations
604 daniel-mar 23
 
1325 daniel-mar 24
if (file_exists($f = __DIR__ . '/mac_utils.inc.php')) include_once $f;
25
else if (file_exists($f = __DIR__ . '/mac_utils.inc.phps')) include_once $f;
1324 daniel-mar 26
 
1325 daniel-mar 27
if (file_exists($f = __DIR__ . '/gmp_supplement.inc.php')) include_once $f;
28
else if (file_exists($f = __DIR__ . '/gmp_supplement.inc.phps')) include_once $f;
604 daniel-mar 29
 
1325 daniel-mar 30
if (file_exists($f = __DIR__ . '/OidDerConverter.class.php')) include_once $f;
31
else if (file_exists($f = __DIR__ . '/OidDerConverter.class.phps')) include_once $f;
32
 
1384 daniel-mar 33
// Note: The RFC allows various notations as payload, not a strict notation constraint
34
const UUID_NAMEBASED_NS_DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
1324 daniel-mar 35
const UUID_NAMEBASED_NS_URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
36
const UUID_NAMEBASED_NS_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8';
1384 daniel-mar 37
const UUID_NAMEBASED_NS_X500_DN = '6ba7b814-9dad-11d1-80b4-00c04fd430c8';
604 daniel-mar 38
 
1324 daniel-mar 39
if (!function_exists('_random_int')) {
40
        function _random_int($min, $max) {
41
                // This function tries a CSRNG and falls back to a RNG if no CSRNG is available
42
                try {
43
                        return random_int($min, $max);
44
                } catch (Exception $e) {
45
                        return mt_rand($min, $max);
46
                }
1261 daniel-mar 47
        }
48
}
49
 
604 daniel-mar 50
function uuid_valid($uuid) {
51
        $uuid = str_replace(array('-', '{', '}'), '', $uuid);
52
        $uuid = strtoupper($uuid);
53
        #$uuid = trim($uuid);
54
 
55
        if (strlen($uuid) != 32) return false;
56
 
1324 daniel-mar 57
        $uuid = preg_replace('@[0-9A-F]@i', '', $uuid);
604 daniel-mar 58
 
59
        return ($uuid == '');
60
}
61
 
1376 daniel-mar 62
function uuid_equal($uuid1, $uuid2) {
63
        $uuid1 = uuid_canonize($uuid1);
64
        if (!$uuid1) return false;
65
        $uuid2 = uuid_canonize($uuid2);
66
        if (!$uuid2) return false;
67
        return $uuid1 === $uuid2;
68
}
69
 
1325 daniel-mar 70
function uuid_version($uuid) {
71
        $uuid = uuid_canonize($uuid);
72
        if (!$uuid) return false;
73
        return substr($uuid, 19, 1);
74
}
75
 
1324 daniel-mar 76
function uuid_info($uuid, $echo=true) {
604 daniel-mar 77
        if (!uuid_valid($uuid)) return false;
78
 
1325 daniel-mar 79
        $oid = uuid_to_oid($uuid);
80
 
81
        echo sprintf("%-32s %s\n", "Your input:", $uuid);
82
        echo "\n";
83
        echo "<u>Various notations:</u>\n";
84
        echo "\n";
85
        echo sprintf("%-32s %s\n", "URN:", 'urn:uuid:' . strtolower(oid_to_uuid(uuid_to_oid($uuid))));
86
        echo sprintf("%-32s %s\n", "URI:", 'uuid:' . strtolower(oid_to_uuid(uuid_to_oid($uuid))));
87
        echo sprintf("%-32s %s\n", "Microsoft GUID syntax:", '{' . strtoupper(oid_to_uuid(uuid_to_oid($uuid))) . '}');
88
        echo sprintf("%-32s %s\n", "C++ struct syntax:", uuid_c_syntax($uuid));
89
        echo "\n";
90
        echo sprintf("%-32s %s\n", "As OID:", $oid);
91
        if (class_exists('OidDerConverter')) {
92
                echo sprintf("%-32s %s\n", "DER encoding of OID:", OidDerConverter::hexarrayToStr(OidDerConverter::oidToDER($oid)));
93
        }
94
        echo "\n";
95
        echo "<u>Interpretation of the UUID:</u>\n\n";
96
 
1324 daniel-mar 97
        if (!$echo) ob_start();
98
 
604 daniel-mar 99
        #$uuid = trim($uuid);
100
        # $uuid = str_replace(array('-', '{', '}'), '', $uuid);
1324 daniel-mar 101
        $uuid = strtolower($uuid);
102
        $uuid = preg_replace('@[^0-9A-F]@i', '', $uuid);
604 daniel-mar 103
 
104
        $x = hexdec(substr($uuid, 16, 1));
1324 daniel-mar 105
             if ($x >= 14 /* 0b1110 */) $variant = 3;
106
        else if ($x >= 12 /* 0b110_ */) $variant = 2;
107
        else if ($x >=  8 /* 0b10__ */) $variant = 1;
108
        else if ($x >=  0 /* 0b0___ */) $variant = 0;
604 daniel-mar 109
        else $variant = -1; // should not happen
110
 
1324 daniel-mar 111
        if ($uuid == '00000000000000000000000000000000') {
112
                echo sprintf("%-32s %s\n", "Special Use:", "Nil UUID");
113
                echo "\n";
114
        }
115
        else if ($uuid == 'ffffffffffffffffffffffffffffffff') {
116
                echo sprintf("%-32s %s\n", "Special Use:", "Max UUID");
117
                echo "\n";
118
        }
119
 
604 daniel-mar 120
        switch ($variant) {
121
                case 0:
1324 daniel-mar 122
                        echo sprintf("%-32s %s\n", "Variant:", "[0b0__] Network Computing System (NCS)");
604 daniel-mar 123
 
124
                        /*
125
                         * Internal structure of variant #0 UUIDs
126
                         *
127
                         * The first 6 octets are the number of 4 usec units of time that have
128
                         * passed since 1/1/80 0000 GMT.  The next 2 octets are reserved for
129
                         * future use.  The next octet is an address family.  The next 7 octets
130
                         * are a host ID in the form allowed by the specified address family.
131
                         *
132
                         * Note that while the family field (octet 8) was originally conceived
133
                         * of as being able to hold values in the range [0..255], only [0..13]
134
                         * were ever used.  Thus, the 2 MSB of this field are always 0 and are
135
                         * used to distinguish old and current UUID forms.
136
                         */
137
 
1324 daniel-mar 138
                        /*
139
                        Variant 0 UUID
140
                        - 32 bit High Time
141
                        - 16 bit Low Time
142
                        - 16 bit Reserved
143
                        -  1 bit Variant (fix 0b0)
144
                        -  7 bit Family
145
                        - 56 bit Node
146
                        */
147
 
604 daniel-mar 148
                        // Example of an UUID: 333a2276-0000-0000-0d00-00809c000000
149
 
1324 daniel-mar 150
                        // TODO: also show legacy format, e.g. 458487b55160.02.c0.64.02.03.00.00.00
604 daniel-mar 151
 
1324 daniel-mar 152
                        # see also some notes at See https://github.com/cjsv/uuid/blob/master/Doc
153
 
154
                        /*
155
                        NOTE: A generator is not possible, because there are no timestamps left!
156
                        The last possible timestamp was:
157
                            [0xFFFFFFFFFFFF] 2015-09-05 05:58:26'210655 GMT
158
                        That is in the following UUID:
159
                            ffffffff-ffff-0000-027f-000001000000
160
                        Current timestamp generator:
161
                            echo dechex(round((microtime(true)+315532800)*250000));
162
                        */
163
 
604 daniel-mar 164
                        # Timestamp: Count of 4us intervals since 01 Jan 1980 00:00:00 GMT
165
                        # 1/0,000004 = 250000
166
                        # Seconds between 1970 and 1980 : 315532800
167
                        # 250000*315532800=78883200000000
168
                        $timestamp = substr($uuid, 0, 12);
169
                        $ts = gmp_init($timestamp, 16);
1324 daniel-mar 170
                        $ts = gmp_add($ts, gmp_init("78883200000000", 10));
171
                        $ms = gmp_mod($ts, gmp_init("250000", 10));
172
                        $ts = gmp_div($ts, gmp_init("250000", 10));
173
                        $ts = gmp_strval($ts, 10);
174
                        $ms = gmp_strval($ms, 10);
175
                        $ts = gmdate('Y-m-d H:i:s', intval($ts))."'".str_pad($ms, 6/*us*/, '0', STR_PAD_LEFT).' GMT';
176
                        echo sprintf("%-32s %s\n", "Timestamp:", "[0x$timestamp] $ts");
604 daniel-mar 177
 
178
                        $reserved = substr($uuid, 12, 4);
1324 daniel-mar 179
                        echo sprintf("%-32s %s\n", "Reserved:", "[0x$reserved]");
604 daniel-mar 180
 
181
                        $family_hex = substr($uuid, 16, 2);
182
                        $family_dec = hexdec($family_hex);
1324 daniel-mar 183
                        $nodeid_hex = substr($uuid, 18, 14);
184
                        $nodeid_dec = hexdec($nodeid_hex);
185
 
186
                        // Sources:
187
                        // - https://bitsavers.org/pdf/ibm/rs6000/aix_3.0/SC23-2206-0_AIX_Version_3_for_RS6000_Communications_Programming_Concepts_199003.pdf
188
                        // - (For comparison) https://github.com/uuid6/uuid6-ietf-draft/issues/26#issuecomment-1062164457
189
                        // - (For comparison) https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.addressfamily?view=net-7.0 [numbers 0..13 are mostly identical]
190
 
191
                        if ($family_dec == 0) {
192
                                # Microsoft's AdressFamily: Unspecified 0       Unspecified address family.
193
                                # AIX 3.0 Manual:  0   unspec = Unspecified
194
                                $family_name = 'socket_$unspec (Unspecified)';
195
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
196
                        }
197
                        else if ($family_dec == 1) {
198
                                # Microsoft's AdressFamily: Unix        1       Unix local to host address.
199
                                # AIX 3.0 Manual:  1   unix = Local to host (pipes, portals)
200
                                $family_name = 'socket_$unix (Local to host, e.g. pipes, portals)';
201
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
202
                        }
203
                        else if ($family_dec == 2) {
204
                                # Microsoft's AdressFamily: InterNetwork        2       Address for IP version 4.
205
                                # AIX 3.0 Manual:  2   ip = Internet Protocols
206
                                $family_name = 'socket_$internet (Internet Protocols, e.g. IPv4)';
207
                                // https://www.ibm.com/docs/en/aix/7.1?topic=u-uuid-gen-command-ncs (AIX 7.1) shows the following example output for /etc/ncs/uuid_gen -P
208
                                // := [
209
                                //    time_high := 16#458487df,
210
                                //    time_low := 16#9fb2,
211
                                //    reserved := 16#000,
212
                                //    family := chr(16#02),
213
                                //    host := [chr(16#c0), chr(16#64), chr(16#02), chr(16#03),
214
                                //             chr(16#00), chr(16#00), chr(16#00)]
215
                                //    ]
216
                                // This means that the IP address is 32 bits hex, and 32 bits are unused
217
                                $nodeid_desc = hexdec(substr($nodeid_hex,0,2)).'.'.
218
                                               hexdec(substr($nodeid_hex,2,2)).'.'.
219
                                               hexdec(substr($nodeid_hex,4,2)).'.'.
220
                                               hexdec(substr($nodeid_hex,6,2));
221
                                $rest = substr($nodeid_hex,8,6);
222
                                if ($rest != '000000') $nodeid_desc .= " + unexpected rest 0x$rest";
223
                        }
224
                        else if ($family_dec == 3) {
225
                                # Microsoft's AdressFamily: ImpLink     3       ARPANET IMP address.
226
                                # AIX 3.0 Manual:  3   implink = ARPANET imp addresses
227
                                $family_name = 'socket_$implink (ARPANET imp addresses)';
228
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
229
                        }
230
                        else if ($family_dec == 4) {
231
                                # Microsoft's AdressFamily: Pup 4       Address for PUP protocols.
232
                                # AIX 3.0 Manual:  4   pup = Pup protocols (for example, BSP)
233
                                $family_name = 'socket_$pup (Pup protocols, e.g. BSP)';
234
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
235
                        }
236
                        else if ($family_dec == 5) {
237
                                # Microsoft's AdressFamily: Chaos       5       Address for MIT CHAOS protocols.
238
                                # AIX 3.0 Manual:  5   chaos = MIT CHAOS protocols
239
                                $family_name = 'socket_$chaos (MIT CHAOS protocols)';
240
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
241
                        }
242
                        else if ($family_dec == 6) {
243
                                # Microsoft's AdressFamily: NS  6       Address for Xerox NS protocols.
244
                                # Microsoft's AdressFamily: Ipx 6       IPX or SPX address.
245
                                # AIX 3.0 Manual:  6   ns = XEROX NS protocols
246
                                $family_name = 'socket_$ns (XEROX NS protocols)';
247
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
248
                        }
249
                        else if ($family_dec == 7) {
250
                                # Microsoft's AdressFamily: Osi 7       Address for OSI protocols.
251
                                # Microsoft's AdressFamily: Iso 7       Address for ISO protocols.
252
                                # AIX 3.0 Manual:  7   nbs = NBS protocols
253
                                $family_name = 'socket_$nbs (NBS protocols)';
254
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
255
                        }
256
                        else if ($family_dec == 8) {
257
                                # Microsoft's AdressFamily: Ecma        8       European Computer Manufacturers Association (ECMA) address.
258
                                # AIX 3.0 Manual:  8   ecma = European computer manufacturers
259
                                $family_name = 'socket_$ecma (European computer manufacturers protocols)';
260
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
261
                        }
262
                        else if ($family_dec == 9) {
263
                                # Microsoft's AdressFamily: DataKit     9       Address for Datakit protocols.
264
                                # AIX 3.0 Manual:  9   datakit = Datakit protocols
265
                                $family_name = 'socket_$datakit (Datakit protocols)';
266
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
267
                        }
268
                        else if ($family_dec == 10) {
269
                                # Microsoft's AdressFamily: Ccitt       10      Addresses for CCITT protocols, such as X.25.
270
                                # AIX 3.0 Manual:  A   ccitt = CCITT protocols (for example, X.25)
271
                                $family_name = 'socket_$ccitt (CCITT protocols, e.g. X.25)';
272
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
273
                        }
274
                        else if ($family_dec == 11) {
275
                                # Microsoft's AdressFamily: Sna 11      IBM SNA address.
276
                                # AIX 3.0 Manual:  B   sna = IBM SNA
277
                                $family_name = 'socket_$sna (IBM SNA)';
278
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
279
                        }
280
                        else if ($family_dec == 12) {
281
                                # Microsoft's AdressFamily: DecNet      12      DECnet address.
282
                                # AIX 3.0 Manual:  C   unspec2 = Unspecified
283
                                $family_name = 'socket_$unspec2 (Unspecified)';
284
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
285
                        }
286
                        else if ($family_dec == 13) {
287
                                # Microsoft's AdressFamily: DataLink    13      Direct data-link interface address.
288
                                # AIX 3.0 Manual:  D   dds = Domain DDS protocol
289
                                # Some also call this "Data Link" ... Is that correct?
290
                                $family_name = 'socket_$dds (Domain DDS protocol)';
291
                                // https://www.ibm.com/docs/en/aix/7.1?topic=u-uuid-gen-command-ncs (AIX 7.1) shows the following example output for /etc/ncs/uuid_gen -C
292
                                // = { 0x34dc23af,
293
                                //    0xf000,
294
                                //    0x0000,
295
                                //    0x0d,
296
                                //    {0x00, 0x00, 0x7c, 0x5f, 0x00, 0x00, 0x00} };
297
                                // https://github.com/cjsv/uuid/blob/master/Doc writes:
298
                                //    "Family 13 (dds) looks like node is 00 | nnnnnn 000000."
299
 
300
                                $nodeid_desc = '';
301
 
302
                                $start = substr($nodeid_hex,0,2);
303
                                if ($start != '00') $nodeid_desc .= "unexpected start 0x$start + ";
304
 
305
                                $nodeid_desc .= ($nodeid_dec >> 24) & 0xFFFFFF;
306
 
307
                                $rest = substr($nodeid_hex,8,6);
308
                                if ($rest != '000000') $nodeid_desc .= " + unexpected rest 0x$rest";
604 daniel-mar 309
                        } else {
1324 daniel-mar 310
                                $family_name = "Unknown (Family $family_dec)"; # There are probably no more families
311
                                $nodeid_desc = "Unknown";
604 daniel-mar 312
                        }
1324 daniel-mar 313
                        echo sprintf("%-32s %s\n", "Family:", "[0x$family_hex] $family_name");
604 daniel-mar 314
 
1324 daniel-mar 315
                        echo sprintf("%-32s %s\n", "Node ID:", "[0x$nodeid_hex] $nodeid_desc");
604 daniel-mar 316
 
317
                        break;
318
                case 1:
1324 daniel-mar 319
                        // TODO: Show byte order: 00112233-4455-6677-8899-aabbccddeeff => 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff
604 daniel-mar 320
 
321
                        $version = hexdec(substr($uuid, 12, 1));
1324 daniel-mar 322
 
323
                        if ($version <= 2) {
324
                                echo sprintf("%-32s %s\n", "Variant:", "[0b10_] RFC 4122 (Leach-Mealling-Salz) / DCE 1.1");
325
                        } else if (($version >= 3) && ($version <= 5)) {
326
                                echo sprintf("%-32s %s\n", "Variant:", "[0b10_] RFC 4122 (Leach-Mealling-Salz)");
327
                        } else if (($version >= 6) && ($version <= 8)) {
1407 daniel-mar 328
                                echo sprintf("%-32s %s\n", "Variant:", "[0b10_] RFC draft-ietf-uuidrev-rfc4122bis (Davis-Peabody-Leach)"); // TODO: When new RFC is published, replace the RFC number
1324 daniel-mar 329
                        } else {
1407 daniel-mar 330
                                echo sprintf("%-32s %s\n", "Variant:", "[0b10_] Unknown RFC");
1324 daniel-mar 331
                        }
332
 
604 daniel-mar 333
                        switch ($version) {
1324 daniel-mar 334
                                case 6:
335
                                        /*
336
                                        Variant 1, Version 6 UUID
337
                                        - 48 bit High Time
338
                                        -  4 bit Version (fix 0x6)
339
                                        - 12 bit Low Time
340
                                        -  2 bit Variant (fix 0b10)
341
                                        -  6 bit Clock Sequence High
342
                                        -  8 bit Clock Sequence Low
343
                                        - 48 bit MAC Address
344
                                        */
1325 daniel-mar 345
                                        echo sprintf("%-32s %s\n", "Version:", "[0x6] Reordered Time-Based");
1324 daniel-mar 346
                                        $uuid = substr($uuid,  0, 8).'-'.
347
                                                substr($uuid,  8, 4).'-'.
348
                                                substr($uuid, 12, 4).'-'.
349
                                                substr($uuid, 16, 4).'-'.
350
                                                substr($uuid, 20, 12);
351
                                        $uuid = uuid6_to_uuid1($uuid);
352
                                        $uuid = str_replace('-', '', $uuid);
353
 
354
                                /* fallthrough */
604 daniel-mar 355
                                case 1:
1324 daniel-mar 356
                                        /*
357
                                        Variant 1, Version 1 UUID
358
                                        - 32 bit Low Time
359
                                        - 16 bit Mid Time
360
                                        -  4 bit Version (fix 0x1)
361
                                        - 12 bit High Time
362
                                        -  2 bit Variant (fix 0b10)
363
                                        -  6 bit Clock Sequence High
364
                                        -  8 bit Clock Sequence Low
365
                                        - 48 bit MAC Address
366
                                        */
604 daniel-mar 367
 
1325 daniel-mar 368
                                        if ($version == 1) echo sprintf("%-32s %s\n", "Version:", "[0x1] Time-based with unique host identifier");
1324 daniel-mar 369
 
604 daniel-mar 370
                                        # Timestamp: Count of 100ns intervals since 15 Oct 1582 00:00:00
371
                                        # 1/0,0000001 = 10000000
372
                                        $timestamp = substr($uuid, 13, 3).substr($uuid, 8, 4).substr($uuid, 0, 8);
373
                                        $ts = gmp_init($timestamp, 16);
1324 daniel-mar 374
                                        $ts = gmp_sub($ts, gmp_init("122192928000000000", 10));
375
                                        $ms = gmp_mod($ts, gmp_init("10000000", 10));
376
                                        $ts = gmp_div($ts, gmp_init("10000000", 10));
377
                                        $ts = gmp_strval($ts, 10);
378
                                        $ms = gmp_strval($ms, 10);
379
                                        $ts = gmdate('Y-m-d H:i:s', intval($ts))."'".str_pad($ms, 7/*0.1us*/, '0', STR_PAD_LEFT).' GMT';
380
                                        echo sprintf("%-32s %s\n", "Timestamp:", "[0x$timestamp] $ts");
604 daniel-mar 381
 
382
                                        $x = hexdec(substr($uuid, 16, 4));
383
                                        $dec = $x & 0x3FFF; // The highest 2 bits are used by "variant" (10x)
384
                                        $hex = substr($uuid, 16, 4);
1324 daniel-mar 385
                                        $hex = '<abbr title="The highest 2 bits are used by the UUID variant (10xx)">'.$hex[0].'</abbr>'.substr($hex,1);
386
                                        echo sprintf("%-32s %s\n", "Clock ID:", "[0x$hex] $dec");
604 daniel-mar 387
 
388
                                        $x = substr($uuid, 20, 12);
389
                                        $nodeid = '';
390
                                        for ($i=0; $i<6; $i++) {
391
                                                $nodeid .= substr($x, $i*2, 2);
1324 daniel-mar 392
                                                if ($i != 5) $nodeid .= '-';
604 daniel-mar 393
                                        }
1324 daniel-mar 394
                                        $nodeid = strtoupper($nodeid);
395
                                        echo sprintf("%-32s %s\n", "Node ID:", "[0x$x] $nodeid");
604 daniel-mar 396
 
1324 daniel-mar 397
                                        echo "\n<u>In case that this Node ID is a MAC address, here is the interpretation of that MAC address:</u>\n\n";
398
                                        decode_mac(strtoupper($nodeid));
604 daniel-mar 399
 
400
                                        break;
401
                                case 2:
1324 daniel-mar 402
                                        /*
403
                                        Variant 1, Version 2 UUID
404
                                        - 32 bit Local Domain Number
405
                                        - 16 bit Mid Time
406
                                        -  4 bit Version (fix 0x2)
407
                                        - 12 bit High Time
408
                                        -  2 bit Variant (fix 0b10)
409
                                        -  6 bit Clock Sequence
410
                                        -  8 bit Local Domain
411
                                        - 48 bit MAC Address
412
                                        */
604 daniel-mar 413
 
1324 daniel-mar 414
                                        // see also https://unicorn-utterances.com/posts/what-happened-to-uuid-v2
604 daniel-mar 415
 
1325 daniel-mar 416
                                        echo sprintf("%-32s %s\n", "Version:", "[0x2] DCE Security version");
1324 daniel-mar 417
 
604 daniel-mar 418
                                        # The clock_seq_low field (which represents an integer in the range [0, 28-1]) is interpreted as a local domain (as represented by sec_rgy_domain_t; see sec_rgy_domain_t ); that is, an identifier domain meaningful to the local host. (Note that the data type sec_rgy_domain_t can potentially hold values outside the range [0, 28-1]; however, the only values currently registered are in the range [0, 2], so this type mismatch is not significant.) In the particular case of a POSIX host, the value sec_rgy_domain_person is to be interpreted as the "POSIX UID domain", and the value sec_rgy_domain_group is to be interpreted as the "POSIX GID domain".
419
                                        $x = substr($uuid, 18, 2);
1324 daniel-mar 420
                                        if ($x == '00') $domain_info = 'Person (e.g. POSIX UID)';
421
                                        else if ($x == '01') $domain_info = 'Group (e.g. POSIX GID)';
422
                                        else if ($x == '02') $domain_info = 'Organization';
423
                                        else $domain_info = 'site-defined (Domain '.hexdec($x).')';
424
                                        echo sprintf("%-32s %s\n", "Local Domain:", "[0x$x] $domain_info");
604 daniel-mar 425
 
1324 daniel-mar 426
                                        # The time_low field (which represents an integer in the range [0, 232-1]) is interpreted as a local-ID; that is, an identifier (within the domain specified by clock_seq_low) meaningful to the local host. In the particular case of a POSIX host, when combined with a POSIX UID or POSIX GID domain in the clock_seq_low field (above), the time_low field represents a POSIX UID or POSIX GID, respectively.
427
                                        $x = substr($uuid, 0, 8);
428
                                        $dec = hexdec($x);
429
                                        echo sprintf("%-32s %s\n", "Local Domain Number:", "[0x$x] $dec");
430
 
604 daniel-mar 431
                                        # Timestamp: Count of 100ns intervals since 15 Oct 1582 00:00:00
432
                                        # 1/0,0000001 = 10000000
433
                                        $timestamp = substr($uuid, 13, 3).substr($uuid, 8, 4).'00000000';
434
                                        $ts = gmp_init($timestamp, 16);
1324 daniel-mar 435
                                        $ts = gmp_sub($ts, gmp_init("122192928000000000", 10));
436
                                        $ms = gmp_mod($ts, gmp_init("10000000", 10));
437
                                        $ts = gmp_div($ts, gmp_init("10000000", 10));
438
                                        $ts = gmp_strval($ts, 10);
439
                                        $ms = gmp_strval($ms, 10);
440
                                        $ts_min = gmdate('Y-m-d H:i:s', intval($ts))."'".str_pad($ms, 7/*0.1us*/, '0', STR_PAD_LEFT).' GMT';
604 daniel-mar 441
 
442
                                        $timestamp = substr($uuid, 13, 3).substr($uuid, 8, 4).'FFFFFFFF';
443
                                        $ts = gmp_init($timestamp, 16);
1324 daniel-mar 444
                                        $ts = gmp_sub($ts, gmp_init("122192928000000000", 10));
445
                                        $ms = gmp_mod($ts, gmp_init("10000000", 10));
446
                                        $ts = gmp_div($ts, gmp_init("10000000", 10));
447
                                        $ts = gmp_strval($ts, 10);
448
                                        $ms = gmp_strval($ms, 10);
449
                                        $ts_max = gmdate('Y-m-d H:i:s', intval($ts))."'".str_pad($ms, 7/*0.1us*/, '0', STR_PAD_LEFT).' GMT';
604 daniel-mar 450
 
1324 daniel-mar 451
                                        $timestamp = substr($uuid, 13, 3).substr($uuid, 8, 4)/*.'xxxxxxxx'*/;
452
                                        echo sprintf("%-32s %s\n", "Timestamp:", "[0x$timestamp] $ts_min - $ts_max");
604 daniel-mar 453
 
1324 daniel-mar 454
                                        $x = hexdec(substr($uuid, 16, 2));
455
                                        $dec = $x & 0x3F; // The highest 2 bits are used by "variant" (10xx)
456
                                        $hex = substr($uuid, 16, 2);
457
                                        $hex = '<abbr title="The highest 2 bits are used by the UUID variant (10xx)">'.$hex[0].'</abbr>'.substr($hex,1);
458
                                        echo sprintf("%-32s %s\n", "Clock ID:", "[0x$hex] $dec");
604 daniel-mar 459
 
460
                                        $x = substr($uuid, 20, 12);
461
                                        $nodeid = '';
462
                                        for ($i=0; $i<6; $i++) {
463
                                                $nodeid .= substr($x, $i*2, 2);
1324 daniel-mar 464
                                                if ($i != 5) $nodeid .= '-';
604 daniel-mar 465
                                        }
1324 daniel-mar 466
                                        $nodeid = strtoupper($nodeid);
467
                                        echo sprintf("%-32s %s\n", "Node ID:", "[0x$x] $nodeid");
604 daniel-mar 468
 
1324 daniel-mar 469
                                        echo "\n<u>In case that this Node ID is a MAC address, here is the interpretation of that MAC address:</u>\n\n";
470
                                        decode_mac(strtoupper($nodeid));
604 daniel-mar 471
 
472
                                        break;
473
                                case 3:
1324 daniel-mar 474
                                        /*
475
                                        Variant 1, Version 3 UUID
476
                                        - 48 bit Hash High
477
                                        -  4 bit Version (fix 0x3)
478
                                        - 12 bit Hash Mid
479
                                        -  2 bit Variant (fix 0b10)
480
                                        - 62 bit Hash Low
481
                                        */
604 daniel-mar 482
 
1325 daniel-mar 483
                                        echo sprintf("%-32s %s\n", "Version:", "[0x3] Name-based (MD5 hash)");
1324 daniel-mar 484
 
604 daniel-mar 485
                                        $hash = str_replace('-', '', strtolower($uuid));
1324 daniel-mar 486
 
604 daniel-mar 487
                                        $hash[12] = '?'; // was overwritten by version
1324 daniel-mar 488
 
489
                                        $var16a = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b0000));
490
                                        $var16b = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b0100));
491
                                        $var16c = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b1000));
492
                                        $var16d = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b1100));
604 daniel-mar 493
                                        $hash[16] = '?'; // was partially overwritten by variant
494
 
1324 daniel-mar 495
                                        $p = 16;
496
                                        $hash = substr($hash,0,$p)."<abbr title=\"$var16a, $var16b, $var16c, or $var16d\">".substr($hash,$p,1).'</abbr>'.substr($hash,$p+1);
497
                                        echo sprintf("%-32s %s\n", "MD5(Namespace+Subject):", "[0x$hash]");
604 daniel-mar 498
 
499
                                        break;
500
                                case 4:
1324 daniel-mar 501
                                        /*
502
                                        Variant 1, Version 4 UUID
503
                                        - 48 bit Random High
504
                                        -  4 bit Version (fix 0x4)
505
                                        - 12 bit Random Mid
506
                                        -  2 bit Variant (fix 0b10)
507
                                        - 62 bit Random Low
508
                                        */
604 daniel-mar 509
 
1325 daniel-mar 510
                                        echo sprintf("%-32s %s\n", "Version:", "[0x4] Random");
1324 daniel-mar 511
 
512
                                        $rand_line1 = '';
513
                                        $rand_line2 = '';
604 daniel-mar 514
                                        for ($i=0; $i<16; $i++) {
515
                                                $bin = base_convert(substr($uuid, $i*2, 2), 16, 2);
516
                                                $bin = str_pad($bin, 8, "0", STR_PAD_LEFT);
517
 
518
                                                if ($i == 6) {
1324 daniel-mar 519
                                                        // was overwritten by version
520
                                                        $bin[0] = '?';
521
                                                        $bin[1] = '?';
522
                                                        $bin[2] = '?';
523
                                                        $bin[3] = '?';
604 daniel-mar 524
                                                } else if ($i == 8) {
1324 daniel-mar 525
                                                        // was partially overwritten by variant
526
                                                        $bin[0] = '?';
527
                                                        $bin[1] = '?';
604 daniel-mar 528
                                                }
529
 
1324 daniel-mar 530
                                                if ($i<8) $rand_line1 .= "$bin ";
531
                                                if ($i>=8) $rand_line2 .= "$bin ";
604 daniel-mar 532
                                        }
1324 daniel-mar 533
                                        echo sprintf("%-32s %s\n", "Random bits:", trim($rand_line1));
534
                                        echo sprintf("%-32s %s\n", "",             trim($rand_line2));
604 daniel-mar 535
 
1324 daniel-mar 536
                                        $rand_bytes = str_replace('-', '', strtolower($uuid));
537
                                        $rand_bytes[12] = '?'; // was overwritten by version
538
                                        $var16a = strtoupper(dechex(hexdec($rand_bytes[16]) & 0b0011 | 0b0000));
539
                                        $var16b = strtoupper(dechex(hexdec($rand_bytes[16]) & 0b0011 | 0b0100));
540
                                        $var16c = strtoupper(dechex(hexdec($rand_bytes[16]) & 0b0011 | 0b1000));
541
                                        $var16d = strtoupper(dechex(hexdec($rand_bytes[16]) & 0b0011 | 0b1100));
542
                                        $rand_bytes[16] = '?'; // was partially overwritten by variant
604 daniel-mar 543
 
1324 daniel-mar 544
                                        $p = 16;
545
                                        $rand_bytes = substr($rand_bytes,0,$p)."<abbr title=\"$var16a, $var16b, $var16c, or $var16d\">".substr($rand_bytes,$p,1).'</abbr>'.substr($rand_bytes,$p+1);
546
                                        echo sprintf("%-32s %s\n", "Random bytes:", "[0x$rand_bytes]");
547
 
604 daniel-mar 548
                                        break;
549
                                case 5:
1324 daniel-mar 550
                                        /*
551
                                        Variant 1, Version 5 UUID
552
                                        - 48 bit Hash High
553
                                        -  4 bit Version (fix 0x5)
554
                                        - 12 bit Hash Mid
555
                                        -  2 bit Variant (fix 0b10)
556
                                        - 62 bit Hash Low
557
                                        */
604 daniel-mar 558
 
1325 daniel-mar 559
                                        echo sprintf("%-32s %s\n", "Version:", "[0x5] Name-based (SHA-1 hash)");
1324 daniel-mar 560
 
604 daniel-mar 561
                                        $hash = str_replace('-', '', strtolower($uuid));
1324 daniel-mar 562
 
604 daniel-mar 563
                                        $hash[12] = '?'; // was overwritten by version
1324 daniel-mar 564
 
565
                                        $var16a = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b0000));
566
                                        $var16b = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b0100));
567
                                        $var16c = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b1000));
568
                                        $var16d = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b1100));
604 daniel-mar 569
                                        $hash[16] = '?'; // was partially overwritten by variant
1324 daniel-mar 570
 
604 daniel-mar 571
                                        $hash .= '????????'; // was cut off
572
 
1324 daniel-mar 573
                                        $p = 16;
574
                                        $hash = substr($hash,0,$p)."<abbr title=\"$var16a, $var16b, $var16c, or $var16d\">".substr($hash,$p,1).'</abbr>'.substr($hash,$p+1);
575
                                        echo sprintf("%-32s %s\n", "SHA1(Namespace+Subject):", "[0x$hash]");
604 daniel-mar 576
 
1324 daniel-mar 577
                                        break;
578
                                case 7:
579
                                        /*
580
                                        Variant 1, Version 7 UUID
581
                                        - 48 bit Unix Time in milliseconds
582
                                        -  4 bit Version (fix 0x7)
1411 daniel-mar 583
                                        - 12 bit Data
1324 daniel-mar 584
                                        -  2 bit Variant (fix 0b10)
1411 daniel-mar 585
                                        - 62 bit Data
586
 
587
                                        Structure of data (74 bits):
588
                                        - OPTIONAL : Sub-millisecond timestamp fraction (0-12 bits)
589
                                        - OPTIONAL : Carefully seeded counter
590
                                        - Random generated bits for any remaining space
591
 
592
                                        Since we don't know if timestamp fraction or counters are implemented
593
                                        (and if so, how many bits), we don't decode this information
1324 daniel-mar 594
                                        */
604 daniel-mar 595
 
1325 daniel-mar 596
                                        echo sprintf("%-32s %s\n", "Version:", "[0x7] Unix Epoch Time");
1324 daniel-mar 597
 
598
                                        $timestamp = substr($uuid, 0, 12);
599
 
600
                                        // Timestamp: Split into seconds and milliseconds
601
                                        $ts = gmp_init($timestamp, 16);
602
                                        $ms = gmp_mod($ts, gmp_init("1000", 10));
603
                                        $ts = gmp_div($ts, gmp_init("1000", 10));
604
                                        $ts = gmp_strval($ts, 10);
605
                                        $ms = gmp_strval($ms, 10);
606
                                        $ts = gmdate('Y-m-d H:i:s', intval($ts))."'".str_pad($ms, 3/*ms*/, '0', STR_PAD_LEFT).' GMT';
607
                                        echo sprintf("%-32s %s\n", "Timestamp:", "[0x$timestamp] $ts");
608
 
609
                                        $rand = '';
610
                                        for ($i=6; $i<16; $i++) {
611
                                                $bin = base_convert(substr($uuid, $i*2, 2), 16, 2);
612
                                                $bin = str_pad($bin, 8, "0", STR_PAD_LEFT);
613
 
614
                                                if ($i == 6) {
615
                                                        // was overwritten by version
616
                                                        $bin[0] = '?';
617
                                                        $bin[1] = '?';
618
                                                        $bin[2] = '?';
619
                                                        $bin[3] = '?';
620
                                                } else if ($i == 8) {
621
                                                        // was partially overwritten by variant
622
                                                        $bin[0] = '?';
623
                                                        $bin[1] = '?';
624
                                                }
625
 
626
                                                $rand .= "$bin ";
627
                                        }
628
                                        echo sprintf("%-32s %s\n", "Random bits:", trim($rand));
629
 
630
                                        $rand_bytes = substr(str_replace('-', '', strtolower($uuid)),13);
631
                                        $var16a = strtoupper(dechex(hexdec($rand_bytes[3]) & 0b0011 | 0b0000));
632
                                        $var16b = strtoupper(dechex(hexdec($rand_bytes[3]) & 0b0011 | 0b0100));
633
                                        $var16c = strtoupper(dechex(hexdec($rand_bytes[3]) & 0b0011 | 0b1000));
634
                                        $var16d = strtoupper(dechex(hexdec($rand_bytes[3]) & 0b0011 | 0b1100));
635
                                        $rand_bytes[3] = '?'; // was partially overwritten by variant
636
 
637
                                        $p = 3;
638
                                        $rand_bytes = substr($rand_bytes,0,$p)."<abbr title=\"$var16a, $var16b, $var16c, or $var16d\">".substr($rand_bytes,$p,1).'</abbr>'.substr($rand_bytes,$p+1);
639
                                        echo sprintf("%-32s %s\n", "Random bytes:", "[0x$rand_bytes]");
640
 
641
                                        // TODO: convert to and from Base32 CROCKFORD ULID (make 2 methods in uuid_utils.inc.php)
642
                                        // e.g. ULID: 01GCZ05N3JFRKBRWKNGCQZGP44
643
                                        // "Be aware that all version 7 UUIDs may be converted to ULIDs but not all ULIDs may be converted to UUIDs."
644
 
604 daniel-mar 645
                                        break;
1324 daniel-mar 646
                                case 8:
647
                                        /*
648
                                        Variant 1, Version 8 UUID
1376 daniel-mar 649
                                        - 48 bit Custom data [Block 1+2]
1324 daniel-mar 650
                                        -  4 bit Version (fix 0x8)
1376 daniel-mar 651
                                        - 12 bit Custom data [Block 3]
1324 daniel-mar 652
                                        -  2 bit Variant (fix 0b10)
1376 daniel-mar 653
                                        - 62 bit Custom data [Block 4+5]
1324 daniel-mar 654
                                        */
655
 
1325 daniel-mar 656
                                        echo sprintf("%-32s %s\n", "Version:", "[0x8] Custom implementation");
1324 daniel-mar 657
 
658
                                        $custom_data = substr($uuid,0,12).substr($uuid,13); // exclude version nibble
659
                                        $custom_data[15] = dechex(hexdec($custom_data[15]) & 0b0011); // nibble was partially overwritten by variant
660
                                        $custom_data = strtolower($custom_data);
661
 
662
                                        $custom_block1 = substr($uuid,  0, 8);
663
                                        $custom_block2 = substr($uuid,  8, 4);
664
                                        $custom_block3 = substr($uuid, 12, 4);
665
                                        $custom_block4 = substr($uuid, 16, 4);
666
                                        $custom_block5 = substr($uuid, 20);
667
 
668
                                        $custom_block3 = substr($custom_block3, 1); // remove version
669
                                        $custom_block4[0] = dechex(hexdec($custom_block4[0]) & 0b0011); // remove variant
670
 
671
                                        echo sprintf("%-32s %s\n", "Custom data:", "[0x$custom_data]");
1334 daniel-mar 672
                                        echo sprintf("%-32s %s\n", "Custom data block1 (32 bit):", "[0x$custom_block1]");
673
                                        echo sprintf("%-32s %s\n", "Custom data block2 (16 bit):", "[0x$custom_block2]");
674
                                        echo sprintf("%-32s %s\n", "Custom data block3 (12 bit):", "[0x$custom_block3]");
675
                                        echo sprintf("%-32s %s\n", "Custom data block4 (14 bit):", "[0x$custom_block4]");
676
                                        echo sprintf("%-32s %s\n", "Custom data block5 (48 bit):", "[0x$custom_block5]");
1324 daniel-mar 677
 
1345 daniel-mar 678
                                        // START: Check if Custom UUIDv8 is likely an OIDplus 2.0 Custom UUID
1334 daniel-mar 679
 
1345 daniel-mar 680
                                        $oidplus_systemid_hex = $custom_block1;
681
                                        $oidplus_systemid_int = hexdec($oidplus_systemid_hex); // 31 bit hash of public key
682
                                        $oidplus_systemid_valid = hexdec($custom_block1) < 0x80000000;
1334 daniel-mar 683
 
1345 daniel-mar 684
                                        $oidplus_creation_hex = $custom_block2;
685
                                        $oidplus_creation_int = hexdec($oidplus_creation_hex); // days since 1 January 1970, or 0 if unknown
686
                                        //$oidplus_creation_valid = ($oidplus_creation_int >= 14610/*1 Jan 2010*/) && ($oidplus_creation_int <= floor(time()/24/60/60)/*Today*/);
687
                                        $oidplus_creation_unknown = $oidplus_creation_int == 0;
688
 
689
                                        $oidplus_reserved_hex = $custom_block3;
690
                                        $oidplus_reserved_int = hexdec($oidplus_reserved_hex);
691
 
692
                                        $oidplus_namespace_hex = $custom_block4;
693
                                        $oidplus_namespace_int = hexdec($oidplus_namespace_hex);
694
 
695
                                        $oidplus_data_hex = $custom_block5;
696
                                        $oidplus_data_int = (PHP_INT_SIZE == 4) ? gmp_strval(gmp_init($oidplus_data_hex,16),10) : hexdec($custom_block5);
697
 
698
                                        if ($oidplus_systemid_valid && ($oidplus_reserved_int == 0)) {
699
                                                if (($oidplus_namespace_int == 0) && $oidplus_creation_unknown && (strtolower($oidplus_data_hex) == '1890afd80709')) {
700
                                                        // System GUID, e.g. 6e932dd7-0000-8000-8000-1890afd80709
701
                                                        echo "\n<u>Interpretation of <a href=\"https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md\">OIDplus 2.0 Custom UUID</a></u>\n\n";
702
                                                        echo sprintf("%-32s %s\n", "System ID:", "[0x$oidplus_systemid_hex] ".$oidplus_systemid_int);
703
                                                        echo sprintf("%-32s %s\n", "Creation time:", "[0x$oidplus_creation_hex] ".($oidplus_creation_unknown ? 'Unknown' : date('Y-m-d', $oidplus_creation_int*24*60*60))); /**@phpstan-ignore-line*/
704
                                                        echo sprintf("%-32s %s\n", "Reserved:", "[0x$oidplus_reserved_hex]");
705
                                                        echo sprintf("%-32s %s\n", "Namespace:", "[0x$oidplus_namespace_hex] $oidplus_namespace_int=System");
706
                                                        echo sprintf("%-32s %s\n", "Data (empty string hash):", "[0x$oidplus_data_hex] SHA1('') = ????????????????????????????$oidplus_data_hex");
707
                                                }
708
                                                else if (($oidplus_namespace_int == 1) && $oidplus_creation_unknown) {
709
                                                        // User GUID, e.g. 6e932dd7-0000-8000-8001-2938f50e857e (User), 6e932dd7-0000-8000-8001-000000000000 (Admin)
710
                                                        echo "\n<u>Interpretation of <a href=\"https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md\">OIDplus 2.0 Custom UUID</a></u>\n\n";
711
                                                        echo sprintf("%-32s %s\n", "System ID:", "[0x$oidplus_systemid_hex] ".$oidplus_systemid_int);
712
                                                        echo sprintf("%-32s %s\n", "Creation time:", "[0x$oidplus_creation_hex] ".($oidplus_creation_unknown ? 'Unknown' : date('Y-m-d', $oidplus_creation_int*24*60*60)));  /**@phpstan-ignore-line*/
713
                                                        echo sprintf("%-32s %s\n", "Reserved:", "[0x$oidplus_reserved_hex]");
714
                                                        echo sprintf("%-32s %s\n", "Namespace:", "[0x$oidplus_namespace_hex] $oidplus_namespace_int=User");
715
                                                        if ($oidplus_data_int == 0) {
716
                                                                echo sprintf("%-32s %s\n", "Data (Username):", "[0x$oidplus_data_hex] 0=Admin");
717
                                                        } else {
718
                                                                echo sprintf("%-32s %s\n", "Data (Username):", "[0x$oidplus_data_hex] SHA1(UserName) = ????????????????????????????$oidplus_data_hex");
719
                                                        }
720
                                                }
721
                                                else if (($oidplus_namespace_int == 2)/* && $oidplus_creation_valid*/) {
722
                                                        // Log entry GUID, e.g. 6e932dd7-458c-8000-8002-0000000004d2
723
                                                        echo "\n<u>Interpretation of <a href=\"https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md\">OIDplus 2.0 Custom UUID</a></u>\n\n";
724
                                                        echo sprintf("%-32s %s\n", "System ID:", "[0x$oidplus_systemid_hex] ".$oidplus_systemid_int);
725
                                                        echo sprintf("%-32s %s\n", "Creation time:", "[0x$oidplus_creation_hex] ".($oidplus_creation_unknown ? 'Unknown' : date('Y-m-d', $oidplus_creation_int*24*60*60)));
726
                                                        echo sprintf("%-32s %s\n", "Reserved:", "[0x$oidplus_reserved_hex]");
727
                                                        echo sprintf("%-32s %s\n", "Namespace:", "[0x$oidplus_namespace_hex] $oidplus_namespace_int=Log Entry");
728
                                                        echo sprintf("%-32s %s\n", "Data (Sequence number):", "[0x$oidplus_data_hex] $oidplus_data_int");
729
                                                }
730
                                                else if (($oidplus_namespace_int == 3) && $oidplus_creation_unknown) {
731
                                                        // Configuration entry GUID, e.g. 6e932dd7-0000-8000-8003-f14dda42862a
732
                                                        echo "\n<u>Interpretation of <a href=\"https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md\">OIDplus 2.0 Custom UUID</a></u>\n\n";
733
                                                        echo sprintf("%-32s %s\n", "System ID:", "[0x$oidplus_systemid_hex] ".$oidplus_systemid_int);
734
                                                        echo sprintf("%-32s %s\n", "Creation time:", "[0x$oidplus_creation_hex] ".($oidplus_creation_unknown ? 'Unknown' : date('Y-m-d', $oidplus_creation_int*24*60*60))); /**@phpstan-ignore-line*/
735
                                                        echo sprintf("%-32s %s\n", "Reserved:", "[0x$oidplus_reserved_hex]");
736
                                                        echo sprintf("%-32s %s\n", "Namespace:", "[0x$oidplus_namespace_hex] $oidplus_namespace_int=Configuration Entry");
737
                                                        echo sprintf("%-32s %s\n", "Data (Setting name hash):", "[0x$oidplus_data_hex] SHA1(SettingName) = ????????????????????????????$oidplus_data_hex");
738
                                                }
739
                                                else if ($oidplus_namespace_int == 4) {
740
                                                        // ASN.1 Alpahnumeric identifier GUID, e.g. 6e932dd7-0000-8000-8004-208ded8a3f8f
741
                                                        echo "\n<u>Interpretation of <a href=\"https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md\">OIDplus 2.0 Custom UUID</a></u>\n\n";
742
                                                        echo sprintf("%-32s %s\n", "System ID:", "[0x$oidplus_systemid_hex] ".$oidplus_systemid_int);
743
                                                        echo sprintf("%-32s %s\n", "Creation time:", "[0x$oidplus_creation_hex] ".($oidplus_creation_unknown ? 'Unknown' : date('Y-m-d', $oidplus_creation_int*24*60*60)));
744
                                                        echo sprintf("%-32s %s\n", "Reserved:", "[0x$oidplus_reserved_hex]");
745
                                                        echo sprintf("%-32s %s\n", "Namespace:", "[0x$oidplus_namespace_hex] $oidplus_namespace_int=ASN.1 Alphanumeric ID");
746
                                                        $oidplus_data_24hi_hex = substr($oidplus_data_hex, 0, 6);
747
                                                        $oidplus_data_24lo_hex = substr($oidplus_data_hex, 6, 6);
748
                                                        echo sprintf("%-32s %s\n", "Data (OID hash):", "[0x$oidplus_data_24hi_hex] SHA1(OID) = ????????????????????????????$oidplus_data_24hi_hex");
749
                                                        echo sprintf("%-32s %s\n", "Data (Name hash):", "[0x$oidplus_data_24lo_hex] SHA1(AlphaNumId) = ????????????????????????????$oidplus_data_24lo_hex");
750
                                                }
751
                                                else if ($oidplus_namespace_int == 5) {
752
                                                        // Unicode Label entry GUID, e.g. 6e932dd7-0000-8000-8005-208dedaf9a96
753
                                                        echo "\n<u>Interpretation of <a href=\"https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md\">OIDplus 2.0 Custom UUID</a></u>\n\n";
754
                                                        echo sprintf("%-32s %s\n", "System ID:", "[0x$oidplus_systemid_hex] ".$oidplus_systemid_int);
755
                                                        echo sprintf("%-32s %s\n", "Creation time:", "[0x$oidplus_creation_hex] ".($oidplus_creation_unknown ? 'Unknown' : date('Y-m-d', $oidplus_creation_int*24*60*60)));
756
                                                        echo sprintf("%-32s %s\n", "Reserved:", "[0x$oidplus_reserved_hex]");
757
                                                        echo sprintf("%-32s %s\n", "Namespace:", "[0x$oidplus_namespace_hex] $oidplus_namespace_int=Unicode Label");
758
                                                        $oidplus_data_24hi_hex = substr($oidplus_data_hex, 0, 6);
759
                                                        $oidplus_data_24lo_hex = substr($oidplus_data_hex, 6, 6);
760
                                                        echo sprintf("%-32s %s\n", "Data (OID hash):", "[0x$oidplus_data_24hi_hex] SHA1(OID) = ????????????????????????????$oidplus_data_24hi_hex");
761
                                                        echo sprintf("%-32s %s\n", "Data (Name hash):", "[0x$oidplus_data_24lo_hex] SHA1(UnicodeLabel) = ????????????????????????????$oidplus_data_24lo_hex");
762
                                                }
763
                                                else if (($oidplus_namespace_int >= 6) && ($oidplus_namespace_int <= 0xF)) {
764
                                                        // System reserved
765
                                                        echo "\n<u>Interpretation of <a href=\"https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md\">OIDplus 2.0 Custom UUID</a></u>\n\n";
766
                                                        echo sprintf("%-32s %s\n", "System ID:", "[0x$oidplus_systemid_hex] ".$oidplus_systemid_int);
767
                                                        echo sprintf("%-32s %s\n", "Creation time:", "[0x$oidplus_creation_hex] ".($oidplus_creation_unknown ? 'Unknown' : date('Y-m-d', $oidplus_creation_int*24*60*60)));
768
                                                        echo sprintf("%-32s %s\n", "Reserved:", "[0x$oidplus_reserved_hex]");
769
                                                        echo sprintf("%-32s %s\n", "Namespace:", "[0x$oidplus_namespace_hex] $oidplus_namespace_int=Unknown (System Reserved)");
770
                                                        echo sprintf("%-32s %s\n", "Data (Setting name hash):", "[0x$oidplus_data_hex] Unknown");
771
                                                }
772
                                                else if ($oidplus_namespace_int > 0xF) {
773
                                                        // Information Object GUID, e.g. 6e932dd7-458c-8000-b9e9-c1e3894d1105
1336 daniel-mar 774
                                                        $known_objecttype_plugins = array(
775
                                                                // Latest list here: https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md
776
                                                                '1.3.6.1.4.1.37476.2.5.2.4.8.1' => 'doi (ViaThinkSoft plugin)',
777
                                                                '1.3.6.1.4.1.37476.2.5.2.4.8.2' => 'gs1 (ViaThinkSoft plugin)',
778
                                                                '1.3.6.1.4.1.37476.2.5.2.4.8.3' => 'guid (ViaThinkSoft plugin)',
779
                                                                '1.3.6.1.4.1.37476.2.5.2.4.8.4' => 'ipv4 (ViaThinkSoft plugin)',
780
                                                                '1.3.6.1.4.1.37476.2.5.2.4.8.5' => 'ipv6 (ViaThinkSoft plugin)',
781
                                                                '1.3.6.1.4.1.37476.2.5.2.4.8.6' => 'java (ViaThinkSoft plugin)',
782
                                                                '1.3.6.1.4.1.37476.2.5.2.4.8.7' => 'oid (ViaThinkSoft plugin)',
783
                                                                '1.3.6.1.4.1.37476.2.5.2.4.8.8' => 'other (ViaThinkSoft plugin)',
784
                                                                '1.3.6.1.4.1.37476.2.5.2.4.8.9' => 'domain (ViaThinkSoft plugin)',
785
                                                                '1.3.6.1.4.1.37476.2.5.2.4.8.10' => 'fourcc (ViaThinkSoft plugin)',
786
                                                                '1.3.6.1.4.1.37476.2.5.2.4.8.11' => 'aid (ViaThinkSoft plugin)',
787
                                                                '1.3.6.1.4.1.37476.2.5.2.4.8.12' => 'php (ViaThinkSoft plugin)',
788
                                                                '1.3.6.1.4.1.37476.2.5.2.4.8.13' => 'mac (ViaThinkSoft plugin)',
789
                                                                '1.3.6.1.4.1.37553.8.1.8.8.53354196964.27255728261' => 'circuit (Frdlweb plugin)',
790
                                                                '1.3.6.1.4.1.37476.9000.108.19361.856' => 'ns (Frdlweb plugin)',
791
                                                                '1.3.6.1.4.1.37553.8.1.8.8.53354196964.32927' => 'pen (Frdlweb plugin)',
792
                                                                '1.3.6.1.4.1.37553.8.1.8.8.53354196964.39870' => 'uri (Frdlweb plugin)',
793
                                                                '1.3.6.1.4.1.37553.8.1.8.8.53354196964.1958965295' => 'web+fan (Frdlweb plugin)'
794
                                                        );
795
                                                        $namespace_desc = 'Unknown object type';
796
                                                        foreach ($known_objecttype_plugins as $oid => $name) {
1345 daniel-mar 797
                                                                if ((hexdec(substr(sha1($oid), -4)) & 0x3fff) == $oidplus_namespace_int) $namespace_desc = "$oid = $name";
1336 daniel-mar 798
                                                        }
1345 daniel-mar 799
                                                        echo "\n<u>Interpretation of <a href=\"https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md\">OIDplus 2.0 Custom UUID</a></u>\n\n";
800
                                                        echo sprintf("%-32s %s\n", "System ID:", "[0x$oidplus_systemid_hex] ".$oidplus_systemid_int);
801
                                                        echo sprintf("%-32s %s\n", "Creation time:", "[0x$oidplus_creation_hex] ".($oidplus_creation_unknown ? 'Unknown' : date('Y-m-d', $oidplus_creation_int*24*60*60)));
802
                                                        echo sprintf("%-32s %s\n", "Reserved:", "[0x$oidplus_reserved_hex]");
803
                                                        echo sprintf("%-32s %s\n", "Namespace (Obj.type OID hash):", "[0x$oidplus_namespace_hex] $namespace_desc");
804
                                                        echo sprintf("%-32s %s\n", "Data (Object name hash):", "[0x$oidplus_data_hex] SHA1(ObjectName) = ????????????????????????????$oidplus_data_hex");
1336 daniel-mar 805
                                                }
1334 daniel-mar 806
                                        }
807
 
1345 daniel-mar 808
                                        // END: OIDplus 2.0 Custom UUID Interpretation
809
 
1324 daniel-mar 810
                                        break;
604 daniel-mar 811
                                default:
1325 daniel-mar 812
                                        echo sprintf("%-32s %s\n", "Version:", "[0x".dechex($version)."] Unknown");
604 daniel-mar 813
                                        break;
814
                        }
815
 
816
                        break;
817
                case 2:
1324 daniel-mar 818
                        // TODO: Show byte order: 00112233-4455-6677-8899-aabbccddeeff => 33 22 11 00 55 44 77 66 88 99 aa bb cc dd ee ff
819
 
820
                        // TODO: Is there any scheme in that legacy Microsoft GUIDs?
821
                        echo sprintf("%-32s %s\n", "Variant:", "[0b110] Reserved for Microsoft Corporation");
604 daniel-mar 822
                        break;
823
                case 3:
1324 daniel-mar 824
                        echo sprintf("%-32s %s\n", "Variant:", "[0b111] Reserved for future use");
604 daniel-mar 825
                        break;
826
        }
1324 daniel-mar 827
 
828
        if (!$echo) {
829
                $out = ob_get_contents();
830
                ob_end_clean();
831
                return $out;
832
        } else {
833
                return true;
834
        }
604 daniel-mar 835
}
836
 
837
function uuid_canonize($uuid) {
838
        if (!uuid_valid($uuid)) return false;
839
        return oid_to_uuid(uuid_to_oid($uuid));
840
}
841
 
842
function oid_to_uuid($oid) {
1328 daniel-mar 843
        if (!is_uuid_oid($oid,true)) return false;
604 daniel-mar 844
 
719 daniel-mar 845
        if (substr($oid,0,1) == '.') {
604 daniel-mar 846
                $oid = substr($oid, 1);
847
        }
848
        $ary = explode('.', $oid);
849
 
850
        if (!isset($ary[2])) return false;
851
 
852
        $val = $ary[2];
853
 
854
        $x = gmp_init($val, 10);
855
        $y = gmp_strval($x, 16);
856
        $y = str_pad($y, 32, "0", STR_PAD_LEFT);
857
        return substr($y,  0, 8).'-'.
858
               substr($y,  8, 4).'-'.
859
               substr($y, 12, 4).'-'.
860
               substr($y, 16, 4).'-'.
861
               substr($y, 20, 12);
862
}
863
 
864
function is_uuid_oid($oid, $only_allow_root=false) {
721 daniel-mar 865
        if (substr($oid,0,1) == '.') $oid = substr($oid, 1); // remove leading dot
604 daniel-mar 866
 
867
        $ary = explode('.', $oid);
868
 
869
        if ($only_allow_root) {
870
                if (count($ary) != 3) return false;
871
        } else {
872
                if (count($ary) < 3) return false;
873
        }
874
 
875
        if ($ary[0] != '2') return false;
876
        if ($ary[1] != '25') return false;
877
        for ($i=2; $i<count($ary); $i++) {
878
                $v = $ary[$i];
879
                if (!is_numeric($v)) return false;
880
                if ($i == 2) {
881
                        // Must be in the range of 128 bit UUID
882
                        $test = gmp_init($v, 10);
883
                        if (strlen(gmp_strval($test, 16)) > 32) return false;
884
                }
885
                if ($v < 0) return false;
886
        }
887
 
888
        return true;
889
}
890
 
891
function uuid_to_oid($uuid) {
892
        if (!uuid_valid($uuid)) return false;
893
 
894
        $uuid = str_replace(array('-', '{', '}'), '', $uuid);
895
        $x = gmp_init($uuid, 16);
1324 daniel-mar 896
        return '2.25.'.gmp_strval($x, 10);
604 daniel-mar 897
}
898
 
1324 daniel-mar 899
function uuid_numeric_value($uuid) {
900
        $oid = uuid_to_oid($uuid);
901
        if (!$oid) return false;
902
        return substr($oid, strlen('2.25.'));
903
}
904
 
905
function uuid_c_syntax($uuid) {
906
        $uuid = str_replace('{', '', $uuid);
907
        return '{ 0x' . substr($uuid, 0, 8) .
908
                ', 0x' . substr($uuid, 9, 4) .
909
                ', 0x' . substr($uuid, 14, 4) .
910
                ', { 0x' . substr($uuid, 19, 2).
911
                ', 0x' . substr($uuid, 21, 2) .
912
                ', 0x' . substr($uuid, 24, 2) .
913
                ', 0x' . substr($uuid, 26, 2) .
914
                ', 0x' . substr($uuid, 28, 2) .
915
                ', 0x' . substr($uuid, 30, 2) .
916
                ', 0x' . substr($uuid, 32, 2) .
917
                ', 0x' . substr($uuid, 34, 2) . ' } }';
918
}
919
 
920
function gen_uuid($prefer_mac_address_based = true) {
921
        $uuid = $prefer_mac_address_based ? gen_uuid_reordered()/*UUIDv6*/ : false;
922
        if ($uuid === false) $uuid = gen_uuid_unix_epoch()/*UUIDv7*/;
604 daniel-mar 923
        return $uuid;
924
}
925
 
1324 daniel-mar 926
# --------------------------------------
927
// Variant 1, Version 1 (Time based) UUID
928
# --------------------------------------
929
 
930
function gen_uuid_v1() {
931
        return gen_uuid_timebased();
932
}
933
function gen_uuid_timebased($force_php_implementation=false) {
604 daniel-mar 934
        # On Debian: apt-get install php-uuid
935
        # extension_loaded('uuid')
1324 daniel-mar 936
        if (!$force_php_implementation && function_exists('uuid_create')) {
604 daniel-mar 937
                # OSSP uuid extension like seen in php5-uuid at Debian 8
938
                /*
939
                $x = uuid_create($context);
940
                uuid_make($context, UUID_MAKE_V1);
941
                uuid_export($context, UUID_FMT_STR, $uuid);
942
                return trim($uuid);
943
                */
944
 
945
                # PECL uuid extension like seen in php-uuid at Debian 9
946
                return trim(uuid_create(UUID_TYPE_TIME));
947
        }
948
 
949
        # On Debian: apt-get install uuid-runtime
1324 daniel-mar 950
        if (!$force_php_implementation && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
604 daniel-mar 951
                $out = array();
952
                $ec = -1;
953
                exec('uuidgen -t 2>/dev/null', $out, $ec);
954
                if ($ec == 0) return trim($out[0]);
955
        }
956
 
957
        # If we hadn't any success yet, then implement the time based generation routine ourselves!
958
        # Based on https://github.com/fredriklindberg/class.uuid.php/blob/master/class.uuid.php
959
 
960
        $uuid = array(
961
                'time_low' => 0,                /* 32-bit */
962
                'time_mid' => 0,                /* 16-bit */
963
                'time_hi' => 0,                 /* 16-bit */
964
                'clock_seq_hi' => 0,            /*  8-bit */
965
                'clock_seq_low' => 0,           /*  8-bit */
966
                'node' => array()               /* 48-bit */
967
        );
968
 
969
        /*
970
         * Get current time in 100 ns intervals. The magic value
971
         * is the offset between UNIX epoch and the UUID UTC
972
         * time base October 15, 1582.
973
         */
1324 daniel-mar 974
        if (time_nanosleep(0,100) !== true) usleep(1); // Wait 100ns, to make sure that the time part changes if multiple UUIDs are generated
604 daniel-mar 975
        $tp = gettimeofday();
1324 daniel-mar 976
        if (PHP_INT_SIZE == 4) {
977
                $tp['sec'] = gmp_init($tp['sec'],10);
978
                $tp['usec'] = gmp_init($tp['usec'],10);
979
                $time = gmp_add(gmp_add(gmp_mul($tp['sec'], gmp_init('10000000',10)),gmp_mul($tp['usec'], gmp_init('10',10))),gmp_init('01B21DD213814000',16));
980
                $uuid['time_low'] = gmp_and($time, gmp_init('ffffffff',16));
981
                $high = gmp_shiftr($time,32);
982
                $uuid['time_mid'] = gmp_and($high, gmp_init('ffff',16));
1411 daniel-mar 983
                $uuid['time_hi'] = gmp_intval(gmp_and(gmp_shiftr($high,16),gmp_init('fff',16))) | (1/*TimeBased*/ << 12);
1324 daniel-mar 984
        } else {
985
                $time = ($tp['sec'] * 10000000) + ($tp['usec'] * 10) + 0x01B21DD213814000;
986
                $uuid['time_low'] = $time & 0xffffffff;
987
                /* Work around PHP 32-bit bit-operation limits */
988
                $high = intval($time / 0xffffffff);
989
                $uuid['time_mid'] = $high & 0xffff;
990
                $uuid['time_hi'] = (($high >> 16) & 0xfff) | (1/*TimeBased*/ << 12);
991
        }
604 daniel-mar 992
 
993
        /*
994
         * We don't support saved state information and generate
995
         * a random clock sequence each time.
996
         */
1324 daniel-mar 997
        $uuid['clock_seq_hi'] = _random_int(0, 255) & 0b00111111 | 0b10000000; // set variant to 0b10__ (RFC 4122)
1261 daniel-mar 998
        $uuid['clock_seq_low'] = _random_int(0, 255);
604 daniel-mar 999
 
1000
        /*
1001
         * Node should be set to the 48-bit IEEE node identifier
1002
         */
1003
        $mac = get_mac_address();
1004
        if ($mac) {
1324 daniel-mar 1005
                $node = str_replace('-','',str_replace(':','',$mac));
604 daniel-mar 1006
                for ($i = 0; $i < 6; $i++) {
1007
                        $uuid['node'][$i] = hexdec(substr($node, $i*2, 2));
1008
                }
1324 daniel-mar 1009
        } else {
1010
                // If we cannot get a MAC address, then generate a random AAI
1336 daniel-mar 1011
                // RFC 4122 requires the multicast bit to be set, to make sure
1012
                // that a UUID from a system with network card never conflicts
1013
                // with a UUID from a system without network ard.
1014
                // We are additionally defining the other 3 bits as AAI,
1015
                // to avoid that we are misusing the CID or OUI from other vendors
1016
                // if we would create multicast ELI (based on CID) or EUI (based on OUI).
1017
                $uuid['node'] = explode('-', gen_aai(48, true/*Multicast*/));
1324 daniel-mar 1018
                $uuid['node'] = array_map('hexdec', $uuid['node']);
604 daniel-mar 1019
        }
1020
 
1324 daniel-mar 1021
        /*
1022
         * Now output the UUID
1023
         */
1024
        return sprintf(
1025
                '%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x',
1026
                ($uuid['time_low']), ($uuid['time_mid']), ($uuid['time_hi']),
1027
                $uuid['clock_seq_hi'], $uuid['clock_seq_low'],
1028
                $uuid['node'][0], $uuid['node'][1], $uuid['node'][2],
1029
                $uuid['node'][3], $uuid['node'][4], $uuid['node'][5]);
604 daniel-mar 1030
}
1031
 
1324 daniel-mar 1032
# --------------------------------------
1033
// Variant 1, Version 2 (DCE Security) UUID
1034
# --------------------------------------
604 daniel-mar 1035
 
1324 daniel-mar 1036
define('DCE_DOMAIN_PERSON', 0);
1037
define('DCE_DOMAIN_GROUP', 1);
1038
define('DCE_DOMAIN_ORG', 2);
1039
function gen_uuid_v2($domain, $id) {
1040
        return gen_uuid_dce($domain, $id);
1041
}
1042
function gen_uuid_dce($domain, $id) {
1043
        if (($domain ?? '') === '') throw new Exception("Domain ID missing");
1044
        if (!is_numeric($domain)) throw new Exception("Invalid Domain ID");
1376 daniel-mar 1045
        if (($domain < 0) || ($domain > 0xFF)) throw new Exception("Domain ID must be in range 0..255");
604 daniel-mar 1046
 
1324 daniel-mar 1047
        if (($id ?? '') === '') throw new Exception("ID value missing");
1048
        if (!is_numeric($id)) throw new Exception("Invalid ID value");
1376 daniel-mar 1049
        if (($id < 0) || ($id > 0xFFFFFFFF)) throw new Exception("ID value must be in range 0..4294967295");
604 daniel-mar 1050
 
1051
        # Start with a version 1 UUID
1052
        $uuid = gen_uuid_timebased();
1053
 
1324 daniel-mar 1054
        # Add Domain Number
604 daniel-mar 1055
        $uuid = str_pad(dechex($id), 8, '0', STR_PAD_LEFT) . substr($uuid, 8);
1056
 
1324 daniel-mar 1057
        # Add Domain (this overwrites part of the clock sequence)
604 daniel-mar 1058
        $uuid = substr($uuid,0,21) . str_pad(dechex($domain), 2, '0', STR_PAD_LEFT) . substr($uuid, 23);
1059
 
1060
        # Change version to 2
1061
        $uuid[14] = '2';
1062
 
1063
        return $uuid;
1064
}
1065
 
1324 daniel-mar 1066
# --------------------------------------
1067
// Variant 1, Version 3 (MD5 name based) UUID
1068
# --------------------------------------
1069
 
1070
function gen_uuid_v3($namespace_uuid, $name) {
1071
        return gen_uuid_md5_namebased($namespace_uuid, $name);
1072
}
604 daniel-mar 1073
function gen_uuid_md5_namebased($namespace_uuid, $name) {
1324 daniel-mar 1074
        if (($namespace_uuid ?? '') === '') throw new Exception("Namespace UUID missing");
1075
        if (!uuid_valid($namespace_uuid)) throw new Exception("Invalid namespace UUID '$namespace_uuid'");
1076
 
604 daniel-mar 1077
        $namespace_uuid = uuid_canonize($namespace_uuid);
1078
        $namespace_uuid = str_replace('-', '', $namespace_uuid);
1079
        $namespace_uuid = hex2bin($namespace_uuid);
1080
 
1081
        $hash = md5($namespace_uuid.$name);
1082
        $hash[12] = '3'; // Set version: 3 = MD5
1324 daniel-mar 1083
        $hash[16] = dechex(hexdec($hash[16]) & 0b0011 | 0b1000); // Set variant to "10xx" (RFC4122)
604 daniel-mar 1084
 
1085
        return substr($hash,  0, 8).'-'.
1086
               substr($hash,  8, 4).'-'.
1087
               substr($hash, 12, 4).'-'.
1088
               substr($hash, 16, 4).'-'.
1089
               substr($hash, 20, 12);
1090
}
1091
 
1324 daniel-mar 1092
# --------------------------------------
1093
// Variant 1, Version 4 (Random) UUID
1094
# --------------------------------------
1095
 
1096
function gen_uuid_v4() {
1097
        return gen_uuid_random();
1098
}
604 daniel-mar 1099
function gen_uuid_random() {
1100
        # On Windows: Requires
1101
        #    extension_dir = "C:\php-8.0.3-nts-Win32-vs16-x64\ext"
1102
        #    extension=com_dotnet
1103
        if (function_exists('com_create_guid')) {
1325 daniel-mar 1104
                $uuid = trim(com_create_guid(), '{}');
1105
                if (uuid_version($uuid) === '4') { // <-- just to make 100% sure that Windows's CoCreateGuid() did output UUIDv4
1106
                        return strtolower($uuid);
1107
                }
604 daniel-mar 1108
        }
1109
 
1110
        # On Debian: apt-get install php-uuid
1111
        # extension_loaded('uuid')
1112
        if (function_exists('uuid_create')) {
1113
                # OSSP uuid extension like seen in php5-uuid at Debian 8
1114
                /*
1115
                $x = uuid_create($context);
1116
                uuid_make($context, UUID_MAKE_V4);
1117
                uuid_export($context, UUID_FMT_STR, $uuid);
1118
                return trim($uuid);
1119
                */
1120
 
1121
                # PECL uuid extension like seen in php-uuid at Debian 9
1122
                return trim(uuid_create(UUID_TYPE_RANDOM));
1123
        }
1124
 
1125
        if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
1411 daniel-mar 1126
                # On Debian Jessie: UUID V4 (Random)
1127
                if (file_exists($uuidv4_file = '/proc/sys/kernel/random/uuid')) {
1128
                        $uuid = file_get_contents($uuidv4_file);
1129
                        if (uuid_version($uuid) === '4') { // <-- just to make 100% sure that it did output UUIDv4
1130
                                return $uuid;
1131
                        }
1132
                }
1133
 
604 daniel-mar 1134
                # On Debian: apt-get install uuid-runtime
1135
                $out = array();
1136
                $ec = -1;
1137
                exec('uuidgen -r 2>/dev/null', $out, $ec);
1138
                if ($ec == 0) return trim($out[0]);
1139
        }
1140
 
1141
        # Make the UUID by ourselves
1325 daniel-mar 1142
 
1143
        if (function_exists('openssl_random_pseudo_bytes')) {
1144
                // Source: https://www.php.net/manual/en/function.com-create-guid.php#119168
1145
                $data = openssl_random_pseudo_bytes(16);
1146
                $data[6] = chr(ord($data[6]) & 0x0f | 0x40);    // set version to 0100
1147
                $data[8] = chr(ord($data[8]) & 0x3f | 0x80);    // set bits 6-7 to 10
1148
                return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
1149
        } else {
1150
                // Source: http://rogerstringer.com/2013/11/15/generate-uuids-php
1151
                return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
1152
                        _random_int( 0, 0xffff ), _random_int( 0, 0xffff ),
1153
                        _random_int( 0, 0xffff ),
1154
                        _random_int( 0, 0x0fff ) | 0x4000,
1155
                        _random_int( 0, 0x3fff ) | 0x8000,
1156
                        _random_int( 0, 0xffff ), _random_int( 0, 0xffff ), _random_int( 0, 0xffff )
1157
                );
1158
        }
604 daniel-mar 1159
}
1160
 
1324 daniel-mar 1161
# --------------------------------------
1162
// Variant 1, Version 5 (SHA1 name based) UUID
1163
# --------------------------------------
1164
 
1165
function gen_uuid_v5($namespace_uuid, $name) {
1166
        return gen_uuid_sha1_namebased($namespace_uuid, $name);
1167
}
604 daniel-mar 1168
function gen_uuid_sha1_namebased($namespace_uuid, $name) {
1324 daniel-mar 1169
        if (($namespace_uuid ?? '') === '') throw new Exception("Namespace UUID missing");
1170
        if (!uuid_valid($namespace_uuid)) throw new Exception("Invalid namespace UUID '$namespace_uuid'");
1171
 
604 daniel-mar 1172
        $namespace_uuid = str_replace('-', '', $namespace_uuid);
1173
        $namespace_uuid = hex2bin($namespace_uuid);
1174
 
1175
        $hash = sha1($namespace_uuid.$name);
1176
        $hash[12] = '5'; // Set version: 5 = SHA1
1324 daniel-mar 1177
        $hash[16] = dechex(hexdec($hash[16]) & 0b0011 | 0b1000); // Set variant to "0b10__" (RFC4122/DCE1.1)
604 daniel-mar 1178
 
1179
        return substr($hash,  0, 8).'-'.
1180
               substr($hash,  8, 4).'-'.
1181
               substr($hash, 12, 4).'-'.
1182
               substr($hash, 16, 4).'-'.
1183
               substr($hash, 20, 12);
1184
}
1185
 
1324 daniel-mar 1186
# --------------------------------------
1187
// Variant 1, Version 6 (Reordered) UUID
1188
# --------------------------------------
1189
 
1190
function gen_uuid_v6() {
1191
        return gen_uuid_reordered();
604 daniel-mar 1192
}
1324 daniel-mar 1193
function gen_uuid_reordered() {
1194
        // Start with a UUIDv1
1195
        $uuid = gen_uuid_timebased();
604 daniel-mar 1196
 
1324 daniel-mar 1197
        // Convert to UUIDv6
1198
        return uuid1_to_uuid6($uuid);
604 daniel-mar 1199
}
1324 daniel-mar 1200
function uuid6_to_uuid1($hex) {
1201
        $hex = uuid_canonize($hex);
1202
        if ($hex === false) return false;
1203
        $hex = preg_replace('@[^0-9A-F]@i', '', $hex);
1204
        $hex = substr($hex, 7, 5).
1205
               substr($hex, 13, 3).
1206
               substr($hex, 3, 4).
1207
               '1' . substr($hex, 0, 3).
1208
               substr($hex, 16);
1209
        return substr($hex,  0, 8).'-'.
1210
               substr($hex,  8, 4).'-'.
1211
               substr($hex, 12, 4).'-'.
1212
               substr($hex, 16, 4).'-'.
1213
               substr($hex, 20, 12);
1214
}
1215
function uuid1_to_uuid6($hex) {
1216
        $hex = uuid_canonize($hex);
1217
        if ($hex === false) return false;
1218
        $hex = preg_replace('@[^0-9A-F]@i', '', $hex);
1219
        $hex = substr($hex, 13, 3).
1220
               substr($hex, 8, 4).
1221
               substr($hex, 0, 5).
1222
               '6' . substr($hex, 5, 3).
1223
               substr($hex, 16);
1224
        return substr($hex,  0, 8).'-'.
1225
               substr($hex,  8, 4).'-'.
1226
               substr($hex, 12, 4).'-'.
1227
               substr($hex, 16, 4).'-'.
1228
               substr($hex, 20, 12);
1229
}
604 daniel-mar 1230
 
1324 daniel-mar 1231
# --------------------------------------
1232
// Variant 1, Version 7 (Unix Epoch) UUID
1233
# --------------------------------------
604 daniel-mar 1234
 
1411 daniel-mar 1235
function gen_uuid_v7(int $num_ms_frac_bits=12) {
1236
        return gen_uuid_unix_epoch($num_ms_frac_bits);
1324 daniel-mar 1237
}
1411 daniel-mar 1238
function gen_uuid_unix_epoch(int $num_ms_frac_bits=12) {
1239
        $uuid_nibbles = '';
1324 daniel-mar 1240
 
1411 daniel-mar 1241
        // Add the timestamp (milliseconds Unix)
1324 daniel-mar 1242
        if (function_exists('gmp_init')) {
1243
                list($ms,$sec) = explode(' ', microtime(false));
1244
                $sec = gmp_init($sec, 10);
1245
                $ms = gmp_init(substr($ms,2,3), 10);
1246
                $unix_ts = gmp_strval(gmp_add(gmp_mul($sec, '1000'), $ms),16);
1247
        } else {
1411 daniel-mar 1248
                $unix_ts = dechex((int)ceil(microtime(true)*1000));
1324 daniel-mar 1249
        }
1250
        $unix_ts = str_pad($unix_ts, 12, '0', STR_PAD_LEFT);
1411 daniel-mar 1251
        $uuid_nibbles = $unix_ts;
1324 daniel-mar 1252
 
1411 daniel-mar 1253
        // Version = 7
1254
        $uuid_nibbles .= '7';
1324 daniel-mar 1255
 
1411 daniel-mar 1256
        // Optional: millisecond fraction (max 12 bits)
1257
        if (($num_ms_frac_bits < 0) || ($num_ms_frac_bits > 12)) throw new Exception("Invalid msec frac bits (must be 0..12)");
1258
        $resolution_ns = 1000000 / pow(2,$num_ms_frac_bits);
1259
        if ($num_ms_frac_bits > 0) {
1260
                $seconds_fraction = (float)explode(' ',microtime(false))[0]; // <sec=0>,<msec>
1261
 
1262
                $ms_fraction = $seconds_fraction * 1000; // <msec>,<us>
1263
                $ms_fraction -= floor($ms_fraction); // <msec=0>,<us>
1264
 
1265
                $ns_fraction = $ms_fraction * 1000000; // <ns>
1266
                $val = (int)ceil($ns_fraction / $resolution_ns);
1267
 
1268
                // Currently, for the output we only allow frac bits 0, 4, 8, 12 (0-3 nibbles),
1269
                // since UUIDs are usually sorted in their hex notation, and one of the main
1270
                // reasons for using the sub-millisecond fractions it to increase monotonicity
1271
                $num_nibbles = (int)ceil($num_ms_frac_bits/4);
1272
                $uuid_nibbles .= str_pad(dechex($val), $num_nibbles, '0', STR_PAD_LEFT);
1273
        }
1274
 
1275
        // TODO Not implemented: Optional counter (to be defined as parameter to this method)
1276
        // The counter bits need to be spread before and after the variant bits
1277
 
1278
        // Fill with random bits (and the variant bits)
1279
        $uuid = gen_uuid_random();
1280
        $uuid = str_replace('-', '', $uuid);
1281
        for ($i=0; $i<strlen($uuid_nibbles); $i++) $uuid[$i] = $uuid_nibbles[$i];
1282
 
1283
        // Wait to make sure that the time part changes if multiple UUIDs are generated
1284
        if (time_nanosleep(0,(int)ceil($resolution_ns)) !== true) usleep((int)ceil($resolution_ns/1000));
1285
 
1286
        // Output
1287
        return substr($uuid,  0, 8).'-'.
1288
               substr($uuid,  8, 4).'-'.
1289
               substr($uuid, 12, 4).'-'.
1290
               substr($uuid, 16, 4).'-'.
1291
               substr($uuid, 20, 12);
1324 daniel-mar 1292
}
1293
 
1294
# --------------------------------------
1295
// Variant 1, Version 8 (Custom) UUID
1296
# --------------------------------------
1297
 
1298
function gen_uuid_v8($block1_32bit, $block2_16bit, $block3_12bit, $block4_14bit, $block5_48bit) {
1299
        return gen_uuid_custom($block1_32bit, $block2_16bit, $block3_12bit, $block4_14bit, $block5_48bit);
1300
}
1301
function gen_uuid_custom($block1_32bit, $block2_16bit, $block3_12bit, $block4_14bit, $block5_48bit) {
1302
        if (preg_replace('@[0-9A-F]@i', '', $block1_32bit) != '') throw new Exception("Invalid data for block 1. Must be hex input");
1303
        if (preg_replace('@[0-9A-F]@i', '', $block2_16bit) != '') throw new Exception("Invalid data for block 2. Must be hex input");
1304
        if (preg_replace('@[0-9A-F]@i', '', $block3_12bit) != '') throw new Exception("Invalid data for block 3. Must be hex input");
1305
        if (preg_replace('@[0-9A-F]@i', '', $block4_14bit) != '') throw new Exception("Invalid data for block 4. Must be hex input");
1306
        if (preg_replace('@[0-9A-F]@i', '', $block5_48bit) != '') throw new Exception("Invalid data for block 5. Must be hex input");
1307
 
1308
        $block1 = str_pad(substr($block1_32bit, -8),  8, '0', STR_PAD_LEFT);
1309
        $block2 = str_pad(substr($block2_16bit, -4),  4, '0', STR_PAD_LEFT);
1310
        $block3 = str_pad(substr($block3_12bit, -4),  4, '0', STR_PAD_LEFT);
1311
        $block4 = str_pad(substr($block4_14bit, -4),  4, '0', STR_PAD_LEFT);
1312
        $block5 = str_pad(substr($block5_48bit,-12), 12, '0', STR_PAD_LEFT);
1313
 
1314
        $block3[0] = '8'; // Version 8 = Custom
1315
        $block4[0] = dechex(hexdec($block4[0]) & 0b0011 | 0b1000); // Variant 0b10__ = RFC4122
1316
 
1317
        return strtolower($block1.'-'.$block2.'-'.$block3.'-'.$block4.'-'.$block5);
1318
}
1319
 
1411 daniel-mar 1320
function gen_uuid_v8_namebased($hash_algo, $namespace_uuid, $name) {
1321
        if (($hash_algo ?? '') === '') throw new Exception("Hash algorithm argument missing");
1376 daniel-mar 1322
 
1323
        if (($namespace_uuid ?? '') === '') throw new Exception("Namespace UUID missing");
1324
        if (!uuid_valid($namespace_uuid)) throw new Exception("Invalid namespace UUID '$namespace_uuid'");
1325
 
1411 daniel-mar 1326
        $uuid1 = uuid_valid($hash_algo) ? hex2bin(str_replace('-','',uuid_canonize($hash_algo))) : '';
1376 daniel-mar 1327
        $uuid2 = hex2bin(str_replace('-','',uuid_canonize($namespace_uuid)));
1328
        $payload = $uuid1 . $uuid2 . $name;
1329
 
1411 daniel-mar 1330
        if (uuid_valid($hash_algo)) {
1331
                foreach (get_uuidv8_hash_space_ids() as list($algo,$space,$friendlyName,$author,$available)) {
1332
                        if (uuid_equal($hash_algo,$space)) {
1333
                                if (!$available) {
1334
                                        throw new Exception("Algorithm $algo is not available on this system (PHP version too old)");
1335
                                }
1336
                                $hash_algo = $algo;
1337
                                break;
1376 daniel-mar 1338
                        }
1339
                }
1340
        }
1341
 
1411 daniel-mar 1342
        if ($hash_algo == 'shake128') $hash = shake128($payload, 16/*min. required bytes*/, false);
1343
        else if ($hash_algo == 'shake256') $hash = shake256($payload, 16/*min. required bytes*/, false);
1344
        else $hash = hash($hash_algo, $payload, false);
1345
 
1376 daniel-mar 1346
        if ($hash == null) {
1411 daniel-mar 1347
                throw new Exception("Unknown Hash Algorithm $hash_algo");
1376 daniel-mar 1348
        }
1349
 
1411 daniel-mar 1350
        $hash = str_pad($hash, 32, '0', STR_PAD_RIGHT); // fill short hashes with zeros to the right
1351
 
1376 daniel-mar 1352
        $hash[12] = '8'; // Set version: 8 = Custom
1353
        $hash[16] = dechex(hexdec($hash[16]) & 0b0011 | 0b1000); // Set variant to "0b10__" (RFC4122/DCE1.1)
1354
 
1355
        return substr($hash,  0, 8).'-'.
1356
               substr($hash,  8, 4).'-'.
1357
               substr($hash, 12, 4).'-'.
1358
               substr($hash, 16, 4).'-'.
1359
               substr($hash, 20, 12);
1360
}
1361
 
1362
/**
1363
 * Collection of Namebased UUIDv8 Hash Space IDs
1411 daniel-mar 1364
 * @return array An array containing tuples of [PHP Algo Name, Hash Space UUID, Human friendly name, Hash Space Author, Available]
1376 daniel-mar 1365
 */
1366
function get_uuidv8_hash_space_ids(): array {
1367
        $out = array();
1368
 
1407 daniel-mar 1369
        // The following Hash Space UUIDs are defined in draft-ietf-uuidrev-rfc4122bis-11 as Example for Namebased UUIDv8
1370
        $category = 'Internet Draft 11, Appendix B';
1411 daniel-mar 1371
        $out[] = ['sha224', '59031ca3-fbdb-47fb-9f6c-0f30e2e83145', 'SHA-224', $category, PHP_VERSION_ID >= 70100];
1372
        $out[] = ['sha256', '3fb32780-953c-4464-9cfd-e85dbbe9843d', 'SHA-256', $category, PHP_VERSION_ID >= 70100];
1373
        $out[] = ['sha384', 'e6800581-f333-484b-8778-601ff2b58da8', 'SHA-384', $category, PHP_VERSION_ID >= 70100];
1374
        $out[] = ['sha512', '0fde22f2-e7ba-4fd1-9753-9c2ea88fa3f9', 'SHA-512', $category, PHP_VERSION_ID >= 70100];
1375
        $out[] = ['sha512/224', '003c2038-c4fe-4b95-a672-0c26c1b79542', 'SHA-512/224', $category, PHP_VERSION_ID >= 70100];
1376
        $out[] = ['sha512/256', '9475ad00-3769-4c07-9642-5e7383732306', 'SHA-512/256', $category, PHP_VERSION_ID >= 70100];
1377
        $out[] = ['sha3-224', '9768761f-ac5a-419e-a180-7ca239e8025a', 'SHA3-224', $category, PHP_VERSION_ID >= 70100];
1378
        $out[] = ['sha3-256', '2034d66b-4047-4553-8f80-70e593176877', 'SHA3-256', $category, PHP_VERSION_ID >= 70100];
1379
        $out[] = ['sha3-384', '872fb339-2636-4bdd-bda6-b6dc2a82b1b3', 'SHA3-384', $category, PHP_VERSION_ID >= 70100];
1380
        $out[] = ['sha3-512', 'a4920a5d-a8a6-426c-8d14-a6cafbe64c7b', 'SHA3-512', $category, PHP_VERSION_ID >= 70100];
1381
        $out[] = ['shake128'/*Currently no PHP core algorithm!*/, '7ea218f6-629a-425f-9f88-7439d63296bb', 'SHAKE128', $category, file_exists(__DIR__.'/SHA3.php')];
1382
        $out[] = ['shake256'/*Currently no PHP core algorithm!*/, '2e7fc6a4-2919-4edc-b0ba-7d7062ce4f0a', 'SHAKE256', $category, file_exists(__DIR__.'/SHA3.php')];
1376 daniel-mar 1383
 
1384
        return $out;
1385
}
1386
 
1324 daniel-mar 1387
# --------------------------------------
1388
 
604 daniel-mar 1389
// http://php.net/manual/de/function.hex2bin.php#113057
1324 daniel-mar 1390
if (!function_exists('hex2bin')) {
1391
    function hex2bin($str) {
604 daniel-mar 1392
        $sbin = "";
1324 daniel-mar 1393
        $len = strlen($str);
604 daniel-mar 1394
        for ( $i = 0; $i < $len; $i += 2 ) {
1324 daniel-mar 1395
            $sbin .= pack("H*", substr($str, $i, 2));
604 daniel-mar 1396
        }
1397
        return $sbin;
1398
    }
1399
}
1323 daniel-mar 1400
 
1324 daniel-mar 1401
// https://stackoverflow.com/questions/72127764/shift-right-left-bitwise-operators-in-php7-gmp-extension
1402
if (!function_exists('gmp_shiftl')) {
1403
    function gmp_shiftl($x,$n) { // shift left
1404
        return(gmp_mul($x,gmp_pow(2,$n)));
1405
    }
1323 daniel-mar 1406
}
1407
 
1324 daniel-mar 1408
if (!function_exists('gmp_shiftr')) {
1409
    function gmp_shiftr($x,$n) { // shift right
1410
        return(gmp_div_q($x,gmp_pow(2,$n)));
1411
    }
1323 daniel-mar 1412
}
1376 daniel-mar 1413
 
1414
function shake128(string $msg, int $outputLength=512, bool $binary=false): string {
1415
        include_once __DIR__.'/SHA3.php';
1416
        $sponge = SHA3::init(SHA3::SHAKE128);
1417
        $sponge->absorb($msg);
1418
        $bin = $sponge->squeeze($outputLength);
1419
        return $binary ? $bin : bin2hex($bin);
1420
}
1421
 
1422
function shake256(string $msg, int $outputLength=512, bool $binary=false): string {
1423
        include_once __DIR__.'/SHA3.php';
1424
        $sponge = SHA3::init(SHA3::SHAKE256);
1425
        $sponge->absorb($msg);
1426
        $bin = $sponge->squeeze($outputLength);
1427
        return $binary ? $bin : bin2hex($bin);
1428
}