Subversion Repositories oidplus

Rev

Rev 1470 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
604 daniel-mar 1
<?php
2
 
3
/*
4
 * UUID utils for PHP
1469 daniel-mar 5
 * Copyright 2011 - 2024 Daniel Marschall, ViaThinkSoft
1471 daniel-mar 6
 * Version 2024-04-16
604 daniel-mar 7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *     http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
 
21
# This library requires either the GMP extension (or BCMath if gmp_supplement.inc.php is present)
1324 daniel-mar 22
// TODO: If we are on 64 bit PHP (PHP_INT_SIZE > 4), then replace GMP with normal PHP operations
604 daniel-mar 23
 
1325 daniel-mar 24
if (file_exists($f = __DIR__ . '/mac_utils.inc.php')) include_once $f;
25
else if (file_exists($f = __DIR__ . '/mac_utils.inc.phps')) include_once $f;
1324 daniel-mar 26
 
1325 daniel-mar 27
if (file_exists($f = __DIR__ . '/gmp_supplement.inc.php')) include_once $f;
28
else if (file_exists($f = __DIR__ . '/gmp_supplement.inc.phps')) include_once $f;
604 daniel-mar 29
 
1325 daniel-mar 30
if (file_exists($f = __DIR__ . '/OidDerConverter.class.php')) include_once $f;
31
else if (file_exists($f = __DIR__ . '/OidDerConverter.class.phps')) include_once $f;
32
 
1384 daniel-mar 33
// Note: The RFC allows various notations as payload, not a strict notation constraint
34
const UUID_NAMEBASED_NS_DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
1324 daniel-mar 35
const UUID_NAMEBASED_NS_URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
36
const UUID_NAMEBASED_NS_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8';
1384 daniel-mar 37
const UUID_NAMEBASED_NS_X500_DN = '6ba7b814-9dad-11d1-80b4-00c04fd430c8';
604 daniel-mar 38
 
1324 daniel-mar 39
if (!function_exists('_random_int')) {
40
        function _random_int($min, $max) {
41
                // This function tries a CSRNG and falls back to a RNG if no CSRNG is available
42
                try {
43
                        return random_int($min, $max);
44
                } catch (Exception $e) {
45
                        return mt_rand($min, $max);
46
                }
1261 daniel-mar 47
        }
48
}
49
 
604 daniel-mar 50
function uuid_valid($uuid) {
51
        $uuid = str_replace(array('-', '{', '}'), '', $uuid);
52
        $uuid = strtoupper($uuid);
53
        #$uuid = trim($uuid);
54
 
55
        if (strlen($uuid) != 32) return false;
56
 
1324 daniel-mar 57
        $uuid = preg_replace('@[0-9A-F]@i', '', $uuid);
604 daniel-mar 58
 
59
        return ($uuid == '');
60
}
61
 
1376 daniel-mar 62
function uuid_equal($uuid1, $uuid2) {
63
        $uuid1 = uuid_canonize($uuid1);
64
        if (!$uuid1) return false;
65
        $uuid2 = uuid_canonize($uuid2);
66
        if (!$uuid2) return false;
67
        return $uuid1 === $uuid2;
68
}
69
 
1325 daniel-mar 70
function uuid_version($uuid) {
71
        $uuid = uuid_canonize($uuid);
72
        if (!$uuid) return false;
73
        return substr($uuid, 19, 1);
74
}
75
 
1324 daniel-mar 76
function uuid_info($uuid, $echo=true) {
604 daniel-mar 77
        if (!uuid_valid($uuid)) return false;
78
 
1325 daniel-mar 79
        $oid = uuid_to_oid($uuid);
80
 
81
        echo sprintf("%-32s %s\n", "Your input:", $uuid);
82
        echo "\n";
83
        echo "<u>Various notations:</u>\n";
84
        echo "\n";
85
        echo sprintf("%-32s %s\n", "URN:", 'urn:uuid:' . strtolower(oid_to_uuid(uuid_to_oid($uuid))));
86
        echo sprintf("%-32s %s\n", "URI:", 'uuid:' . strtolower(oid_to_uuid(uuid_to_oid($uuid))));
87
        echo sprintf("%-32s %s\n", "Microsoft GUID syntax:", '{' . strtoupper(oid_to_uuid(uuid_to_oid($uuid))) . '}');
88
        echo sprintf("%-32s %s\n", "C++ struct syntax:", uuid_c_syntax($uuid));
89
        echo "\n";
1424 daniel-mar 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
 
1325 daniel-mar 101
        echo "\n";
1424 daniel-mar 102
 
1325 daniel-mar 103
        echo "<u>Interpretation of the UUID:</u>\n\n";
104
 
1324 daniel-mar 105
        if (!$echo) ob_start();
106
 
604 daniel-mar 107
        #$uuid = trim($uuid);
108
        # $uuid = str_replace(array('-', '{', '}'), '', $uuid);
1324 daniel-mar 109
        $uuid = strtolower($uuid);
110
        $uuid = preg_replace('@[^0-9A-F]@i', '', $uuid);
604 daniel-mar 111
 
112
        $x = hexdec(substr($uuid, 16, 1));
1324 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;
604 daniel-mar 117
        else $variant = -1; // should not happen
118
 
1324 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
 
604 daniel-mar 128
        switch ($variant) {
129
                case 0:
1324 daniel-mar 130
                        echo sprintf("%-32s %s\n", "Variant:", "[0b0__] Network Computing System (NCS)");
604 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
 
1324 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
 
604 daniel-mar 156
                        // Example of an UUID: 333a2276-0000-0000-0d00-00809c000000
157
 
1324 daniel-mar 158
                        // TODO: also show legacy format, e.g. 458487b55160.02.c0.64.02.03.00.00.00
604 daniel-mar 159
 
1324 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
 
604 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);
1324 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");
604 daniel-mar 185
 
186
                        $reserved = substr($uuid, 12, 4);
1324 daniel-mar 187
                        echo sprintf("%-32s %s\n", "Reserved:", "[0x$reserved]");
604 daniel-mar 188
 
189
                        $family_hex = substr($uuid, 16, 2);
190
                        $family_dec = hexdec($family_hex);
1324 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";
604 daniel-mar 317
                        } else {
1324 daniel-mar 318
                                $family_name = "Unknown (Family $family_dec)"; # There are probably no more families
319
                                $nodeid_desc = "Unknown";
604 daniel-mar 320
                        }
1324 daniel-mar 321
                        echo sprintf("%-32s %s\n", "Family:", "[0x$family_hex] $family_name");
604 daniel-mar 322
 
1324 daniel-mar 323
                        echo sprintf("%-32s %s\n", "Node ID:", "[0x$nodeid_hex] $nodeid_desc");
604 daniel-mar 324
 
325
                        break;
326
                case 1:
1324 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
604 daniel-mar 328
 
329
                        $version = hexdec(substr($uuid, 12, 1));
1324 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)) {
1471 daniel-mar 336
                                echo sprintf("%-32s %s\n", "Variant:", "[0b10_] RFC 9562 (Davis-Peabody-Leach)");
1324 daniel-mar 337
                        } else {
1407 daniel-mar 338
                                echo sprintf("%-32s %s\n", "Variant:", "[0b10_] Unknown RFC");
1324 daniel-mar 339
                        }
340
 
604 daniel-mar 341
                        switch ($version) {
1324 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
                                        */
1325 daniel-mar 353
                                        echo sprintf("%-32s %s\n", "Version:", "[0x6] Reordered Time-Based");
1324 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 */
604 daniel-mar 363
                                case 1:
1324 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
                                        */
604 daniel-mar 375
 
1325 daniel-mar 376
                                        if ($version == 1) echo sprintf("%-32s %s\n", "Version:", "[0x1] Time-based with unique host identifier");
1324 daniel-mar 377
 
604 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);
1324 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");
604 daniel-mar 389
 
390
                                        $x = hexdec(substr($uuid, 16, 4));
391
                                        $dec = $x & 0x3FFF; // The highest 2 bits are used by "variant" (10x)
392
                                        $hex = substr($uuid, 16, 4);
1324 daniel-mar 393
                                        $hex = '<abbr title="The highest 2 bits are used by the UUID variant (10xx)">'.$hex[0].'</abbr>'.substr($hex,1);
394
                                        echo sprintf("%-32s %s\n", "Clock ID:", "[0x$hex] $dec");
604 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);
1324 daniel-mar 400
                                                if ($i != 5) $nodeid .= '-';
604 daniel-mar 401
                                        }
1324 daniel-mar 402
                                        $nodeid = strtoupper($nodeid);
403
                                        echo sprintf("%-32s %s\n", "Node ID:", "[0x$x] $nodeid");
604 daniel-mar 404
 
1324 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));
604 daniel-mar 407
 
408
                                        break;
409
                                case 2:
1324 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
                                        */
604 daniel-mar 421
 
1324 daniel-mar 422
                                        // see also https://unicorn-utterances.com/posts/what-happened-to-uuid-v2
604 daniel-mar 423
 
1325 daniel-mar 424
                                        echo sprintf("%-32s %s\n", "Version:", "[0x2] DCE Security version");
1324 daniel-mar 425
 
604 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".
427
                                        $x = substr($uuid, 18, 2);
1324 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)';
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");
604 daniel-mar 433
 
1324 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
 
604 daniel-mar 439
                                        # Timestamp: Count of 100ns intervals since 15 Oct 1582 00:00:00
440
                                        # 1/0,0000001 = 10000000
441
                                        $timestamp = substr($uuid, 13, 3).substr($uuid, 8, 4).'00000000';
442
                                        $ts = gmp_init($timestamp, 16);
1324 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';
604 daniel-mar 449
 
450
                                        $timestamp = substr($uuid, 13, 3).substr($uuid, 8, 4).'FFFFFFFF';
