Subversion Repositories uuid_mac_utils

Rev

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

Rev Author Line No. Line
2 daniel-mar 1
<?php
2
 
3
/*
4
 * UUID utils for PHP
15 daniel-mar 5
 * Copyright 2011 - 2023 Daniel Marschall, ViaThinkSoft
73 daniel-mar 6
 * Version 2023-09-24
2 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)
38 daniel-mar 22
// TODO: If we are on 64 bit PHP (PHP_INT_SIZE > 4), then replace GMP with normal PHP operations
2 daniel-mar 23
 
45 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;
42 daniel-mar 26
 
45 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;
2 daniel-mar 29
 
47 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;
45 daniel-mar 32
 
64 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';
31 daniel-mar 35
const UUID_NAMEBASED_NS_URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
36
const UUID_NAMEBASED_NS_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8';
64 daniel-mar 37
const UUID_NAMEBASED_NS_X500_DN = '6ba7b814-9dad-11d1-80b4-00c04fd430c8';
2 daniel-mar 38
 
41 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
                }
24 daniel-mar 47
        }
48
}
49
 
2 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
 
29 daniel-mar 57
        $uuid = preg_replace('@[0-9A-F]@i', '', $uuid);
2 daniel-mar 58
 
59
        return ($uuid == '');
60
}
61
 
59 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
 
46 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
 
28 daniel-mar 76
function uuid_info($uuid, $echo=true) {
2 daniel-mar 77
        if (!uuid_valid($uuid)) return false;
78
 
45 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";
48 daniel-mar 95
        echo "<u>Interpretation of the UUID:</u>\n\n";
45 daniel-mar 96
 
28 daniel-mar 97
        if (!$echo) ob_start();
98
 
2 daniel-mar 99
        #$uuid = trim($uuid);
100
        # $uuid = str_replace(array('-', '{', '}'), '', $uuid);
29 daniel-mar 101
        $uuid = strtolower($uuid);
102
        $uuid = preg_replace('@[^0-9A-F]@i', '', $uuid);
2 daniel-mar 103
 
104
        $x = hexdec(substr($uuid, 16, 1));
28 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;
2 daniel-mar 109
        else $variant = -1; // should not happen
110
 
35 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
 
2 daniel-mar 120
        switch ($variant) {
121
                case 0:
33 daniel-mar 122
                        echo sprintf("%-32s %s\n", "Variant:", "[0b0__] Network Computing System (NCS)");
2 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
 
28 daniel-mar 138
                        /*
139
                        Variant 0 UUID
140
                        - 32 bit High Time
141
                        - 16 bit Low Time
142
                        - 16 bit Reserved
29 daniel-mar 143
                        -  1 bit Variant (fix 0b0)
28 daniel-mar 144
                        -  7 bit Family
145
                        - 56 bit Node
146
                        */
147
 
2 daniel-mar 148
                        // Example of an UUID: 333a2276-0000-0000-0d00-00809c000000
149
 
35 daniel-mar 150
                        // TODO: also show legacy format, e.g. 458487b55160.02.c0.64.02.03.00.00.00
151
 
28 daniel-mar 152
                        # see also some notes at See https://github.com/cjsv/uuid/blob/master/Doc
2 daniel-mar 153
 
29 daniel-mar 154
                        /*
155
                        NOTE: A generator is not possible, because there are no timestamps left!
156
                        The last possible timestamp was:
30 daniel-mar 157
                            [0xFFFFFFFFFFFF] 2015-09-05 05:58:26'210655 GMT
29 daniel-mar 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
                        */
27 daniel-mar 163
 
2 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);
30 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';
25 daniel-mar 176
                        echo sprintf("%-32s %s\n", "Timestamp:", "[0x$timestamp] $ts");
2 daniel-mar 177
 
178
                        $reserved = substr($uuid, 12, 4);
27 daniel-mar 179
                        echo sprintf("%-32s %s\n", "Reserved:", "[0x$reserved]");
2 daniel-mar 180
 
181
                        $family_hex = substr($uuid, 16, 2);
182
                        $family_dec = hexdec($family_hex);
28 daniel-mar 183
                        $nodeid_hex = substr($uuid, 18, 14);
184
                        $nodeid_dec = hexdec($nodeid_hex);
