Subversion Repositories oidinfo_api

Rev

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

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