451
                                        $ts = gmp_init($timestamp, 16);
1324 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';
604 daniel-mar 458
 
1324 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");
604 daniel-mar 461
 
1324 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);
465
                                        $hex = '<abbr title="The highest 2 bits are used by the UUID variant (10xx)">'.$hex[0].'</abbr>'.substr($hex,1);
466
                                        echo sprintf("%-32s %s\n", "Clock ID:", "[0x$hex] $dec");
604 daniel-mar 467
 
468
                                        $x = substr($uuid, 20, 12);
469
                                        $nodeid = '';
470
                                        for ($i=0; $i<6; $i++) {
471
                                                $nodeid .= substr($x, $i*2, 2);
1324 daniel-mar 472
                                                if ($i != 5) $nodeid .= '-';
604 daniel-mar 473
                                        }
1324 daniel-mar 474
                                        $nodeid = strtoupper($nodeid);
475
                                        echo sprintf("%-32s %s\n", "Node ID:", "[0x$x] $nodeid");
604 daniel-mar 476
 
1324 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));
604 daniel-mar 479
 
480
                                        break;
481
                                case 3:
1324 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
                                        */
604 daniel-mar 490
 
1325 daniel-mar 491
                                        echo sprintf("%-32s %s\n", "Version:", "[0x3] Name-based (MD5 hash)");
1324 daniel-mar 492
 
604 daniel-mar 493
                                        $hash = str_replace('-', '', strtolower($uuid));
1324 daniel-mar 494
 
604 daniel-mar 495
                                        $hash[12] = '?'; // was overwritten by version
1324 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));
604 daniel-mar 501
                                        $hash[16] = '?'; // was partially overwritten by variant
502
 
1324 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);
505
                                        echo sprintf("%-32s %s\n", "MD5(Namespace+Subject):", "[0x$hash]");
604 daniel-mar 506
 
507
                                        break;
508
                                case 4:
1324 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
                                        */
604 daniel-mar 517
 
1325 daniel-mar 518
                                        echo sprintf("%-32s %s\n", "Version:", "[0x4] Random");
1324 daniel-mar 519
 
520
                                        $rand_line1 = '';
521
                                        $rand_line2 = '';
604 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) {
1324 daniel-mar 527
                                                        // was overwritten by version
528
                                                        $bin[0] = '?';
529
                                                        $bin[1] = '?';
530
                                                        $bin[2] = '?';
531
                                                        $bin[3] = '?';
604 daniel-mar 532
                                                } else if ($i == 8) {
1324 daniel-mar 533
                                                        // was partially overwritten by variant
534
                                                        $bin[0] = '?';
535
                                                        $bin[1] = '?';
604 daniel-mar 536
                                                }
537
 
1324 daniel-mar 538
                                                if ($i<8) $rand_line1 .= "$bin ";
539
                                                if ($i>=8) $rand_line2 .= "$bin ";
604 daniel-mar 540
                                        }
1324 daniel-mar 541
                                        echo sprintf("%-32s %s\n", "Random bits:", trim($rand_line1));
542
                                        echo sprintf("%-32s %s\n", "",             trim($rand_line2));
604 daniel-mar 543
 
1324 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
604 daniel-mar 551
 
1324 daniel-mar 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);
554
                                        echo sprintf("%-32s %s\n", "Random bytes:", "[0x$rand_bytes]");
555
 
604 daniel-mar 556
                                        break;
557
                                case 5:
1324 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
                                        */
604 daniel-mar 566
 
1325 daniel-mar 567
                                        echo sprintf("%-32s %s\n", "Version:", "[0x5] Name-based (SHA-1 hash)");
1324 daniel-mar 568
 
604 daniel-mar 569
                                        $hash = str_replace('-', '', strtolower($uuid));
1324 daniel-mar 570
 
604 daniel-mar 571
                                        $hash[12] = '?'; // was overwritten by version
1324 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));
604 daniel-mar 577
                                        $hash[16] = '?'; // was partially overwritten by variant
1324 daniel-mar 578
 
604 daniel-mar 579
                                        $hash .= '????????'; // was cut off
580
 
1324 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);
583
                                        echo sprintf("%-32s %s\n", "SHA1(Namespace+Subject):", "[0x$hash]");
604 daniel-mar 584
 
1324 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)
1411 daniel-mar 591
                                        - 12 bit Data
1324 daniel-mar 592
                                        -  2 bit Variant (fix 0b10)
1411 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
1324 daniel-mar 602
                                        */
604 daniel-mar 603
 
1325 daniel-mar 604
                                        echo sprintf("%-32s %s\n", "Version:", "[0x7] Unix Epoch Time");
1324 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
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);
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
 
604 daniel-mar 653
                                        break;
1324 daniel-mar 654
                                case 8:
655
                                        /*
656
                                        Variant 1, Version 8 UUID
1376 daniel-mar 657
                                        - 48 bit Custom data [Block 1+2]
1324 daniel-mar 658
                                        -  4 bit Version (fix 0x8)
1376 daniel-mar 659
                                        - 12 bit Custom data [Block 3]
1324 daniel-mar 660
                                        -  2 bit Variant (fix 0b10)
1376 daniel-mar 661
                                        - 62 bit Custom data [Block 4+5]
1324 daniel-mar 662
                                        */
663
 
1325 daniel-mar 664
                                        echo sprintf("%-32s %s\n", "Version:", "[0x8] Custom implementation");
1324 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]");
1334 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]");
1324 daniel-mar 685
 
1345 daniel-mar 686
                                        // START: Check if Custom UUIDv8 is likely an OIDplus 2.0 Custom UUID
1334 daniel-mar 687
 
1345 daniel-mar 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;
1334 daniel-mar 691
 
1345 daniel-mar 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
1336 daniel-mar 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) {
1345 daniel-mar 805
                                                                if ((hexdec(substr(sha1($oid), -4)) & 0x3fff) == $oidplus_namespace_int) $namespace_desc = "$oid = $name";
1336 daniel-mar 806
                                                        }
1345 daniel-mar 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");
1336 daniel-mar 813
                                                }
1334 daniel-mar 814
                                        }
815
 
1345 daniel-mar 816
                                        // END: OIDplus 2.0 Custom UUID Interpretation
817
 
1470 daniel-mar 818
                                        // START: hayageek UUIDv8 Interpretation
819
                                        // https://github.com/hayageek/uuid-v8
820
                                        // Example: 07e80404-1736-8934-8374-e63eb25babd2
821
 
822
                                        $year    = hexdec(substr($uuid,0,4));
823
                                        $month   = hexdec(substr($uuid,4,2));
824
                                        $day     = hexdec(substr($uuid,6,2));
825
                                        $hours   = hexdec(substr($uuid,8,2));
826
                                        $minutes = hexdec(substr($uuid,10,2));
827
                                        $ver     = hexdec(substr($uuid,12,1));
828
                                        $rnd1    = substr($uuid,13,1);
829
                                        $seconds = hexdec(substr($uuid,14,2));
830
 
831
                                        // Diagram (WRONG):  <2 variant 0b10> <8 msec> <6 random>
832
                                        // Actual Code:      <2 variant 0b10> <2 zero> <12 msec 0..0x3E7>
833
                                        $tmp   = hexdec(substr($uuid,16,4));
834
 
835
                                        $var = ($tmp >> 14) & 0b11;
836
                                        $zero = ($tmp >> 12) & 0b11;
837
                                        $milliseconds = $tmp & 0xFFF;
838
 
839
                                        $rnd2  = substr($uuid,20,12);
840
 
841
                                        $utc_time =
842
                                                str_pad("$year",4,'0',STR_PAD_LEFT).'-'.
843
                                                str_pad("$month",2,'0',STR_PAD_LEFT).'-'.
844
                                                str_pad("$day",2,'0',STR_PAD_LEFT).' '.
845
                                                str_pad("$hours",2,'0',STR_PAD_LEFT).':'.
846
                                                str_pad("$minutes",2,'0',STR_PAD_LEFT).':'.
847
                                                str_pad("$seconds",2,'0',STR_PAD_LEFT)."'".
848
                                                str_pad("$milliseconds",3/*ms*/,'0',STR_PAD_LEFT);
849
 
850
                                        $date_valid = checkdate($month, $day, $year);
851
                                        $time_valid = ($hours   >= 0) && ($hours <= 23) &&
852
                                                      ($minutes >= 0) && ($minutes <= 59) &&
853
                                                      ($seconds >= 0) && ($seconds <= 59) &&
854
                                                      ($milliseconds >= 0) && ($milliseconds <= 999); /**@phpstan-ignore-line*/
855
 
856
                                        if (($ver == 8) && ($var == 2) && ($zero == 0) && $date_valid && $time_valid) {
857
                                                echo "\n<u>Interpretation of <a href=\"https://github.com/hayageek/uuid-v8\">hayageek UUIDv8</a></u>\n\n";
858
 
859
                                                echo sprintf("%-32s %s\n", "Date/Time:", "$utc_time UTC");
860
                                                echo sprintf("%-32s %s\n", "Random Data:", "0x$rnd1$rnd2");
861
                                                echo sprintf("%-32s %s\n", "Year:", sprintf("[0x%04x] %d", $year, $year));
862
                                                echo sprintf("%-32s %s\n", "Month:", sprintf("[0x%02x] %d", $month, $month));
863
                                                echo sprintf("%-32s %s\n", "Day:", sprintf("[0x%02x] %d", $day, $day));
864
                                                echo sprintf("%-32s %s\n", "Hours:", sprintf("[0x%02x] %d", $hours, $hours));
865
                                                echo sprintf("%-32s %s\n", "Minutes:", sprintf("[0x%02x] %d", $minutes, $minutes));
866
                                                echo sprintf("%-32s %s\n", "Random 1:", sprintf("[0x%s]", $rnd1));
867
                                                echo sprintf("%-32s %s\n", "Seconds:", sprintf("[0x%02x] %d", $seconds, $seconds));
868
                                                echo sprintf("%-32s %s\n", "Milliseconds:", sprintf("[0x%03x] %d", $milliseconds, $milliseconds));
869
                                                echo sprintf("%-32s %s\n", "Random 2:", sprintf("[0x%s]", $rnd2));
870
                                        }