37 daniel-mar 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
 
36 daniel-mar 191
                        if ($family_dec == 0) {
37 daniel-mar 192
                                # Microsoft's AdressFamily: Unspecified 0       Unspecified address family.
193
                                # AIX 3.0 Manual:  0   unspec = Unspecified
194
                                $family_name = 'socket_$unspec (Unspecified)';
36 daniel-mar 195
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
196
                        }
197
                        else if ($family_dec == 1) {
37 daniel-mar 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)';
36 daniel-mar 201
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
202
                        }
203
                        else if ($family_dec == 2) {
37 daniel-mar 204
                                # Microsoft's AdressFamily: InterNetwork        2       Address for IP version 4.
205
                                # AIX 3.0 Manual:  2   ip = Internet Protocols
40 daniel-mar 206
                                $family_name = 'socket_$internet (Internet Protocols, e.g. IPv4)';
28 daniel-mar 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";
36 daniel-mar 223
                        }
224
                        else if ($family_dec == 3) {
37 daniel-mar 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)';
36 daniel-mar 228
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
229
                        }
230
                        else if ($family_dec == 4) {
37 daniel-mar 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)';
36 daniel-mar 234
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
235
                        }
236
                        else if ($family_dec == 5) {
37 daniel-mar 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)';
36 daniel-mar 240
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
241
                        }
242
                        else if ($family_dec == 6) {
37 daniel-mar 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)';
36 daniel-mar 247
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
248
                        }
249
                        else if ($family_dec == 7) {
37 daniel-mar 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)';
36 daniel-mar 254
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
255
                        }
256
                        else if ($family_dec == 8) {
37 daniel-mar 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)';
36 daniel-mar 260
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
261
                        }
262
                        else if ($family_dec == 9) {
37 daniel-mar 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)';
36 daniel-mar 266
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
267
                        }
268
                        else if ($family_dec == 10) {
37 daniel-mar 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)';
36 daniel-mar 272
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
273
                        }
274
                        else if ($family_dec == 11) {
37 daniel-mar 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)';
36 daniel-mar 278
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
279
                        }
280
                        else if ($family_dec == 12) {
37 daniel-mar 281
                                # Microsoft's AdressFamily: DecNet      12      DECnet address.
282
                                # AIX 3.0 Manual:  C   unspec2 = Unspecified
283
                                $family_name = 'socket_$unspec2 (Unspecified)';
36 daniel-mar 284
                                $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
285
                        }
286
                        else if ($family_dec == 13) {
37 daniel-mar 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)';
28 daniel-mar 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";
2 daniel-mar 309
                        } else {
29 daniel-mar 310
                                $family_name = "Unknown (Family $family_dec)"; # There are probably no more families
311
                                $nodeid_desc = "Unknown";
2 daniel-mar 312
                        }
29 daniel-mar 313
                        echo sprintf("%-32s %s\n", "Family:", "[0x$family_hex] $family_name");
2 daniel-mar 314
 
28 daniel-mar 315
                        echo sprintf("%-32s %s\n", "Node ID:", "[0x$nodeid_hex] $nodeid_desc");
2 daniel-mar 316
 
317
                        break;
318
                case 1:
35 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
320
 
30 daniel-mar 321
                        $version = hexdec(substr($uuid, 12, 1));
2 daniel-mar 322
 
30 daniel-mar 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)) {
66 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
30 daniel-mar 329
                        } else {
66 daniel-mar 330
                                echo sprintf("%-32s %s\n", "Variant:", "[0b10_] Unknown RFC");
30 daniel-mar 331
                        }
332
 
2 daniel-mar 333
                        switch ($version) {
29 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)
35 daniel-mar 341
                                        -  6 bit Clock Sequence High
342
                                        -  8 bit Clock Sequence Low
29 daniel-mar 343
                                        - 48 bit MAC Address
344
                                        */
47 daniel-mar 345
                                        echo sprintf("%-32s %s\n", "Version:", "[0x6] Reordered Time-Based");
29 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 */
2 daniel-mar 355
                                case 1:
27 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
28 daniel-mar 362
                                        -  2 bit Variant (fix 0b10)
35 daniel-mar 363
                                        -  6 bit Clock Sequence High
364
                                        -  8 bit Clock Sequence Low
