Subversion Repositories uuid_mac_utils

Rev

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

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