871
 
872
                                        // END: hayageek UUIDv8 Interpretation
873
 
1324 daniel-mar 874
                                        break;
604 daniel-mar 875
                                default:
1325 daniel-mar 876
                                        echo sprintf("%-32s %s\n", "Version:", "[0x".dechex($version)."] Unknown");
604 daniel-mar 877
                                        break;
878
                        }
879
 
880
                        break;
881
                case 2:
1324 daniel-mar 882
                        // 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
883
 
884
                        // TODO: Is there any scheme in that legacy Microsoft GUIDs?
885
                        echo sprintf("%-32s %s\n", "Variant:", "[0b110] Reserved for Microsoft Corporation");
604 daniel-mar 886
                        break;
887
                case 3:
1324 daniel-mar 888
                        echo sprintf("%-32s %s\n", "Variant:", "[0b111] Reserved for future use");
604 daniel-mar 889
                        break;
890
        }
1324 daniel-mar 891
 
1469 daniel-mar 892
        // START: HickelSOFT UUID
893
 
894
        // Block 5
895
        $signature = substr($uuid,20,12);
896
        if (strtolower($signature) == '5ce32bd83b96') {
897
                // HickelSOFT "SQL Server sortable UUID in C#"
898
                // Version 2: Resolution of 1 milliseconds, random part of 18 bits, UTC time, UUIDv8 conform.
899
                // Example: 2088dc33-000d-8045-87e8-5ce32bd83b96
900
                // Block 4
901
                $unused2bits = hexdec(substr($uuid,16,1)) & 0x3;
902
                $year = hexdec(substr($uuid,17,3));
903
                // Block 3
904
                $dayOfYear = hexdec(substr($uuid,13,3)); // 1..366
905
                $day = (($dayOfYear < 1) || ($dayOfYear > 366)) ? "XX" : intval(getDateFromDay($year, $dayOfYear)->format('d'));
906
                $month = (($dayOfYear < 1) || ($dayOfYear > 366)) ? "XX" : intval(getDateFromDay($year, $dayOfYear)->format('m'));
907
                // Block 2
908
                $minuteOfDay = hexdec(substr($uuid,8,4)); // 1..1440
909
                $minutes = (($minuteOfDay < 1) || ($minuteOfDay > 1440)) ? "XX" : ($minuteOfDay-1) % 60;
910
                $hours = (($minuteOfDay < 1) || ($minuteOfDay > 1440)) ? "XX" : (int)floor(($minuteOfDay-1) / 60);
911
                // Block 1
912
                $rnd16bits = substr($uuid,0,4);
913
                $millisecond8bits = hexdec(substr($uuid,4,2));
914
                $milliseconds = round($millisecond8bits / 255 * 999);
915
                $seconds = hexdec(substr($uuid,6,2));
916
                // Verbose info
917
                $utc_time =
918
                        str_pad("$year",4,'0',STR_PAD_LEFT).'-'.
919
                        str_pad("$month",2,'0',STR_PAD_LEFT).'-'.
920
                        str_pad("$day",2,'0',STR_PAD_LEFT).' '.
921
                        str_pad("$hours",2,'0',STR_PAD_LEFT).':'.
922
                        str_pad("$minutes",2,'0',STR_PAD_LEFT).':'.
923
                        str_pad("$seconds",2,'0',STR_PAD_LEFT)."'".
1470 daniel-mar 924
                        str_pad("$milliseconds",3/*ms*/,'0',STR_PAD_LEFT);
1471 daniel-mar 925
                if ((strpos($utc_time,'X') === false) && checkdate($month, $day, $year)) {
1469 daniel-mar 926
                        $deviation = "(deviation -2ms..2ms)";
927
                        echo "\n<u>Interpretation of <a href=\"https://gist.github.com/danielmarschall/7fafd270a3bc107d38e8449ce7420c25\">HickelSOFT \"SQL Server Sortable Custom UUID\", Version 2</a></u>\n\n";
928
                        echo sprintf("%-32s %s\n", "Random 16 bits:", "[0x$rnd16bits] 0b".str_pad("".base_convert($rnd16bits, 16, 2), 16, '0', STR_PAD_LEFT));
929
                        echo sprintf("%-32s %s\n", "Milliseconds:", "[0x".substr($uuid,4,2)."] $milliseconds $deviation");
930
                        echo sprintf("%-32s %s\n", "Seconds:", "[0x".substr($uuid,6,2)."] $seconds");
931
                        echo sprintf("%-32s %s\n", "Minute of day:", "[0x".substr($uuid,8,4)."] $minuteOfDay (".str_pad("$hours",2,'0',STR_PAD_LEFT).":".str_pad("$minutes",2,'0',STR_PAD_LEFT).")");
932
                        echo sprintf("%-32s %s\n", "Day of year:", "[0x".substr($uuid,13,3)."] $dayOfYear (Day=$day, Month=$month)");
933
                        echo sprintf("%-32s %s\n", "Unused 2 bits:", "[$unused2bits] 0b".str_pad("".base_convert("$unused2bits", 16, 2), 2, '0', STR_PAD_LEFT));
934
                        echo sprintf("%-32s %s\n", "Year:", "[0x".substr($uuid,17,3)."] $year");
935
                        echo sprintf("%-32s %s\n", "Signature:", "[0x".substr($uuid,20,12)."] HickelSOFT \"SQL Server Sortable Custom UUID\", Version 2 (very likely)");
936
                        echo sprintf("%-32s %s\n", "UTC Date Time:", "$utc_time $deviation");
937
                }
938
        } else if (strtolower($signature) == '000000000000') {
939
                // HickelSOFT "SQL Server sortable UUID in C#"
940
                // Version 1: Resolution of 1 milliseconds, random part of 16 bits, local timezone, NOT UUIDv8 conform.
941
                // Example: ff38da51-1301-0903-2420-000000000000
942
                // Block 4
943
                $year = substr($uuid,18,2) . substr($uuid,16,2);
944
                $year = (!is_numeric($year) || ($year < 2000) || ($year > 2999)) ? "XXXX" : $year = intval($year);
945
                // Block 3
946
                $day = substr($uuid,12,2);
947
                $day = (!is_numeric($day) || ($day < 0) || ($day >= 60)) ? "XX" : intval($day);
948
                $month = substr($uuid,14,2);
949
                $month = (!is_numeric($month) || ($month < 0) || ($month >= 60)) ? "XX" : intval($month);
950
                // Block 2
951
                $minutes = substr($uuid,8,2);
952
                $minutes = (!is_numeric($minutes) || ($minutes < 0) || ($minutes >= 60)) ? "XX" : intval($minutes);
953
                $hours = substr($uuid,10,2);
954
                $hours = (!is_numeric($hours) || ($hours < 0) || ($hours >= 60)) ? "XX" : intval($hours);
955
                // Block 1
956
                $rnd16bits = substr($uuid,0,4);
957
                $millisecond8bits = hexdec(substr($uuid,4,2));
958
                $milliseconds = round($millisecond8bits / 255 * 999);
959
                $seconds = substr($uuid,6,2);
960
                $seconds = (!is_numeric($seconds) || ($seconds < 0) || ($seconds >= 60)) ? "XX" : intval($seconds);
961
                // Verbose info
962
                $local_time =
963
                        str_pad("$year",4,'0',STR_PAD_LEFT).'-'.
964
                        str_pad("$month",2,'0',STR_PAD_LEFT).'-'.
965
                        str_pad("$day",2,'0',STR_PAD_LEFT).' '.
966
                        str_pad("$hours",2,'0',STR_PAD_LEFT).':'.
967
                        str_pad("$minutes",2,'0',STR_PAD_LEFT).':'.
968
                        str_pad("$seconds",2,'0',STR_PAD_LEFT)."'".
1470 daniel-mar 969
                        str_pad("$milliseconds",3/*ms*/,'0',STR_PAD_LEFT);
1471 daniel-mar 970
                if ((strpos($local_time,'X') === false) && checkdate($month, $day, $year)) {
1469 daniel-mar 971
                        $deviation = "(deviation -4ms..0ms)";
972
                        echo "\n<u>Interpretation of <a href=\"https://gist.github.com/danielmarschall/7fafd270a3bc107d38e8449ce7420c25\">HickelSOFT \"SQL Server Sortable Custom UUID\", Version 1</a></u>\n\n";
973
                        echo sprintf("%-32s %s\n", "Random 16 bits:", "[0x$rnd16bits] 0b".str_pad(base_convert($rnd16bits, 16, 2), 16, '0', STR_PAD_LEFT));
974
                        echo sprintf("%-32s %s\n", "Milliseconds:", "[0x".substr($uuid,4,2)."] $milliseconds $deviation");
975
                        echo sprintf("%-32s %s\n", "Seconds:", "[0x".substr($uuid,6,2)."] $seconds");
976
                        echo sprintf("%-32s %s\n", "Minutes:", "[0x".substr($uuid,8,2)."] $minutes");
977
                        echo sprintf("%-32s %s\n", "Hours:", "[0x".substr($uuid,10,2)."] $hours");
978
                        echo sprintf("%-32s %s\n", "Day:", "[0x".substr($uuid,12,2)."] $day");
979
                        echo sprintf("%-32s %s\n", "Month:", "[0x".substr($uuid,14,2)."] $month");
980
                        echo sprintf("%-32s %s\n", "Year:", "[0x".substr($uuid,16,4)."] $year");
981
                        echo sprintf("%-32s %s\n", "Signature:", "[0x".substr($uuid,20,12)."] HickelSOFT \"SQL Server Sortable Custom UUID\", Version 1 (maybe)");
982
                        echo sprintf("%-32s %s\n", "Generator's Local Date Time:", "$local_time $deviation");
983
                }
984
        }