27 daniel-mar 365
                                        - 48 bit MAC Address
366
                                        */
367
 
47 daniel-mar 368
                                        if ($version == 1) echo sprintf("%-32s %s\n", "Version:", "[0x1] Time-based with unique host identifier");
2 daniel-mar 369
 
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);
30 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';
25 daniel-mar 380
                                        echo sprintf("%-32s %s\n", "Timestamp:", "[0x$timestamp] $ts");
2 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);
44 daniel-mar 385
                                        $hex = '<abbr title="The highest 2 bits are used by the UUID variant (10xx)">'.$hex[0].'</abbr>'.substr($hex,1);
25 daniel-mar 386
                                        echo sprintf("%-32s %s\n", "Clock ID:", "[0x$hex] $dec");
2 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);
25 daniel-mar 392
                                                if ($i != 5) $nodeid .= '-';
2 daniel-mar 393
                                        }
30 daniel-mar 394
                                        $nodeid = strtoupper($nodeid);
27 daniel-mar 395
                                        echo sprintf("%-32s %s\n", "Node ID:", "[0x$x] $nodeid");
2 daniel-mar 396
 
43 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";
42 daniel-mar 398
                                        decode_mac(strtoupper($nodeid));
2 daniel-mar 399
 
400
                                        break;
401
                                case 2:
27 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
28 daniel-mar 408
                                        -  2 bit Variant (fix 0b10)
35 daniel-mar 409
                                        -  6 bit Clock Sequence
28 daniel-mar 410
                                        -  8 bit Local Domain
27 daniel-mar 411
                                        - 48 bit MAC Address
412
                                        */
413
 
28 daniel-mar 414
                                        // see also https://unicorn-utterances.com/posts/what-happened-to-uuid-v2
415
 
47 daniel-mar 416
                                        echo sprintf("%-32s %s\n", "Version:", "[0x2] DCE Security version");
2 daniel-mar 417
 
27 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);
43 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)';
27 daniel-mar 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");
425
 
2 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);
29 daniel-mar 428
                                        $dec = hexdec($x);
429
                                        echo sprintf("%-32s %s\n", "Local Domain Number:", "[0x$x] $dec");
2 daniel-mar 430
 
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);
30 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';
2 daniel-mar 441
 
442
                                        $timestamp = substr($uuid, 13, 3).substr($uuid, 8, 4).'FFFFFFFF';
443
                                        $ts = gmp_init($timestamp, 16);
30 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';
2 daniel-mar 450
 
29 daniel-mar 451
                                        $timestamp = substr($uuid, 13, 3).substr($uuid, 8, 4)/*.'xxxxxxxx'*/;
25 daniel-mar 452
                                        echo sprintf("%-32s %s\n", "Timestamp:", "[0x$timestamp] $ts_min - $ts_max");
2 daniel-mar 453
 
28 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);
44 daniel-mar 457
                                        $hex = '<abbr title="The highest 2 bits are used by the UUID variant (10xx)">'.$hex[0].'</abbr>'.substr($hex,1);
27 daniel-mar 458
                                        echo sprintf("%-32s %s\n", "Clock ID:", "[0x$hex] $dec");
2 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);
25 daniel-mar 464
                                                if ($i != 5) $nodeid .= '-';
2 daniel-mar 465
                                        }
30 daniel-mar 466
                                        $nodeid = strtoupper($nodeid);
27 daniel-mar 467
                                        echo sprintf("%-32s %s\n", "Node ID:", "[0x$x] $nodeid");
2 daniel-mar 468
 
43 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";
42 daniel-mar 470
                                        decode_mac(strtoupper($nodeid));
2 daniel-mar 471
 
472
                                        break;
473
                                case 3:
28 daniel-mar 474
                                        /*
475
                                        Variant 1, Version 3 UUID
476
                                        - 48 bit Hash High
29 daniel-mar 477
                                        -  4 bit Version (fix 0x3)
28 daniel-mar 478
                                        - 12 bit Hash Mid
479
                                        -  2 bit Variant (fix 0b10)
480
                                        - 62 bit Hash Low
481
                                        */
482
 
47 daniel-mar 483
                                        echo sprintf("%-32s %s\n", "Version:", "[0x3] Name-based (MD5 hash)");