985
 
986
        // END: HickelSOFT UUID
987
 
1324 daniel-mar 988
        if (!$echo) {
989
                $out = ob_get_contents();
990
                ob_end_clean();
991
                return $out;
992
        } else {
993
                return true;
994
        }
604 daniel-mar 995
}
996
 
997
function uuid_canonize($uuid) {
998
        if (!uuid_valid($uuid)) return false;
999
        return oid_to_uuid(uuid_to_oid($uuid));
1000
}
1001
 
1438 daniel-mar 1002
/*
1003
assert(oid_to_uuid('2.25.111325678376819997685911819737516232943')=='53c08bb6-b2eb-5038-bf28-ad41a08c50ef');
1004
assert(oid_to_uuid('1.2.840.113556.1.8000.2554.21440.35766.45803.20536.48936.11354528.9195759')=='53c08bb6-b2eb-5038-bf28-ad41a08c50ef');
1005
assert(oid_to_uuid('1.3.6.1.4.1.54392.1.6034977117478539320.13774449957690691823')=='53c08bb6-b2eb-5038-bf28-ad41a08c50ef');
1006
assert(oid_to_uuid('1.3.6.1.4.1.54392.2.1405127606.3001765944.3207114049.2693550319')=='53c08bb6-b2eb-5038-bf28-ad41a08c50ef');
1007
assert(oid_to_uuid('1.3.6.1.4.1.54392.3.21440.35766.45803.20536.48936.44353.41100.20719')=='53c08bb6-b2eb-5038-bf28-ad41a08c50ef');
1008
*/
604 daniel-mar 1009
function oid_to_uuid($oid) {
1438 daniel-mar 1010
        if (substr($oid,0,1) == '.') $oid = substr($oid, 1); // remove leading dot
604 daniel-mar 1011
 
1438 daniel-mar 1012
        // Information about Microsoft and Waterjuice UUID-OID: https://waterjuiceweb.wordpress.com/2019/09/24/guids-to-oids/
1013
 
1014
        $ary = explode('.', $oid);
1015
        if ((count($ary) == 3) && (strpos($oid, '2.25.') === 0)) {
1016
                // ISO/ITU-T UUID-to-OID
1017
                // Example: {53c08bb6-b2eb-5038-bf28-ad41a08c50ef} = 2.25.111325678376819997685911819737516232943
1018
                $val = $ary[2];
1019
                $dec = gmp_init($val, 10);
1020
                $hex = gmp_strval($dec, 16);
1021
                $hex = str_pad($hex, 32, "0", STR_PAD_LEFT);
1022
                return substr($hex,0,8).'-'.substr($hex,8,4).'-'.substr($hex,12,4).'-'.substr($hex,16,4).'-'.substr($hex,20,12);
1023
        } else if ((count($ary) == 14) && (strpos($oid, '1.2.840.113556.1.8000.2554.') === 0)) {
1024
                // Microsoft UUID-to-OID
1025
                // Example: {53c08bb6-b2eb-5038-bf28-ad41a08c50ef} = 1.2.840.113556.1.8000.2554.21440.35766.45803.20536.48936.11354528.9195759
1469 daniel-mar 1026
                $a = intval($ary[7]);
1027
                $b = intval($ary[8]);
1028
                $c = intval($ary[9]);
1029
                $d = intval($ary[10]);
1030
                $e = intval($ary[11]);
1031
                $f = intval($ary[12]);
1032
                $g = intval($ary[13]);
1438 daniel-mar 1033
                return dechex($a).dechex($b).'-'.dechex($c).'-'.dechex($d).'-'.dechex($e).'-'.dechex($f).dechex($g);
1034
        } else if ((count($ary) == 10) && (strpos($oid, '1.3.6.1.4.1.54392.1.') === 0)) {
1035
                // Waterjuice UUID-to-OID 2x64 Bits
1036
                // Example: {53c08bb6-b2eb-5038-bf28-ad41a08c50ef} = 1.3.6.1.4.1.54392.1.6034977117478539320.13774449957690691823
1037
                $a1 = gmp_strval(gmp_init($ary[8],10),16); if (strlen($a1)>16) return false;
1038
                $a2 = gmp_strval(gmp_init($ary[9],10),16); if (strlen($a2)>16) return false;
1039
                $hex =
1040
                        str_pad($a1, 16, "0", STR_PAD_LEFT).
1041
                        str_pad($a2, 16, "0", STR_PAD_LEFT);
1042
                return substr($hex,0,8).'-'.substr($hex,8,4).'-'.substr($hex,12,4).'-'.substr($hex,16,4).'-'.substr($hex,20,12);
1043
        } else if ((count($ary) == 12) && (strpos($oid, '1.3.6.1.4.1.54392.2.') === 0)) {
1044
                // Waterjuice UUID-to-OID 4x32 Bits
1045
                // Example: {53c08bb6-b2eb-5038-bf28-ad41a08c50ef} = 1.3.6.1.4.1.54392.2.1405127606.3001765944.3207114049.2693550319
1046
                $a1 = gmp_strval(gmp_init($ary[8],10),16); if (strlen($a1)>8) return false;
1047
                $a2 = gmp_strval(gmp_init($ary[9],10),16); if (strlen($a2)>8) return false;
1048
                $a3 = gmp_strval(gmp_init($ary[10],10),16); if (strlen($a3)>8) return false;
1049
                $a4 = gmp_strval(gmp_init($ary[11],10),16); if (strlen($a4)>8) return false;
1050
                $hex =
1051
                        str_pad($a1, 8, "0", STR_PAD_LEFT).
1052
                        str_pad($a2, 8, "0", STR_PAD_LEFT).
1053
                        str_pad($a3, 8, "0", STR_PAD_LEFT).
1054
                        str_pad($a4, 8, "0", STR_PAD_LEFT);
1055
                return substr($hex,0,8).'-'.substr($hex,8,4).'-'.substr($hex,12,4).'-'.substr($hex,16,4).'-'.substr($hex,20,12);
1056
        } else if ((count($ary) == 16) && (strpos($oid, '1.3.6.1.4.1.54392.3.') === 0)) {
1057
                // Waterjuice UUID-to-OID 8x16 Bits
1058
                // Example: {53c08bb6-b2eb-5038-bf28-ad41a08c50ef} = 1.3.6.1.4.1.54392.3.21440.35766.45803.20536.48936.44353.41100.20719
1059
                $a1 = gmp_strval(gmp_init($ary[8],10),16); if (strlen($a1)>4) return false;
1060
                $a2 = gmp_strval(gmp_init($ary[9],10),16); if (strlen($a2)>4) return false;
1061
                $a3 = gmp_strval(gmp_init($ary[10],10),16); if (strlen($a3)>4) return false;
1062
                $a4 = gmp_strval(gmp_init($ary[11],10),16); if (strlen($a4)>4) return false;
1063
                $a5 = gmp_strval(gmp_init($ary[12],10),16); if (strlen($a5)>4) return false;
1064
                $a6 = gmp_strval(gmp_init($ary[13],10),16); if (strlen($a6)>4) return false;
1065
                $a7 = gmp_strval(gmp_init($ary[14],10),16); if (strlen($a7)>4) return false;
1066
                $a8 = gmp_strval(gmp_init($ary[15],10),16); if (strlen($a8)>4) return false;
1067
                $hex =
1068
                        str_pad($a1, 4, "0", STR_PAD_LEFT).
1069
                        str_pad($a2, 4, "0", STR_PAD_LEFT).
1070
                        str_pad($a3, 4, "0", STR_PAD_LEFT).
1071
                        str_pad($a4, 4, "0", STR_PAD_LEFT).
1072
                        str_pad($a5, 4, "0", STR_PAD_LEFT).
1073
                        str_pad($a6, 4, "0", STR_PAD_LEFT).
1074
                        str_pad($a7, 4, "0", STR_PAD_LEFT).
1075
                        str_pad($a8, 4, "0", STR_PAD_LEFT);
1076
                return substr($hex,0,8).'-'.substr($hex,8,4).'-'.substr($hex,12,4).'-'.substr($hex,16,4).'-'.substr($hex,20,12);
1077
        } else {
1078
                return false;
604 daniel-mar 1079
        }
1080
}
1081
 
1082
function is_uuid_oid($oid, $only_allow_root=false) {
721 daniel-mar 1083
        if (substr($oid,0,1) == '.') $oid = substr($oid, 1); // remove leading dot
604 daniel-mar 1084
 
1085
        if ($only_allow_root) {
1438 daniel-mar 1086
                return oid_to_uuid($oid) !== false;
604 daniel-mar 1087
        } else {
1438 daniel-mar 1088
                // TODO: Check range of the components (e.g. max 128 bits for 2.25)
1089
                if (strpos($oid,'2.25.') === 0) return true;
1090
                if (strpos($oid,'1.2.840.113556.1.8000.2554.') === 0) return true;
1091
                if (strpos($oid,'1.3.6.1.4.1.54392.1.') === 0) return true;
1092
                if (strpos($oid,'1.3.6.1.4.1.54392.2.') === 0) return true;
1093
                if (strpos($oid,'1.3.6.1.4.1.54392.3.') === 0) return true;
1094
                return false;
604 daniel-mar 1095
        }
1096
}
1097
 
1424 daniel-mar 1098
function uuid_to_oid($uuid, $base='2.25') {
604 daniel-mar 1099
        if (!uuid_valid($uuid)) return false;
1424 daniel-mar 1100
        #$base = oid_sanitize($base);
604 daniel-mar 1101
 
1102
        $uuid = str_replace(array('-', '{', '}'), '', $uuid);
1424 daniel-mar 1103
 
1104
        // Information about Microsoft and Waterjuice UUID-OID: https://waterjuiceweb.wordpress.com/2019/09/24/guids-to-oids/
1105
 
1106
        if ($base == '2.25') {
1107
                $x = gmp_init($uuid, 16);
1108
                return $base.'.'.gmp_strval($x, 10);
1109
        } else if ($base == '1.2.840.113556.1.8000.2554') {
1110
                return $base.'.'.
1111
                        gmp_strval(gmp_init(substr($uuid,0,4),16),10).'.'.
1112
                        gmp_strval(gmp_init(substr($uuid,4,4),16),10).'.'.
1113
                        gmp_strval(gmp_init(substr($uuid,8,4),16),10).'.'.
1114
                        gmp_strval(gmp_init(substr($uuid,12,4),16),10).'.'.
1115
                        gmp_strval(gmp_init(substr($uuid,16,4),16),10).'.'.
1116
                        gmp_strval(gmp_init(substr($uuid,20,6),16),10).'.'.
1117
                        gmp_strval(gmp_init(substr($uuid,26,6),16),10);
1118
        } else if ($base == '1.3.6.1.4.1.54392.1') {
1119
                return $base.'.'.
1120
                        gmp_strval(gmp_init(substr($uuid,0,16),16),10).'.'.
1121
                        gmp_strval(gmp_init(substr($uuid,16,16),16),10);
1122
        } else if ($base == '1.3.6.1.4.1.54392.2') {
1123
                return $base.'.'.
1124
                        gmp_strval(gmp_init(substr($uuid,0,8),16),10).'.'.
1125
                        gmp_strval(gmp_init(substr($uuid,8,8),16),10).'.'.
1126
                        gmp_strval(gmp_init(substr($uuid,16,8),16),10).'.'.
1127
                        gmp_strval(gmp_init(substr($uuid,24,8),16),10);
1128
        } else if ($base == '1.3.6.1.4.1.54392.3') {
1129
                return $base.'.'.
1130
                        gmp_strval(gmp_init(substr($uuid,0,4),16),10).'.'.
1131
                        gmp_strval(gmp_init(substr($uuid,4,4),16),10).'.'.
1132
                        gmp_strval(gmp_init(substr($uuid,8,4),16),10).'.'.
1133
                        gmp_strval(gmp_init(substr($uuid,12,4),16),10).'.'.
1134
                        gmp_strval(gmp_init(substr($uuid,16,4),16),10).'.'.
1135
                        gmp_strval(gmp_init(substr($uuid,20,4),16),10).'.'.
1136
                        gmp_strval(gmp_init(substr($uuid,24,4),16),10).'.'.
1137
                        gmp_strval(gmp_init(substr($uuid,28,4),16),10);
1138
        } else {
1139
                throw new Exception("Unsupported UUID-to-OID base");
1140
        }
604 daniel-mar 1141
}
1142
 
1324 daniel-mar 1143
function uuid_numeric_value($uuid) {
1144
        $oid = uuid_to_oid($uuid);
1145
        if (!$oid) return false;
1146
        return substr($oid, strlen('2.25.'));
1147
}
1148
 
1149
function uuid_c_syntax($uuid) {
1150
        $uuid = str_replace('{', '', $uuid);
1151
        return '{ 0x' . substr($uuid, 0, 8) .
1152
                ', 0x' . substr($uuid, 9, 4) .
1153
                ', 0x' . substr($uuid, 14, 4) .
1154
                ', { 0x' . substr($uuid, 19, 2).
1155
                ', 0x' . substr($uuid, 21, 2) .
1156
                ', 0x' . substr($uuid, 24, 2) .
1157
                ', 0x' . substr($uuid, 26, 2) .
1158
                ', 0x' . substr($uuid, 28, 2) .
1159
                ', 0x' . substr($uuid, 30, 2) .
1160
                ', 0x' . substr($uuid, 32, 2) .
1161
                ', 0x' . substr($uuid, 34, 2) . ' } }';
1162
}
1163
 
1164
function gen_uuid($prefer_mac_address_based = true) {
1165
        $uuid = $prefer_mac_address_based ? gen_uuid_reordered()/*UUIDv6*/ : false;
1166
        if ($uuid === false) $uuid = gen_uuid_unix_epoch()/*UUIDv7*/;
604 daniel-mar 1167
        return $uuid;
1168
}
1169
 
1324 daniel-mar 1170
# --------------------------------------
1171
// Variant 1, Version 1 (Time based) UUID
1172
# --------------------------------------
1173
 
1174
function gen_uuid_v1() {
1175
        return gen_uuid_timebased();
1176
}
1177
function gen_uuid_timebased($force_php_implementation=false) {
604 daniel-mar 1178
        # On Debian: apt-get install php-uuid
1179
        # extension_loaded('uuid')
1324 daniel-mar 1180
        if (!$force_php_implementation && function_exists('uuid_create')) {
604 daniel-mar 1181
                # OSSP uuid extension like seen in php5-uuid at Debian 8
1182
                /*
1183
                $x = uuid_create($context);
1184
                uuid_make($context, UUID_MAKE_V1);
1185
                uuid_export($context, UUID_FMT_STR, $uuid);
1186
                return trim($uuid);
1187
                */
1188
 
1189
                # PECL uuid extension like seen in php-uuid at Debian 9
1190
                return trim(uuid_create(UUID_TYPE_TIME));
1191
        }
1192
 
1193
        # On Debian: apt-get install uuid-runtime
1324 daniel-mar 1194
        if (!$force_php_implementation && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
604 daniel-mar 1195
                $out = array();
1196
                $ec = -1;
1197
                exec('uuidgen -t 2>/dev/null', $out, $ec);
1198
                if ($ec == 0) return trim($out[0]);
1199
        }
1200
 
1201
        # If we hadn't any success yet, then implement the time based generation routine ourselves!
1202
        # Based on https://github.com/fredriklindberg/class.uuid.php/blob/master/class.uuid.php
1203
 
1204
        $uuid = array(
1205
                'time_low' => 0,                /* 32-bit */
1206
                'time_mid' => 0,                /* 16-bit */
1207
                'time_hi' => 0,                 /* 16-bit */
1208
                'clock_seq_hi' => 0,            /*  8-bit */
1209
                'clock_seq_low' => 0,           /*  8-bit */
1210
                'node' => array()               /* 48-bit */
1211
        );
1212
 
1213
        /*
1214
         * Get current time in 100 ns intervals. The magic value
1215
         * is the offset between UNIX epoch and the UUID UTC
1216
         * time base October 15, 1582.
1217
         */
1324 daniel-mar 1218
        if (time_nanosleep(0,100) !== true) usleep(1); // Wait 100ns, to make sure that the time part changes if multiple UUIDs are generated
604 daniel-mar 1219
        $tp = gettimeofday();
1324 daniel-mar 1220
        if (PHP_INT_SIZE == 4) {
1221
                $tp['sec'] = gmp_init($tp['sec'],10);
1222
                $tp['usec'] = gmp_init($tp['usec'],10);
1223
                $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));
1224
                $uuid['time_low'] = gmp_and($time, gmp_init('ffffffff',16));
1225
                $high = gmp_shiftr($time,32);
1226
                $uuid['time_mid'] = gmp_and($high, gmp_init('ffff',16));
1411 daniel-mar 1227
                $uuid['time_hi'] = gmp_intval(gmp_and(gmp_shiftr($high,16),gmp_init('fff',16))) | (1/*TimeBased*/ << 12);
1324 daniel-mar 1228
        } else {
1229
                $time = ($tp['sec'] * 10000000) + ($tp['usec'] * 10) + 0x01B21DD213814000;
1230
                $uuid['time_low'] = $time & 0xffffffff;
1231
                /* Work around PHP 32-bit bit-operation limits */
1232
                $high = intval($time / 0xffffffff);
1233
                $uuid['time_mid'] = $high & 0xffff;
1234
                $uuid['time_hi'] = (($high >> 16) & 0xfff) | (1/*TimeBased*/ << 12);
1235
        }
604 daniel-mar 1236
 
1237
        /*
1238
         * We don't support saved state information and generate
1239
         * a random clock sequence each time.
1240
         */
1324 daniel-mar 1241
        $uuid['clock_seq_hi'] = _random_int(0, 255) & 0b00111111 | 0b10000000; // set variant to 0b10__ (RFC 4122)
1261 daniel-mar 1242
        $uuid['clock_seq_low'] = _random_int(0, 255);
604 daniel-mar 1243
 
1244
        /*
1245
         * Node should be set to the 48-bit IEEE node identifier
1246
         */
1247
        $mac = get_mac_address();
1248
        if ($mac) {
1324 daniel-mar 1249
                $node = str_replace('-','',str_replace(':','',$mac));
604 daniel-mar 1250
                for ($i = 0; $i < 6; $i++) {
1251
                        $uuid['node'][$i] = hexdec(substr($node, $i*2, 2));
1252
                }
1324 daniel-mar 1253
        } else {
1254
                // If we cannot get a MAC address, then generate a random AAI
1336 daniel-mar 1255
                // RFC 4122 requires the multicast bit to be set, to make sure
1256
                // that a UUID from a system with network card never conflicts
1257
                // with a UUID from a system without network ard.
1258
                // We are additionally defining the other 3 bits as AAI,
1259
                // to avoid that we are misusing the CID or OUI from other vendors
1260
                // if we would create multicast ELI (based on CID) or EUI (based on OUI).
1261
                $uuid['node'] = explode('-', gen_aai(48, true/*Multicast*/));
1324 daniel-mar 1262
                $uuid['node'] = array_map('hexdec', $uuid['node']);
604 daniel-mar 1263
        }