2 daniel-mar 484
 
485
                                        $hash = str_replace('-', '', strtolower($uuid));
27 daniel-mar 486
 
2 daniel-mar 487
                                        $hash[12] = '?'; // was overwritten by version
27 daniel-mar 488
 
38 daniel-mar 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));
2 daniel-mar 493
                                        $hash[16] = '?'; // was partially overwritten by variant
494
 
43 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);
27 daniel-mar 497
                                        echo sprintf("%-32s %s\n", "MD5(Namespace+Subject):", "[0x$hash]");
2 daniel-mar 498
 
499
                                        break;
500
                                case 4:
28 daniel-mar 501
                                        /*
502
                                        Variant 1, Version 4 UUID
503
                                        - 48 bit Random High
29 daniel-mar 504
                                        -  4 bit Version (fix 0x4)
28 daniel-mar 505
                                        - 12 bit Random Mid
506
                                        -  2 bit Variant (fix 0b10)
507
                                        - 62 bit Random Low
508
                                        */
509
 
47 daniel-mar 510
                                        echo sprintf("%-32s %s\n", "Version:", "[0x4] Random");
2 daniel-mar 511
 
25 daniel-mar 512
                                        $rand_line1 = '';
513
                                        $rand_line2 = '';
2 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) {
25 daniel-mar 519
                                                        // was overwritten by version
520
                                                        $bin[0] = '?';
521
                                                        $bin[1] = '?';
522
                                                        $bin[2] = '?';
523
                                                        $bin[3] = '?';
2 daniel-mar 524
                                                } else if ($i == 8) {
25 daniel-mar 525
                                                        // was partially overwritten by variant
526
                                                        $bin[0] = '?';
527
                                                        $bin[1] = '?';
2 daniel-mar 528
                                                }
529
 
25 daniel-mar 530
                                                if ($i<8) $rand_line1 .= "$bin ";
531
                                                if ($i>=8) $rand_line2 .= "$bin ";
2 daniel-mar 532
                                        }
25 daniel-mar 533
                                        echo sprintf("%-32s %s\n", "Random bits:", trim($rand_line1));
534
                                        echo sprintf("%-32s %s\n", "",             trim($rand_line2));
2 daniel-mar 535
 
27 daniel-mar 536
                                        $rand_bytes = str_replace('-', '', strtolower($uuid));
537
                                        $rand_bytes[12] = '?'; // was overwritten by version
38 daniel-mar 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));
27 daniel-mar 542
                                        $rand_bytes[16] = '?'; // was partially overwritten by variant
43 daniel-mar 543
 
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);
27 daniel-mar 546
                                        echo sprintf("%-32s %s\n", "Random bytes:", "[0x$rand_bytes]");
547
 
2 daniel-mar 548
                                        break;
549
                                case 5:
28 daniel-mar 550
                                        /*
551
                                        Variant 1, Version 5 UUID
552
                                        - 48 bit Hash High
29 daniel-mar 553
                                        -  4 bit Version (fix 0x5)
28 daniel-mar 554
                                        - 12 bit Hash Mid
555
                                        -  2 bit Variant (fix 0b10)
556
                                        - 62 bit Hash Low
557
                                        */
558
 
47 daniel-mar 559
                                        echo sprintf("%-32s %s\n", "Version:", "[0x5] Name-based (SHA-1 hash)");
2 daniel-mar 560
 
561
                                        $hash = str_replace('-', '', strtolower($uuid));
27 daniel-mar 562
 
2 daniel-mar 563
                                        $hash[12] = '?'; // was overwritten by version
27 daniel-mar 564
 
38 daniel-mar 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));
2 daniel-mar 569
                                        $hash[16] = '?'; // was partially overwritten by variant
27 daniel-mar 570
 
2 daniel-mar 571
                                        $hash .= '????????'; // was cut off
572
 
43 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);
27 daniel-mar 575
                                        echo sprintf("%-32s %s\n", "SHA1(Namespace+Subject):", "[0x$hash]");
2 daniel-mar 576
 
577
                                        break;
27 daniel-mar 578
                                case 7:
29 daniel-mar 579
                                        /*
580
                                        Variant 1, Version 7 UUID
581
                                        - 48 bit Unix Time in milliseconds
582
                                        -  4 bit Version (fix 0x7)
70 daniel-mar 583
                                        - 12 bit Data
29 daniel-mar 584
                                        -  2 bit Variant (fix 0b10)
70 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
29 daniel-mar 594
                                        */
595
 
47 daniel-mar 596
                                        echo sprintf("%-32s %s\n", "Version:", "[0x7] Unix Epoch Time");
29 daniel-mar 597
 
598
                                        $timestamp = substr($uuid, 0, 12);
30 daniel-mar 599
 
600
                                        // Timestamp: Split into seconds and milliseconds
29 daniel-mar 601
                                        $ts = gmp_init($timestamp, 16);
30 daniel-mar 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';
29 daniel-mar 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);
38 daniel-mar 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));
29 daniel-mar 635
                                        $rand_bytes[3] = '?'; // was partially overwritten by variant
43 daniel-mar 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);
29 daniel-mar 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
 
27 daniel-mar 645
                                        break;
646
                                case 8:
29 daniel-mar 647
                                        /*
648
                                        Variant 1, Version 8 UUID
63 daniel-mar 649
                                        - 48 bit Custom data [Block 1+2]
29 daniel-mar 650
                                        -  4 bit Version (fix 0x8)
63 daniel-mar 651
                                        - 12 bit Custom data [Block 3]
29 daniel-mar 652
                                        -  2 bit Variant (fix 0b10)
63 daniel-mar 653
                                        - 62 bit Custom data [Block 4+5]
29 daniel-mar 654
                                        */
655
 
47 daniel-mar 656
                                        echo sprintf("%-32s %s\n", "Version:", "[0x8] Custom implementation");
29 daniel-mar 657
 
658
                                        $custom_data = substr($uuid,0,12).substr($uuid,13); // exclude version nibble
38 daniel-mar 659
                                        $custom_data[15] = dechex(hexdec($custom_data[15]) & 0b0011); // nibble was partially overwritten by variant
29 daniel-mar 660
                                        $custom_data = strtolower($custom_data);
661
 
34 daniel-mar 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
38 daniel-mar 669
                                        $custom_block4[0] = dechex(hexdec($custom_block4[0]) & 0b0011); // remove variant
34 daniel-mar 670
 
29 daniel-mar 671
                                        echo sprintf("%-32s %s\n", "Custom data:", "[0x$custom_data]");
50 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]");
29 daniel-mar 677
 
55 daniel-mar 678
                                        // START: Check if Custom UUIDv8 is likely an OIDplus 2.0 Custom UUID
50 daniel-mar 679
 
55 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;
50 daniel-mar 683
 
55 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
54 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) {
55 daniel-mar 797
                                                                if ((hexdec(substr(sha1($oid), -4)) & 0x3fff) == $oidplus_namespace_int) $namespace_desc = "$oid = $name";
54 daniel-mar 798
                                                        }
55 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");
54 daniel-mar 805
                                                }
50 daniel-mar 806
                                        }
807
 
55 daniel-mar 808
                                        // END: OIDplus 2.0 Custom UUID Interpretation
809
 
27 daniel-mar 810
                                        break;
2 daniel-mar 811
                                default:
47 daniel-mar 812
                                        echo sprintf("%-32s %s\n", "Version:", "[0x".dechex($version)."] Unknown");
2 daniel-mar 813
                                        break;
814
                        }
815
 
816
                        break;
817
                case 2:
35 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
 
31 daniel-mar 820
                        // TODO: Is there any scheme in that legacy Microsoft GUIDs?
27 daniel-mar 821
                        echo sprintf("%-32s %s\n", "Variant:", "[0b110] Reserved for Microsoft Corporation");
2 daniel-mar 822
                        break;
823
                case 3:
27 daniel-mar 824
                        echo sprintf("%-32s %s\n", "Variant:", "[0b111] Reserved for future use");
2 daniel-mar 825
                        break;
826
        }
28 daniel-mar 827
 
828
        if (!$echo) {
829
                $out = ob_get_contents();
830
                ob_end_clean();
831
                return $out;
31 daniel-mar 832
        } else {
833
                return true;
28 daniel-mar 834
        }