1264
 
1324 daniel-mar 1265
        /*
1266
         * Now output the UUID
1267
         */
1268
        return sprintf(
1269
                '%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x',
1270
                ($uuid['time_low']), ($uuid['time_mid']), ($uuid['time_hi']),
1271
                $uuid['clock_seq_hi'], $uuid['clock_seq_low'],
1272
                $uuid['node'][0], $uuid['node'][1], $uuid['node'][2],
1273
                $uuid['node'][3], $uuid['node'][4], $uuid['node'][5]);
604 daniel-mar 1274
}
1275
 
1324 daniel-mar 1276
# --------------------------------------
1277
// Variant 1, Version 2 (DCE Security) UUID
1278
# --------------------------------------
604 daniel-mar 1279
 
1324 daniel-mar 1280
define('DCE_DOMAIN_PERSON', 0);
1281
define('DCE_DOMAIN_GROUP', 1);
1282
define('DCE_DOMAIN_ORG', 2);
1283
function gen_uuid_v2($domain, $id) {
1284
        return gen_uuid_dce($domain, $id);
1285
}
1286
function gen_uuid_dce($domain, $id) {
1287
        if (($domain ?? '') === '') throw new Exception("Domain ID missing");
1288
        if (!is_numeric($domain)) throw new Exception("Invalid Domain ID");
1376 daniel-mar 1289
        if (($domain < 0) || ($domain > 0xFF)) throw new Exception("Domain ID must be in range 0..255");
604 daniel-mar 1290
 
1324 daniel-mar 1291
        if (($id ?? '') === '') throw new Exception("ID value missing");
1292
        if (!is_numeric($id)) throw new Exception("Invalid ID value");
1376 daniel-mar 1293
        if (($id < 0) || ($id > 0xFFFFFFFF)) throw new Exception("ID value must be in range 0..4294967295");
604 daniel-mar 1294
 
1295
        # Start with a version 1 UUID
1296
        $uuid = gen_uuid_timebased();
1297
 
1324 daniel-mar 1298
        # Add Domain Number
604 daniel-mar 1299
        $uuid = str_pad(dechex($id), 8, '0', STR_PAD_LEFT) . substr($uuid, 8);
1300
 
1324 daniel-mar 1301
        # Add Domain (this overwrites part of the clock sequence)
604 daniel-mar 1302
        $uuid = substr($uuid,0,21) . str_pad(dechex($domain), 2, '0', STR_PAD_LEFT) . substr($uuid, 23);
1303
 
1304
        # Change version to 2
1305
        $uuid[14] = '2';
1306
 
1307
        return $uuid;
1308
}
1309
 
1324 daniel-mar 1310
# --------------------------------------
1311
// Variant 1, Version 3 (MD5 name based) UUID
1312
# --------------------------------------
1313
 
1314
function gen_uuid_v3($namespace_uuid, $name) {
1315
        return gen_uuid_md5_namebased($namespace_uuid, $name);
1316
}
604 daniel-mar 1317
function gen_uuid_md5_namebased($namespace_uuid, $name) {
1324 daniel-mar 1318
        if (($namespace_uuid ?? '') === '') throw new Exception("Namespace UUID missing");
1319
        if (!uuid_valid($namespace_uuid)) throw new Exception("Invalid namespace UUID '$namespace_uuid'");
1320
 
604 daniel-mar 1321
        $namespace_uuid = uuid_canonize($namespace_uuid);
1322
        $namespace_uuid = str_replace('-', '', $namespace_uuid);
1323
        $namespace_uuid = hex2bin($namespace_uuid);
1324
 
1325
        $hash = md5($namespace_uuid.$name);
1326
        $hash[12] = '3'; // Set version: 3 = MD5
1324 daniel-mar 1327
        $hash[16] = dechex(hexdec($hash[16]) & 0b0011 | 0b1000); // Set variant to "10xx" (RFC4122)
604 daniel-mar 1328
 
1329
        return substr($hash,  0, 8).'-'.
1330
               substr($hash,  8, 4).'-'.
1331
               substr($hash, 12, 4).'-'.
1332
               substr($hash, 16, 4).'-'.
1333
               substr($hash, 20, 12);
1334
}
1335
 
1324 daniel-mar 1336
# --------------------------------------
1337
// Variant 1, Version 4 (Random) UUID
1338
# --------------------------------------
1339
 
1340
function gen_uuid_v4() {
1341
        return gen_uuid_random();
1342
}
604 daniel-mar 1343
function gen_uuid_random() {
1344
        # On Windows: Requires
1345
        #    extension_dir = "C:\php-8.0.3-nts-Win32-vs16-x64\ext"
1346
        #    extension=com_dotnet
1347
        if (function_exists('com_create_guid')) {
1325 daniel-mar 1348
                $uuid = trim(com_create_guid(), '{}');
1349
                if (uuid_version($uuid) === '4') { // <-- just to make 100% sure that Windows's CoCreateGuid() did output UUIDv4
1350
                        return strtolower($uuid);
1351
                }
604 daniel-mar 1352
        }
1353
 
1354
        # On Debian: apt-get install php-uuid
1355
        # extension_loaded('uuid')
1356
        if (function_exists('uuid_create')) {
1357
                # OSSP uuid extension like seen in php5-uuid at Debian 8
1358
                /*
1359
                $x = uuid_create($context);
1360
                uuid_make($context, UUID_MAKE_V4);
1361
                uuid_export($context, UUID_FMT_STR, $uuid);
1362
                return trim($uuid);
1363
                */
1364
 
1365
                # PECL uuid extension like seen in php-uuid at Debian 9
1366
                return trim(uuid_create(UUID_TYPE_RANDOM));
1367
        }
1368
 
1369
        if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
1411 daniel-mar 1370
                # On Debian Jessie: UUID V4 (Random)
1371
                if (file_exists($uuidv4_file = '/proc/sys/kernel/random/uuid')) {
1372
                        $uuid = file_get_contents($uuidv4_file);
1373
                        if (uuid_version($uuid) === '4') { // <-- just to make 100% sure that it did output UUIDv4
1374
                                return $uuid;
1375
                        }
1376
                }
1377
 
604 daniel-mar 1378
                # On Debian: apt-get install uuid-runtime
1379
                $out = array();
1380
                $ec = -1;
1381
                exec('uuidgen -r 2>/dev/null', $out, $ec);
1382
                if ($ec == 0) return trim($out[0]);
1383
        }
1384
 
1385
        # Make the UUID by ourselves
1325 daniel-mar 1386
 
1387
        if (function_exists('openssl_random_pseudo_bytes')) {
1388
                // Source: https://www.php.net/manual/en/function.com-create-guid.php#119168
1389
                $data = openssl_random_pseudo_bytes(16);
1390
                $data[6] = chr(ord($data[6]) & 0x0f | 0x40);    // set version to 0100
1391
                $data[8] = chr(ord($data[8]) & 0x3f | 0x80);    // set bits 6-7 to 10
1392
                return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
1393
        } else {
1394
                // Source: http://rogerstringer.com/2013/11/15/generate-uuids-php
1395
                return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
1396
                        _random_int( 0, 0xffff ), _random_int( 0, 0xffff ),
1397
                        _random_int( 0, 0xffff ),
1398
                        _random_int( 0, 0x0fff ) | 0x4000,
1399
                        _random_int( 0, 0x3fff ) | 0x8000,
1400
                        _random_int( 0, 0xffff ), _random_int( 0, 0xffff ), _random_int( 0, 0xffff )
1401
                );
1402
        }
604 daniel-mar 1403
}
1404
 
1324 daniel-mar 1405
# --------------------------------------
1406
// Variant 1, Version 5 (SHA1 name based) UUID
1407
# --------------------------------------
1408
 
1409
function gen_uuid_v5($namespace_uuid, $name) {
1410
        return gen_uuid_sha1_namebased($namespace_uuid, $name);
1411
}
604 daniel-mar 1412
function gen_uuid_sha1_namebased($namespace_uuid, $name) {
1324 daniel-mar 1413
        if (($namespace_uuid ?? '') === '') throw new Exception("Namespace UUID missing");
1414
        if (!uuid_valid($namespace_uuid)) throw new Exception("Invalid namespace UUID '$namespace_uuid'");
1415
 
604 daniel-mar 1416
        $namespace_uuid = str_replace('-', '', $namespace_uuid);
1417
        $namespace_uuid = hex2bin($namespace_uuid);
1418
 
1419
        $hash = sha1($namespace_uuid.$name);
1420
        $hash[12] = '5'; // Set version: 5 = SHA1
1324 daniel-mar 1421
        $hash[16] = dechex(hexdec($hash[16]) & 0b0011 | 0b1000); // Set variant to "0b10__" (RFC4122/DCE1.1)
604 daniel-mar 1422
 
1423
        return substr($hash,  0, 8).'-'.
1424
               substr($hash,  8, 4).'-'.
1425
               substr($hash, 12, 4).'-'.
1426
               substr($hash, 16, 4).'-'.
1427
               substr($hash, 20, 12);
1428
}
1429
 
1324 daniel-mar 1430
# --------------------------------------
1431
// Variant 1, Version 6 (Reordered) UUID
1432
# --------------------------------------
1433
 
1434
function gen_uuid_v6() {
1435
        return gen_uuid_reordered();
604 daniel-mar 1436
}
1324 daniel-mar 1437
function gen_uuid_reordered() {
1438
        // Start with a UUIDv1
1439
        $uuid = gen_uuid_timebased();
604 daniel-mar 1440
 
1324 daniel-mar 1441
        // Convert to UUIDv6
1442
        return uuid1_to_uuid6($uuid);
604 daniel-mar 1443
}
1324 daniel-mar 1444
function uuid6_to_uuid1($hex) {
1445
        $hex = uuid_canonize($hex);
1446
        if ($hex === false) return false;
1447
        $hex = preg_replace('@[^0-9A-F]@i', '', $hex);
1448
        $hex = substr($hex, 7, 5).
1449
               substr($hex, 13, 3).
1450
               substr($hex, 3, 4).
1451
               '1' . substr($hex, 0, 3).
1452
               substr($hex, 16);
1453
        return substr($hex,  0, 8).'-'.
1454
               substr($hex,  8, 4).'-'.
1455
               substr($hex, 12, 4).'-'.
1456
               substr($hex, 16, 4).'-'.
1457
               substr($hex, 20, 12);
1458
}
1459
function uuid1_to_uuid6($hex) {
1460
        $hex = uuid_canonize($hex);
1461
        if ($hex === false) return false;
1462
        $hex = preg_replace('@[^0-9A-F]@i', '', $hex);
1463
        $hex = substr($hex, 13, 3).
1464
               substr($hex, 8, 4).
1465
               substr($hex, 0, 5).
1466
               '6' . substr($hex, 5, 3).
1467
               substr($hex, 16);
1468
        return substr($hex,  0, 8).'-'.
1469
               substr($hex,  8, 4).'-'.
1470
               substr($hex, 12, 4).'-'.
1471
               substr($hex, 16, 4).'-'.
1472
               substr($hex, 20, 12);
1473
}
604 daniel-mar 1474
 
1324 daniel-mar 1475
# --------------------------------------
1476
// Variant 1, Version 7 (Unix Epoch) UUID
1477
# --------------------------------------
604 daniel-mar 1478
 
1411 daniel-mar 1479
function gen_uuid_v7(int $num_ms_frac_bits=12) {
1480
        return gen_uuid_unix_epoch($num_ms_frac_bits);
1324 daniel-mar 1481
}
1411 daniel-mar 1482
function gen_uuid_unix_epoch(int $num_ms_frac_bits=12) {
1483
        $uuid_nibbles = '';
1324 daniel-mar 1484
 
1411 daniel-mar 1485
        // Add the timestamp (milliseconds Unix)
1324 daniel-mar 1486
        if (function_exists('gmp_init')) {
1487
                list($ms,$sec) = explode(' ', microtime(false));
1488
                $sec = gmp_init($sec, 10);
1489
                $ms = gmp_init(substr($ms,2,3), 10);
1490
                $unix_ts = gmp_strval(gmp_add(gmp_mul($sec, '1000'), $ms),16);
1491
        } else {
1411 daniel-mar 1492
                $unix_ts = dechex((int)ceil(microtime(true)*1000));
1324 daniel-mar 1493
        }
1494
        $unix_ts = str_pad($unix_ts, 12, '0', STR_PAD_LEFT);
1411 daniel-mar 1495
        $uuid_nibbles = $unix_ts;
1324 daniel-mar 1496
 
1411 daniel-mar 1497
        // Version = 7
1498
        $uuid_nibbles .= '7';
1324 daniel-mar 1499
 
1411 daniel-mar 1500
        // Optional: millisecond fraction (max 12 bits)
1501
        if (($num_ms_frac_bits < 0) || ($num_ms_frac_bits > 12)) throw new Exception("Invalid msec frac bits (must be 0..12)");
1502
        $resolution_ns = 1000000 / pow(2,$num_ms_frac_bits);
1503
        if ($num_ms_frac_bits > 0) {
1504
                $seconds_fraction = (float)explode(' ',microtime(false))[0]; // <sec=0>,<msec>
1505
 
1506
                $ms_fraction = $seconds_fraction * 1000; // <msec>,<us>
1507
                $ms_fraction -= floor($ms_fraction); // <msec=0>,<us>
1508
 
1509
                $ns_fraction = $ms_fraction * 1000000; // <ns>
1510
                $val = (int)ceil($ns_fraction / $resolution_ns);
1511
 
1512
                // Currently, for the output we only allow frac bits 0, 4, 8, 12 (0-3 nibbles),
1513
                // since UUIDs are usually sorted in their hex notation, and one of the main
1514
                // reasons for using the sub-millisecond fractions it to increase monotonicity
1515
                $num_nibbles = (int)ceil($num_ms_frac_bits/4);
1516
                $uuid_nibbles .= str_pad(dechex($val), $num_nibbles, '0', STR_PAD_LEFT);
1517
        }
1518
 
1519
        // TODO Not implemented: Optional counter (to be defined as parameter to this method)
1520
        // The counter bits need to be spread before and after the variant bits
1521
 
1522
        // Fill with random bits (and the variant bits)
1523
        $uuid = gen_uuid_random();
1524
        $uuid = str_replace('-', '', $uuid);
1525
        for ($i=0; $i<strlen($uuid_nibbles); $i++) $uuid[$i] = $uuid_nibbles[$i];
1526
 
1527
        // Wait to make sure that the time part changes if multiple UUIDs are generated
1528
        if (time_nanosleep(0,(int)ceil($resolution_ns)) !== true) usleep((int)ceil($resolution_ns/1000));
1529
 
1530
        // Output
1531
        return substr($uuid,  0, 8).'-'.
1532
               substr($uuid,  8, 4).'-'.
1533
               substr($uuid, 12, 4).'-'.
1534
               substr($uuid, 16, 4).'-'.
1535
               substr($uuid, 20, 12);
1324 daniel-mar 1536
}
1537
 
1538
# --------------------------------------
1539
// Variant 1, Version 8 (Custom) UUID
1540
# --------------------------------------
1541
 
1542
function gen_uuid_v8($block1_32bit, $block2_16bit, $block3_12bit, $block4_14bit, $block5_48bit) {
1543
        return gen_uuid_custom($block1_32bit, $block2_16bit, $block3_12bit, $block4_14bit, $block5_48bit);
1544
}
1545
function gen_uuid_custom($block1_32bit, $block2_16bit, $block3_12bit, $block4_14bit, $block5_48bit) {
1546
        if (preg_replace('@[0-9A-F]@i', '', $block1_32bit) != '') throw new Exception("Invalid data for block 1. Must be hex input");
1547
        if (preg_replace('@[0-9A-F]@i', '', $block2_16bit) != '') throw new Exception("Invalid data for block 2. Must be hex input");
1548
        if (preg_replace('@[0-9A-F]@i', '', $block3_12bit) != '') throw new Exception("Invalid data for block 3. Must be hex input");
1549
        if (preg_replace('@[0-9A-F]@i', '', $block4_14bit) != '') throw new Exception("Invalid data for block 4. Must be hex input");
1550
        if (preg_replace('@[0-9A-F]@i', '', $block5_48bit) != '') throw new Exception("Invalid data for block 5. Must be hex input");
1551
 
1552
        $block1 = str_pad(substr($block1_32bit, -8),  8, '0', STR_PAD_LEFT);
1553
        $block2 = str_pad(substr($block2_16bit, -4),  4, '0', STR_PAD_LEFT);
1554
        $block3 = str_pad(substr($block3_12bit, -4),  4, '0', STR_PAD_LEFT);
1555
        $block4 = str_pad(substr($block4_14bit, -4),  4, '0', STR_PAD_LEFT);
1556
        $block5 = str_pad(substr($block5_48bit,-12), 12, '0', STR_PAD_LEFT);
1557
 
1558
        $block3[0] = '8'; // Version 8 = Custom
1559
        $block4[0] = dechex(hexdec($block4[0]) & 0b0011 | 0b1000); // Variant 0b10__ = RFC4122
1560
 
1561
        return strtolower($block1.'-'.$block2.'-'.$block3.'-'.$block4.'-'.$block5);
1562
}
1563
 