2 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) {
49 daniel-mar 843
        if (!is_uuid_oid($oid,true)) return false;
2 daniel-mar 844
 
8 daniel-mar 845
        if (substr($oid,0,1) == '.') {
2 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) {
9 daniel-mar 865
        if (substr($oid,0,1) == '.') $oid = substr($oid, 1); // remove leading dot
2 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);
29 daniel-mar 896
        return '2.25.'.gmp_strval($x, 10);
2 daniel-mar 897
}
898
 
31 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
 
30 daniel-mar 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*/;
2 daniel-mar 923
        return $uuid;
924
}
925
 
30 daniel-mar 926
# --------------------------------------
927
// Variant 1, Version 1 (Time based) UUID
928
# --------------------------------------
28 daniel-mar 929
 
30 daniel-mar 930
function gen_uuid_v1() {
931
        return gen_uuid_timebased();
932
}
39 daniel-mar 933
function gen_uuid_timebased($force_php_implementation=false) {
2 daniel-mar 934
        # On Debian: apt-get install php-uuid
935
        # extension_loaded('uuid')
39 daniel-mar 936
        if (!$force_php_implementation && function_exists('uuid_create')) {
2 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
39 daniel-mar 950
        if (!$force_php_implementation && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
2 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
         */
38 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
2 daniel-mar 975
        $tp = gettimeofday();
38 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));
69 daniel-mar 983
                $uuid['time_hi'] = gmp_intval(gmp_and(gmp_shiftr($high,16),gmp_init('fff',16))) | (1/*TimeBased*/ << 12);
38 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
        }
2 daniel-mar 992
 
993
        /*
994
         * We don't support saved state information and generate
995
         * a random clock sequence each time.
996
         */
40 daniel-mar 997
        $uuid['clock_seq_hi'] = _random_int(0, 255) & 0b00111111 | 0b10000000; // set variant to 0b10__ (RFC 4122)
24 daniel-mar 998
        $uuid['clock_seq_low'] = _random_int(0, 255);
2 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) {
25 daniel-mar 1005
                $node = str_replace('-','',str_replace(':','',$mac));
2 daniel-mar 1006
                for ($i = 0; $i < 6; $i++) {
1007
                        $uuid['node'][$i] = hexdec(substr($node, $i*2, 2));
1008
                }
39 daniel-mar 1009
        } else {
1010
                // If we cannot get a MAC address, then generate a random AAI
52 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*/));
41 daniel-mar 1018
                $uuid['node'] = array_map('hexdec', $uuid['node']);
2 daniel-mar 1019
        }
1020
 
39 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]);
2 daniel-mar 1030
}
1031
 
30 daniel-mar 1032
# --------------------------------------
28 daniel-mar 1033
// Variant 1, Version 2 (DCE Security) UUID
30 daniel-mar 1034
# --------------------------------------
1035
 
27 daniel-mar 1036
define('DCE_DOMAIN_PERSON', 0);
1037
define('DCE_DOMAIN_GROUP', 1);
1038
define('DCE_DOMAIN_ORG', 2);
30 daniel-mar 1039
function gen_uuid_v2($domain, $id) {
1040
        return gen_uuid_dce($domain, $id);
1041
}
2 daniel-mar 1042
function gen_uuid_dce($domain, $id) {
31 daniel-mar 1043
        if (($domain ?? '') === '') throw new Exception("Domain ID missing");
1044
        if (!is_numeric($domain)) throw new Exception("Invalid Domain ID");
63 daniel-mar 1045
        if (($domain < 0) || ($domain > 0xFF)) throw new Exception("Domain ID must be in range 0..255");
31 daniel-mar 1046
 
1047
        if (($id ?? '') === '') throw new Exception("ID value missing");
1048
        if (!is_numeric($id)) throw new Exception("Invalid ID value");
63 daniel-mar 1049
        if (($id < 0) || ($id > 0xFFFFFFFF)) throw new Exception("ID value must be in range 0..4294967295");
31 daniel-mar 1050
 
2 daniel-mar 1051
        # Start with a version 1 UUID
1052
        $uuid = gen_uuid_timebased();
1053
 
27 daniel-mar 1054
        # Add Domain Number
2 daniel-mar 1055
        $uuid = str_pad(dechex($id), 8, '0', STR_PAD_LEFT) . substr($uuid, 8);
1056
 
27 daniel-mar 1057
        # Add Domain (this overwrites part of the clock sequence)
2 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
 
30 daniel-mar 1066
# --------------------------------------
28 daniel-mar 1067
// Variant 1, Version 3 (MD5 name based) UUID
30 daniel-mar 1068
# --------------------------------------
1069
 
1070
function gen_uuid_v3($namespace_uuid, $name) {
1071
        return gen_uuid_md5_namebased($namespace_uuid, $name);
1072
}
2 daniel-mar 1073
function gen_uuid_md5_namebased($namespace_uuid, $name) {
31 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
 
2 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
38 daniel-mar 1083
        $hash[16] = dechex(hexdec($hash[16]) & 0b0011 | 0b1000); // Set variant to "10xx" (RFC4122)
2 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
 
30 daniel-mar 1092
# --------------------------------------
28 daniel-mar 1093
// Variant 1, Version 4 (Random) UUID
30 daniel-mar 1094
# --------------------------------------
1095
 
1096
function gen_uuid_v4() {
1097
        return gen_uuid_random();
1098
}
2 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')) {
46 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
                }
2 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') {
72 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
 
2 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
46 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
        }