1411 daniel-mar 1564
function gen_uuid_v8_namebased($hash_algo, $namespace_uuid, $name) {
1565
        if (($hash_algo ?? '') === '') throw new Exception("Hash algorithm argument missing");
1376 daniel-mar 1566
 
1567
        if (($namespace_uuid ?? '') === '') throw new Exception("Namespace UUID missing");
1568
        if (!uuid_valid($namespace_uuid)) throw new Exception("Invalid namespace UUID '$namespace_uuid'");
1569
 
1417 daniel-mar 1570
        $uuid1 = uuid_valid($hash_algo) ? hex2bin(str_replace('-','',uuid_canonize($hash_algo))) : ''; // old "hash space" concept (dropped in Internet Draft 12)
1376 daniel-mar 1571
        $uuid2 = hex2bin(str_replace('-','',uuid_canonize($namespace_uuid)));
1572
        $payload = $uuid1 . $uuid2 . $name;
1573
 
1411 daniel-mar 1574
        if (uuid_valid($hash_algo)) {
1417 daniel-mar 1575
                if (uuid_equal($hash_algo, '59031ca3-fbdb-47fb-9f6c-0f30e2e83145')) $hash_algo = 'sha224';
1576
                if (uuid_equal($hash_algo, '3fb32780-953c-4464-9cfd-e85dbbe9843d')) $hash_algo = 'sha256';
1577
                if (uuid_equal($hash_algo, 'e6800581-f333-484b-8778-601ff2b58da8')) $hash_algo = 'sha384';
1578
                if (uuid_equal($hash_algo, '0fde22f2-e7ba-4fd1-9753-9c2ea88fa3f9')) $hash_algo = 'sha512';
1579
                if (uuid_equal($hash_algo, '003c2038-c4fe-4b95-a672-0c26c1b79542')) $hash_algo = 'sha512/224';
1580
                if (uuid_equal($hash_algo, '9475ad00-3769-4c07-9642-5e7383732306')) $hash_algo = 'sha512/256';
1581
                if (uuid_equal($hash_algo, '9768761f-ac5a-419e-a180-7ca239e8025a')) $hash_algo = 'sha3-224';
1582
                if (uuid_equal($hash_algo, '2034d66b-4047-4553-8f80-70e593176877')) $hash_algo = 'sha3-256';
1583
                if (uuid_equal($hash_algo, '872fb339-2636-4bdd-bda6-b6dc2a82b1b3')) $hash_algo = 'sha3-384';
1584
                if (uuid_equal($hash_algo, 'a4920a5d-a8a6-426c-8d14-a6cafbe64c7b')) $hash_algo = 'sha3-512';
1585
                if (uuid_equal($hash_algo, '7ea218f6-629a-425f-9f88-7439d63296bb')) $hash_algo = 'shake128';
1586
                if (uuid_equal($hash_algo, '2e7fc6a4-2919-4edc-b0ba-7d7062ce4f0a')) $hash_algo = 'shake256';
1376 daniel-mar 1587
        }
1588
 
1411 daniel-mar 1589
        if ($hash_algo == 'shake128') $hash = shake128($payload, 16/*min. required bytes*/, false);
1590
        else if ($hash_algo == 'shake256') $hash = shake256($payload, 16/*min. required bytes*/, false);
1591
        else $hash = hash($hash_algo, $payload, false);
1592
 
1376 daniel-mar 1593
        if ($hash == null) {
1411 daniel-mar 1594
                throw new Exception("Unknown Hash Algorithm $hash_algo");
1376 daniel-mar 1595
        }
1596
 
1411 daniel-mar 1597
        $hash = str_pad($hash, 32, '0', STR_PAD_RIGHT); // fill short hashes with zeros to the right
1598
 
1376 daniel-mar 1599
        $hash[12] = '8'; // Set version: 8 = Custom
1600
        $hash[16] = dechex(hexdec($hash[16]) & 0b0011 | 0b1000); // Set variant to "0b10__" (RFC4122/DCE1.1)
1601
 
1602
        return substr($hash,  0, 8).'-'.
1603
               substr($hash,  8, 4).'-'.
1604
               substr($hash, 12, 4).'-'.
1605
               substr($hash, 16, 4).'-'.
1606
               substr($hash, 20, 12);
1607
}
1608
 
1469 daniel-mar 1609
/**
1610
 * The sorting of SQL Server is rather confusing and incompatible with UUIDv6 and UUIDv7.
1611
 * Therefore this method generates UUID which are sortable by SQL Server.
1612
 * Version 1: Resolution of 1 milliseconds, random part of 16 bits, local timezone, 48 zero bits "signature", NOT UUIDv8 conform.
1613
 * Version 2: Resolution of 1 milliseconds, random part of 16 bits, UTC time, 48 bit random "signature", UUIDv8 conform.
1614
 * C# implementation: https://gist.github.com/danielmarschall/7fafd270a3bc107d38e8449ce7420c25
1615
 * PHP implementation: https://github.com/danielmarschall/uuid_mac_utils/blob/master/includes/uuid_utils.inc.php
1616
 *
1617
 * @param int      $hickelUuidVersion (optional)
1618
 * @param DateTime $dt                (optional)
1619
 * @return string The UUID
1620
 */
1621
function gen_uuid_v8_sqlserver_sortable(int $hickelUuidVersion = 2, DateTime $dt = null): string {
1622
        // The sorting in SQL Server is like this:
1623
 
1624
        if ($dt == null) $dt = new DateTime();
1470 daniel-mar 1625
        if ($hickelUuidVersion >= 2) $dt->setTimeZone(new DateTimeZone("UTC"));
1469 daniel-mar 1626
 
1627
        // First Sort block 5, nibbles from left to right (i.e. 000000000001 < 000000000010 < ... < 010000000000 < 100000000000)
1628
        if ($hickelUuidVersion == 1) {
1629
                $block5 = "000000000000";
1630
        } else if ($hickelUuidVersion == 2) {
1631
                $block5 = "5ce32bd83b96";
1632
        } else {
1633
                throw new Exception("Invalid version");
1634
        }
1635
 
1636
        // Then: Sort block 4, nibbles from left to right
1637
        if ($hickelUuidVersion == 1) {
1638
                $year = $dt->format('Y');
1639
                $block4 = substr($year, 2, 2).substr($year, 0, 2); // Example: 0x2420 = 2024
1640
        } else {
1471 daniel-mar 1641
                $variant = 0x8; // First nibble needs to be 0b10_ (0x8-0xB) for RFC 9562. We use it to store 2 more random bits.
1469 daniel-mar 1642
                $unused2bits = 0; // Cannot be used for random, because it would affect the sorting
1643
                $year = $dt->format('Y');
1644
                $block4 = sprintf('%01x%03x', $variant + ($unused2bits & 0x3), $year);
1645
        }
1646
 
1647
        // Then: Sort block 3, bytes from right to left (i.e. 0100 < 1000 < 0001 < 0010)
1648
        if ($hickelUuidVersion == 1) {
1649
                $block3 = $dt->format('dm');
1650
        } else {
1651
                $uuidVersion = 8; // First nibble needs to be "8" for "UUIDv8 = Custom UUID"
1652
                $dayOfYear = intval($dt->format('z')) + 1; /* 1..366 */
1653
                $block3 = sprintf('%01x%03x', $uuidVersion, $dayOfYear);
1654
        }
1655
 
1656
        // Then: Sort block 2, bytes from right to left
1657
        if ($hickelUuidVersion == 1) {
1470 daniel-mar 1658
                $block2 = $dt->format('iH');
1469 daniel-mar 1659
        } else {
1470 daniel-mar 1660
                $minuteOfDay = (intval($dt->format('i')) + intval($dt->format('H')) * 60) + 1; // 1..1440
1469 daniel-mar 1661
                $block2 = sprintf('%04x', $minuteOfDay);
1662
        }
1663
 
1664
        // Then: Sort block 1, bytes from right to left
1665
        if ($hickelUuidVersion == 1) {
1666
                $millisecond8bits = ceil(($dt->format('v') / 999) * 255); // deviation -4ms..0ms
1667
                $rnd16bits = _random_int(0x0000, 0xFFFF-1);
1668
                $block1 = sprintf('%04x%02x', $rnd16bits, $millisecond8bits).$dt->format('s');
1669
        } else {
1670
                $millisecond8bits = round(($dt->format('v') / 999) * 255); // deviation -2ms..2ms
1671
                $rnd16bits = _random_int(0x0000, 0xFFFF);
1672
                $block1 = sprintf('%04x%02x%02x', $rnd16bits, $millisecond8bits, $dt->format('s'));
1673
        }
1674
 
1675
        $sleep_ms = (int)ceil(999 / 255); // Make sure that "millisecond" is not repeated on this system
1676
        if (time_nanosleep(0,$sleep_ms*1000*1000) !== true) usleep($sleep_ms*1000);
1677
 
1678
        return strtolower("$block1-$block2-$block3-$block4-$block5");
1679
}
1680
 
1324 daniel-mar 1681
# --------------------------------------
1682
 
604 daniel-mar 1683
// http://php.net/manual/de/function.hex2bin.php#113057
1324 daniel-mar 1684
if (!function_exists('hex2bin')) {
1685
    function hex2bin($str) {
604 daniel-mar 1686
        $sbin = "";
1324 daniel-mar 1687
        $len = strlen($str);
604 daniel-mar 1688
        for ( $i = 0; $i < $len; $i += 2 ) {
1324 daniel-mar 1689
            $sbin .= pack("H*", substr($str, $i, 2));
604 daniel-mar 1690
        }
1691
        return $sbin;
1692
    }
1693
}
1323 daniel-mar 1694
 
1324 daniel-mar 1695
// https://stackoverflow.com/questions/72127764/shift-right-left-bitwise-operators-in-php7-gmp-extension
1696
if (!function_exists('gmp_shiftl')) {
1697
    function gmp_shiftl($x,$n) { // shift left
1698
        return(gmp_mul($x,gmp_pow(2,$n)));
1699
    }
1323 daniel-mar 1700
}
1701
 
1324 daniel-mar 1702
if (!function_exists('gmp_shiftr')) {
1703
    function gmp_shiftr($x,$n) { // shift right
1704
        return(gmp_div_q($x,gmp_pow(2,$n)));
1705
    }
1323 daniel-mar 1706
}
1376 daniel-mar 1707
 
1708
function shake128(string $msg, int $outputLength=512, bool $binary=false): string {
1709
        include_once __DIR__.'/SHA3.php';
1710
        $sponge = SHA3::init(SHA3::SHAKE128);
1711
        $sponge->absorb($msg);
1712
        $bin = $sponge->squeeze($outputLength);
1713
        return $binary ? $bin : bin2hex($bin);
1714
}
1715
 
1716
function shake256(string $msg, int $outputLength=512, bool $binary=false): string {
1717
        include_once __DIR__.'/SHA3.php';
1718
        $sponge = SHA3::init(SHA3::SHAKE256);
1719
        $sponge->absorb($msg);
1720
        $bin = $sponge->squeeze($outputLength);
1721
        return $binary ? $bin : bin2hex($bin);
1722
}
1469 daniel-mar 1723
 
1724
/**
1725
 * Converts the day of year of a year into a DateTime object
1726
 * @param int $year The year
1727
 * @param int $dayOfYear The day of year (value 1 till 365 or 1 till 366 for leap years)
1728
 * @return \DateTime The resulting date
1729
 */
1730
function getDateFromDay(int $year, int $dayOfYear): \DateTime {
1731
        // Note: "Y z" and "z Y" make a difference for leap years (last tested with PHP 8.0.3)
1732
        $date = \DateTime::createFromFormat('Y z', strval($year) . ' ' . strval($dayOfYear-1));
1733
        return $date;
1734
}
1735