2 daniel-mar 1159
}
1160
 
30 daniel-mar 1161
# --------------------------------------
28 daniel-mar 1162
// Variant 1, Version 5 (SHA1 name based) UUID
30 daniel-mar 1163
# --------------------------------------
1164
 
1165
function gen_uuid_v5($namespace_uuid, $name) {
1166
        return gen_uuid_sha1_namebased($namespace_uuid, $name);
1167
}
2 daniel-mar 1168
function gen_uuid_sha1_namebased($namespace_uuid, $name) {
31 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
 
2 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
38 daniel-mar 1177
        $hash[16] = dechex(hexdec($hash[16]) & 0b0011 | 0b1000); // Set variant to "0b10__" (RFC4122/DCE1.1)
2 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
 
30 daniel-mar 1186
# --------------------------------------
1187
// Variant 1, Version 6 (Reordered) UUID
1188
# --------------------------------------
1189
 
1190
function gen_uuid_v6() {
1191
        return gen_uuid_reordered();
1192
}
1193
function gen_uuid_reordered() {
1194
        // Start with a UUIDv1
1195
        $uuid = gen_uuid_timebased();
1196
 
1197
        // Convert to UUIDv6
1198
        return uuid1_to_uuid6($uuid);
1199
}
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
}
1230
 
1231
# --------------------------------------
1232
// Variant 1, Version 7 (Unix Epoch) UUID
1233
# --------------------------------------
1234
 
70 daniel-mar 1235
function gen_uuid_v7(int $num_ms_frac_bits=12) {
1236
        return gen_uuid_unix_epoch($num_ms_frac_bits);
30 daniel-mar 1237
}
70 daniel-mar 1238
function gen_uuid_unix_epoch(int $num_ms_frac_bits=12) {
1239
        $uuid_nibbles = '';
30 daniel-mar 1240
 
70 daniel-mar 1241
        // Add the timestamp (milliseconds Unix)
30 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 {
70 daniel-mar 1248
                $unix_ts = dechex((int)ceil(microtime(true)*1000));
30 daniel-mar 1249
        }
1250
        $unix_ts = str_pad($unix_ts, 12, '0', STR_PAD_LEFT);
70 daniel-mar 1251
        $uuid_nibbles = $unix_ts;
30 daniel-mar 1252
 
70 daniel-mar 1253
        // Version = 7
1254
        $uuid_nibbles .= '7';
30 daniel-mar 1255
 
70 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)");
71 daniel-mar 1258
        $resolution_ns = 1000000 / pow(2,$num_ms_frac_bits);
70 daniel-mar 1259
        if ($num_ms_frac_bits > 0) {
71 daniel-mar 1260
                $seconds_fraction = (float)explode(' ',microtime(false))[0]; // <sec=0>,<msec>
70 daniel-mar 1261
 
71 daniel-mar 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
 
70 daniel-mar 1268
                // Currently, for the output we only allow frac bits 0, 4, 8, 12 (0-3 nibbles),
73 daniel-mar 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
70 daniel-mar 1271
                $num_nibbles = (int)ceil($num_ms_frac_bits/4);
71 daniel-mar 1272
                $uuid_nibbles .= str_pad(dechex($val), $num_nibbles, '0', STR_PAD_LEFT);
70 daniel-mar 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
71 daniel-mar 1284
        if (time_nanosleep(0,(int)ceil($resolution_ns)) !== true) usleep((int)ceil($resolution_ns/1000));
70 daniel-mar 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);
30 daniel-mar 1292
}
1293
 
1294
# --------------------------------------
34 daniel-mar 1295
// Variant 1, Version 8 (Custom) UUID
1296
# --------------------------------------
30 daniel-mar 1297
 
34 daniel-mar 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
38 daniel-mar 1315
        $block4[0] = dechex(hexdec($block4[0]) & 0b0011 | 0b1000); // Variant 0b10__ = RFC4122
34 daniel-mar 1316
 
1317
        return strtolower($block1.'-'.$block2.'-'.$block3.'-'.$block4.'-'.$block5);
1318
}
1319
 
69 daniel-mar 1320
function gen_uuid_v8_namebased($hash_algo, $namespace_uuid, $name) {
1321
        if (($hash_algo ?? '') === '') throw new Exception("Hash algorithm argument missing");
60 daniel-mar 1322
 
61 daniel-mar 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
 
69 daniel-mar 1326
        $uuid1 = uuid_valid($hash_algo) ? hex2bin(str_replace('-','',uuid_canonize($hash_algo))) : '';
60 daniel-mar 1327
        $uuid2 = hex2bin(str_replace('-','',uuid_canonize($namespace_uuid)));
1328
        $payload = $uuid1 . $uuid2 . $name;
1329
 
69 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;
62 daniel-mar 1338
                        }
60 daniel-mar 1339
                }
1340
        }
1341
 
69 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
 
60 daniel-mar 1346
        if ($hash == null) {
69 daniel-mar 1347
                throw new Exception("Unknown Hash Algorithm $hash_algo");
60 daniel-mar 1348
        }
1349
 
73 daniel-mar 1350
        $hash = str_pad($hash, 32, '0', STR_PAD_RIGHT); // fill short hashes with zeros to the right
1351
 
60 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).'-'.
63 daniel-mar 1356
               substr($hash,  8, 4).'-'.
1357
               substr($hash, 12, 4).'-'.
1358
               substr($hash, 16, 4).'-'.
1359
               substr($hash, 20, 12);
60 daniel-mar 1360
}
1361
 
1362
/**
1363
 * Collection of Namebased UUIDv8 Hash Space IDs
69 daniel-mar 1364
 * @return array An array containing tuples of [PHP Algo Name, Hash Space UUID, Human friendly name, Hash Space Author, Available]
60 daniel-mar 1365
 */
1366
function get_uuidv8_hash_space_ids(): array {
1367
        $out = array();
1368
 
67 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';
69 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')];
60 daniel-mar 1383
 
1384
        return $out;
1385
}
1386
 
34 daniel-mar 1387
# --------------------------------------
1388
 
2 daniel-mar 1389
// http://php.net/manual/de/function.hex2bin.php#113057
38 daniel-mar 1390
if (!function_exists('hex2bin')) {
1391
    function hex2bin($str) {
2 daniel-mar 1392
        $sbin = "";
38 daniel-mar 1393
        $len = strlen($str);
2 daniel-mar 1394
        for ( $i = 0; $i < $len; $i += 2 ) {
38 daniel-mar 1395
            $sbin .= pack("H*", substr($str, $i, 2));
2 daniel-mar 1396
        }
1397
        return $sbin;
1398
    }
1399
}
38 daniel-mar 1400
 
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
    }
1406
}
1407
 
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
    }
1412
}
57 daniel-mar 1413
 
1414
function shake128(string $msg, int $outputLength=512, bool $binary=false): string {
63 daniel-mar 1415
        include_once __DIR__.'/SHA3.php';
57 daniel-mar 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 {
63 daniel-mar 1423
        include_once __DIR__.'/SHA3.php';
57 daniel-mar 1424
        $sponge = SHA3::init(SHA3::SHAKE256);
1425
        $sponge->absorb($msg);
1426
        $bin = $sponge->squeeze($outputLength);
1427
        return $binary ? $bin : bin2hex($bin);
1428
}