Subversion Repositories oidinfo_api

Rev

Rev 36 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * UUID utils for PHP
  5.  * Copyright 2011 - 2024 Daniel Marschall, ViaThinkSoft
  6.  * Version 2024-03-09
  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)
  22. // TODO: If we are on 64 bit PHP (PHP_INT_SIZE > 4), then replace GMP with normal PHP operations
  23.  
  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;
  26.  
  27. if (file_exists($f = __DIR__ . '/gmp_supplement.inc.php')) include_once $f;
  28. else if (file_exists($f = __DIR__ . '/gmp_supplement.inc.phps')) include_once $f;
  29.  
  30. if (file_exists($f = __DIR__ . '/OidDerConverter.class.php')) include_once $f;
  31. else if (file_exists($f = __DIR__ . '/OidDerConverter.class.phps')) include_once $f;
  32.  
  33. // Note: The RFC allows various notations as payload, not a strict notation constraint
  34. const UUID_NAMEBASED_NS_DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
  35. const UUID_NAMEBASED_NS_URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
  36. const UUID_NAMEBASED_NS_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8';
  37. const UUID_NAMEBASED_NS_X500_DN = '6ba7b814-9dad-11d1-80b4-00c04fd430c8';
  38.  
  39. if (!function_exists('_random_int')) {
  40.         function _random_int($min, $max) {
  41.                 // This function tries a CSRNG and falls back to a RNG if no CSRNG is available
  42.                 try {
  43.                         return random_int($min, $max);
  44.                 } catch (Exception $e) {
  45.                         return mt_rand($min, $max);
  46.                 }
  47.         }
  48. }
  49.  
  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.  
  57.         $uuid = preg_replace('@[0-9A-F]@i', '', $uuid);
  58.  
  59.         return ($uuid == '');
  60. }
  61.  
  62. function uuid_equal($uuid1, $uuid2) {
  63.         $uuid1 = uuid_canonize($uuid1);
  64.         if (!$uuid1) return false;
  65.         $uuid2 = uuid_canonize($uuid2);
  66.         if (!$uuid2) return false;
  67.         return $uuid1 === $uuid2;
  68. }
  69.  
  70. function uuid_version($uuid) {
  71.         $uuid = uuid_canonize($uuid);
  72.         if (!$uuid) return false;
  73.         return substr($uuid, 19, 1);
  74. }
  75.  
  76. function uuid_info($uuid, $echo=true) {
  77.         if (!uuid_valid($uuid)) return false;
  78.  
  79.         $oid = uuid_to_oid($uuid);
  80.  
  81.         echo sprintf("%-32s %s\n", "Your input:", $uuid);
  82.         echo "\n";
  83.         echo "<u>Various notations:</u>\n";
  84.         echo "\n";
  85.         echo sprintf("%-32s %s\n", "URN:", 'urn:uuid:' . strtolower(oid_to_uuid(uuid_to_oid($uuid))));
  86.         echo sprintf("%-32s %s\n", "URI:", 'uuid:' . strtolower(oid_to_uuid(uuid_to_oid($uuid))));
  87.         echo sprintf("%-32s %s\n", "Microsoft GUID syntax:", '{' . strtoupper(oid_to_uuid(uuid_to_oid($uuid))) . '}');
  88.         echo sprintf("%-32s %s\n", "C++ struct syntax:", uuid_c_syntax($uuid));
  89.         echo "\n";
  90.  
  91.         echo sprintf("%-32s %s\n", "As OID (ISO/ITU-T 128 bits):", $oid=uuid_to_oid($uuid, '2.25'));
  92.         # Removed because it is too much information (we would also need to add this to the other OIDs too)
  93.         #if (class_exists('OidDerConverter')) {
  94.         #       echo sprintf("%-32s %s\n", "DER encoding of OID:", OidDerConverter::hexarrayToStr(OidDerConverter::oidToDER($oid)));
  95.         #}
  96.         echo sprintf("%-32s %s\n", "As OID (Microsoft):", $oid=uuid_to_oid($uuid, '1.2.840.113556.1.8000.2554'));
  97.         echo sprintf("%-32s %s\n", "As OID (Waterjuice 2x64 bits):", $oid=uuid_to_oid($uuid, '1.3.6.1.4.1.54392.1'));
  98.         echo sprintf("%-32s %s\n", "As OID (Waterjuice 4x32 bits):", $oid=uuid_to_oid($uuid, '1.3.6.1.4.1.54392.2'));
  99.         echo sprintf("%-32s %s\n", "As OID (Waterjuice 8x16 bits):", $oid=uuid_to_oid($uuid, '1.3.6.1.4.1.54392.3'));
  100.  
  101.         echo "\n";
  102.  
  103.         echo "<u>Interpretation of the UUID:</u>\n\n";
  104.  
  105.         if (!$echo) ob_start();
  106.  
  107.         #$uuid = trim($uuid);
  108.         # $uuid = str_replace(array('-', '{', '}'), '', $uuid);
  109.         $uuid = strtolower($uuid);
  110.         $uuid = preg_replace('@[^0-9A-F]@i', '', $uuid);
  111.  
  112.         $x = hexdec(substr($uuid, 16, 1));
  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;
  117.         else $variant = -1; // should not happen
  118.  
  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.  
  128.         switch ($variant) {
  129.                 case 0:
  130.                         echo sprintf("%-32s %s\n", "Variant:", "[0b0__] Network Computing System (NCS)");
  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.  
  146.                         /*
  147.                         Variant 0 UUID
  148.                         - 32 bit High Time
  149.                         - 16 bit Low Time
  150.                         - 16 bit Reserved
  151.                         -  1 bit Variant (fix 0b0)
  152.                         -  7 bit Family
  153.                         - 56 bit Node
  154.                         */
  155.  
  156.                         // Example of an UUID: 333a2276-0000-0000-0d00-00809c000000
  157.  
  158.                         // TODO: also show legacy format, e.g. 458487b55160.02.c0.64.02.03.00.00.00
  159.  
  160.                         # see also some notes at See https://github.com/cjsv/uuid/blob/master/Doc
  161.  
  162.                         /*
  163.                         NOTE: A generator is not possible, because there are no timestamps left!
  164.                         The last possible timestamp was:
  165.                             [0xFFFFFFFFFFFF] 2015-09-05 05:58:26'210655 GMT
  166.                         That is in the following UUID:
  167.                             ffffffff-ffff-0000-027f-000001000000
  168.                         Current timestamp generator:
  169.                             echo dechex(round((microtime(true)+315532800)*250000));
  170.                         */
  171.  
  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);
  178.                         $ts = gmp_add($ts, gmp_init("78883200000000", 10));
  179.                         $ms = gmp_mod($ts, gmp_init("250000", 10));
  180.                         $ts = gmp_div($ts, gmp_init("250000", 10));
  181.                         $ts = gmp_strval($ts, 10);
  182.                         $ms = gmp_strval($ms, 10);
  183.                         $ts = gmdate('Y-m-d H:i:s', intval($ts))."'".str_pad($ms, 6/*us*/, '0', STR_PAD_LEFT).' GMT';
  184.                         echo sprintf("%-32s %s\n", "Timestamp:", "[0x$timestamp] $ts");
  185.  
  186.                         $reserved = substr($uuid, 12, 4);
  187.                         echo sprintf("%-32s %s\n", "Reserved:", "[0x$reserved]");
  188.  
  189.                         $family_hex = substr($uuid, 16, 2);
  190.                         $family_dec = hexdec($family_hex);
  191.                         $nodeid_hex = substr($uuid, 18, 14);
  192.                         $nodeid_dec = hexdec($nodeid_hex);
  193.  
  194.                         // Sources:
  195.                         // - https://bitsavers.org/pdf/ibm/rs6000/aix_3.0/SC23-2206-0_AIX_Version_3_for_RS6000_Communications_Programming_Concepts_199003.pdf
  196.                         // - (For comparison) https://github.com/uuid6/uuid6-ietf-draft/issues/26#issuecomment-1062164457
  197.                         // - (For comparison) https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.addressfamily?view=net-7.0 [numbers 0..13 are mostly identical]
  198.  
  199.                         if ($family_dec == 0) {
  200.                                 # Microsoft's AdressFamily: Unspecified 0       Unspecified address family.
  201.                                 # AIX 3.0 Manual:  0   unspec = Unspecified
  202.                                 $family_name = 'socket_$unspec (Unspecified)';
  203.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  204.                         }
  205.                         else if ($family_dec == 1) {
  206.                                 # Microsoft's AdressFamily: Unix        1       Unix local to host address.
  207.                                 # AIX 3.0 Manual:  1   unix = Local to host (pipes, portals)
  208.                                 $family_name = 'socket_$unix (Local to host, e.g. pipes, portals)';
  209.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  210.                         }
  211.                         else if ($family_dec == 2) {
  212.                                 # Microsoft's AdressFamily: InterNetwork        2       Address for IP version 4.
  213.                                 # AIX 3.0 Manual:  2   ip = Internet Protocols
  214.                                 $family_name = 'socket_$internet (Internet Protocols, e.g. IPv4)';
  215.                                 // https://www.ibm.com/docs/en/aix/7.1?topic=u-uuid-gen-command-ncs (AIX 7.1) shows the following example output for /etc/ncs/uuid_gen -P
  216.                                 // := [
  217.                                 //    time_high := 16#458487df,
  218.                                 //    time_low := 16#9fb2,
  219.                                 //    reserved := 16#000,
  220.                                 //    family := chr(16#02),
  221.                                 //    host := [chr(16#c0), chr(16#64), chr(16#02), chr(16#03),
  222.                                 //             chr(16#00), chr(16#00), chr(16#00)]
  223.                                 //    ]
  224.                                 // This means that the IP address is 32 bits hex, and 32 bits are unused
  225.                                 $nodeid_desc = hexdec(substr($nodeid_hex,0,2)).'.'.
  226.                                                hexdec(substr($nodeid_hex,2,2)).'.'.
  227.                                                hexdec(substr($nodeid_hex,4,2)).'.'.
  228.                                                hexdec(substr($nodeid_hex,6,2));
  229.                                 $rest = substr($nodeid_hex,8,6);
  230.                                 if ($rest != '000000') $nodeid_desc .= " + unexpected rest 0x$rest";
  231.                         }
  232.                         else if ($family_dec == 3) {
  233.                                 # Microsoft's AdressFamily: ImpLink     3       ARPANET IMP address.
  234.                                 # AIX 3.0 Manual:  3   implink = ARPANET imp addresses
  235.                                 $family_name = 'socket_$implink (ARPANET imp addresses)';
  236.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  237.                         }
  238.                         else if ($family_dec == 4) {
  239.                                 # Microsoft's AdressFamily: Pup 4       Address for PUP protocols.
  240.                                 # AIX 3.0 Manual:  4   pup = Pup protocols (for example, BSP)
  241.                                 $family_name = 'socket_$pup (Pup protocols, e.g. BSP)';
  242.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  243.                         }
  244.                         else if ($family_dec == 5) {
  245.                                 # Microsoft's AdressFamily: Chaos       5       Address for MIT CHAOS protocols.
  246.                                 # AIX 3.0 Manual:  5   chaos = MIT CHAOS protocols
  247.                                 $family_name = 'socket_$chaos (MIT CHAOS protocols)';
  248.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  249.                         }
  250.                         else if ($family_dec == 6) {
  251.                                 # Microsoft's AdressFamily: NS  6       Address for Xerox NS protocols.
  252.                                 # Microsoft's AdressFamily: Ipx 6       IPX or SPX address.
  253.                                 # AIX 3.0 Manual:  6   ns = XEROX NS protocols
  254.                                 $family_name = 'socket_$ns (XEROX NS protocols)';
  255.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  256.                         }
  257.                         else if ($family_dec == 7) {
  258.                                 # Microsoft's AdressFamily: Osi 7       Address for OSI protocols.
  259.                                 # Microsoft's AdressFamily: Iso 7       Address for ISO protocols.
  260.                                 # AIX 3.0 Manual:  7   nbs = NBS protocols
  261.                                 $family_name = 'socket_$nbs (NBS protocols)';
  262.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  263.                         }
  264.                         else if ($family_dec == 8) {
  265.                                 # Microsoft's AdressFamily: Ecma        8       European Computer Manufacturers Association (ECMA) address.
  266.                                 # AIX 3.0 Manual:  8   ecma = European computer manufacturers
  267.                                 $family_name = 'socket_$ecma (European computer manufacturers protocols)';
  268.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  269.                         }
  270.                         else if ($family_dec == 9) {
  271.                                 # Microsoft's AdressFamily: DataKit     9       Address for Datakit protocols.
  272.                                 # AIX 3.0 Manual:  9   datakit = Datakit protocols
  273.                                 $family_name = 'socket_$datakit (Datakit protocols)';
  274.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  275.                         }
  276.                         else if ($family_dec == 10) {
  277.                                 # Microsoft's AdressFamily: Ccitt       10      Addresses for CCITT protocols, such as X.25.
  278.                                 # AIX 3.0 Manual:  A   ccitt = CCITT protocols (for example, X.25)
  279.                                 $family_name = 'socket_$ccitt (CCITT protocols, e.g. X.25)';
  280.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  281.                         }
  282.                         else if ($family_dec == 11) {
  283.                                 # Microsoft's AdressFamily: Sna 11      IBM SNA address.
  284.                                 # AIX 3.0 Manual:  B   sna = IBM SNA
  285.                                 $family_name = 'socket_$sna (IBM SNA)';
  286.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  287.                         }
  288.                         else if ($family_dec == 12) {
  289.                                 # Microsoft's AdressFamily: DecNet      12      DECnet address.
  290.                                 # AIX 3.0 Manual:  C   unspec2 = Unspecified
  291.                                 $family_name = 'socket_$unspec2 (Unspecified)';
  292.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  293.                         }
  294.                         else if ($family_dec == 13) {
  295.                                 # Microsoft's AdressFamily: DataLink    13      Direct data-link interface address.
  296.                                 # AIX 3.0 Manual:  D   dds = Domain DDS protocol
  297.                                 # Some also call this "Data Link" ... Is that correct?
  298.                                 $family_name = 'socket_$dds (Domain DDS protocol)';
  299.                                 // https://www.ibm.com/docs/en/aix/7.1?topic=u-uuid-gen-command-ncs (AIX 7.1) shows the following example output for /etc/ncs/uuid_gen -C
  300.                                 // = { 0x34dc23af,
  301.                                 //    0xf000,
  302.                                 //    0x0000,
  303.                                 //    0x0d,
  304.                                 //    {0x00, 0x00, 0x7c, 0x5f, 0x00, 0x00, 0x00} };
  305.                                 // https://github.com/cjsv/uuid/blob/master/Doc writes:
  306.                                 //    "Family 13 (dds) looks like node is 00 | nnnnnn 000000."
  307.  
  308.                                 $nodeid_desc = '';
  309.  
  310.                                 $start = substr($nodeid_hex,0,2);
  311.                                 if ($start != '00') $nodeid_desc .= "unexpected start 0x$start + ";
  312.  
  313.                                 $nodeid_desc .= ($nodeid_dec >> 24) & 0xFFFFFF;
  314.  
  315.                                 $rest = substr($nodeid_hex,8,6);
  316.                                 if ($rest != '000000') $nodeid_desc .= " + unexpected rest 0x$rest";
  317.                         } else {
  318.                                 $family_name = "Unknown (Family $family_dec)"; # There are probably no more families
  319.                                 $nodeid_desc = "Unknown";
  320.                         }
  321.                         echo sprintf("%-32s %s\n", "Family:", "[0x$family_hex] $family_name");
  322.  
  323.                         echo sprintf("%-32s %s\n", "Node ID:", "[0x$nodeid_hex] $nodeid_desc");
  324.  
  325.                         break;
  326.                 case 1:
  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.  
  329.                         $version = hexdec(substr($uuid, 12, 1));
  330.  
  331.                         if ($version <= 2) {
  332.                                 echo sprintf("%-32s %s\n", "Variant:", "[0b10_] RFC 4122 (Leach-Mealling-Salz) / DCE 1.1");
  333.                         } else if (($version >= 3) && ($version <= 5)) {
  334.                                 echo sprintf("%-32s %s\n", "Variant:", "[0b10_] RFC 4122 (Leach-Mealling-Salz)");
  335.                         } else if (($version >= 6) && ($version <= 8)) {
  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
  337.                         } else {
  338.                                 echo sprintf("%-32s %s\n", "Variant:", "[0b10_] Unknown RFC");
  339.                         }
  340.  
  341.                         switch ($version) {
  342.                                 case 6:
  343.                                         /*
  344.                                         Variant 1, Version 6 UUID
  345.                                         - 48 bit High Time
  346.                                         -  4 bit Version (fix 0x6)
  347.                                         - 12 bit Low Time
  348.                                         -  2 bit Variant (fix 0b10)
  349.                                         -  6 bit Clock Sequence High
  350.                                         -  8 bit Clock Sequence Low
  351.                                         - 48 bit MAC Address
  352.                                         */
  353.                                         echo sprintf("%-32s %s\n", "Version:", "[0x6] Reordered Time-Based");
  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 */
  363.                                 case 1:
  364.                                         /*
  365.                                         Variant 1, Version 1 UUID
  366.                                         - 32 bit Low Time
  367.                                         - 16 bit Mid Time
  368.                                         -  4 bit Version (fix 0x1)
  369.                                         - 12 bit High Time
  370.                                         -  2 bit Variant (fix 0b10)
  371.                                         -  6 bit Clock Sequence High
  372.                                         -  8 bit Clock Sequence Low
  373.                                         - 48 bit MAC Address
  374.                                         */
  375.  
  376.                                         if ($version == 1) echo sprintf("%-32s %s\n", "Version:", "[0x1] Time-based with unique host identifier");
  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);
  382.                                         $ts = gmp_sub($ts, gmp_init("122192928000000000", 10));
  383.                                         $ms = gmp_mod($ts, gmp_init("10000000", 10));
  384.                                         $ts = gmp_div($ts, gmp_init("10000000", 10));
  385.                                         $ts = gmp_strval($ts, 10);
  386.                                         $ms = gmp_strval($ms, 10);
  387.                                         $ts = gmdate('Y-m-d H:i:s', intval($ts))."'".str_pad($ms, 7/*0.1us*/, '0', STR_PAD_LEFT).' GMT';
  388.                                         echo sprintf("%-32s %s\n", "Timestamp:", "[0x$timestamp] $ts");
  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);
  393.                                         $hex = '<abbr title="The highest 2 bits are used by the UUID variant (10xx)">'.$hex[0].'</abbr>'.substr($hex,1);
  394.                                         echo sprintf("%-32s %s\n", "Clock ID:", "[0x$hex] $dec");
  395.  
  396.                                         $x = substr($uuid, 20, 12);
  397.                                         $nodeid = '';
  398.                                         for ($i=0; $i<6; $i++) {
  399.                                                 $nodeid .= substr($x, $i*2, 2);
  400.                                                 if ($i != 5) $nodeid .= '-';
  401.                                         }
  402.                                         $nodeid = strtoupper($nodeid);
  403.                                         echo sprintf("%-32s %s\n", "Node ID:", "[0x$x] $nodeid");
  404.  
  405.                                         echo "\n<u>In case that this Node ID is a MAC address, here is the interpretation of that MAC address:</u>\n\n";
  406.                                         decode_mac(strtoupper($nodeid));
  407.  
  408.                                         break;
  409.                                 case 2:
  410.                                         /*
  411.                                         Variant 1, Version 2 UUID
  412.                                         - 32 bit Local Domain Number
  413.                                         - 16 bit Mid Time
  414.                                         -  4 bit Version (fix 0x2)
  415.                                         - 12 bit High Time
  416.                                         -  2 bit Variant (fix 0b10)
  417.                                         -  6 bit Clock Sequence
  418.                                         -  8 bit Local Domain
  419.                                         - 48 bit MAC Address
  420.                                         */
  421.  
  422.                                         // see also https://unicorn-utterances.com/posts/what-happened-to-uuid-v2
  423.  
  424.                                         echo sprintf("%-32s %s\n", "Version:", "[0x2] DCE Security version");
  425.  
  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);
  428.                                         if ($x == '00') $domain_info = 'Person (e.g. POSIX UID)';
  429.                                         else if ($x == '01') $domain_info = 'Group (e.g. POSIX GID)';
  430.                                         else if ($x == '02') $domain_info = 'Organization';
  431.                                         else $domain_info = 'site-defined (Domain '.hexdec($x).')';
  432.                                         echo sprintf("%-32s %s\n", "Local Domain:", "[0x$x] $domain_info");
  433.  
  434.                                         # The time_low field (which represents an integer in the range [0, 232-1]) is interpreted as a local-ID; that is, an identifier (within the domain specified by clock_seq_low) meaningful to the local host. In the particular case of a POSIX host, when combined with a POSIX UID or POSIX GID domain in the clock_seq_low field (above), the time_low field represents a POSIX UID or POSIX GID, respectively.
  435.                                         $x = substr($uuid, 0, 8);
  436.                                         $dec = hexdec($x);
  437.                                         echo sprintf("%-32s %s\n", "Local Domain Number:", "[0x$x] $dec");
  438.  
  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);
  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';
  449.  
  450.                                         $timestamp = substr($uuid, 13, 3).substr($uuid, 8, 4).'FFFFFFFF';
  451.                                         $ts = gmp_init($timestamp, 16);
  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';
  458.  
  459.                                         $timestamp = substr($uuid, 13, 3).substr($uuid, 8, 4)/*.'xxxxxxxx'*/;
  460.                                         echo sprintf("%-32s %s\n", "Timestamp:", "[0x$timestamp] $ts_min - $ts_max");
  461.  
  462.                                         $x = hexdec(substr($uuid, 16, 2));
  463.                                         $dec = $x & 0x3F; // The highest 2 bits are used by "variant" (10xx)
  464.                                         $hex = substr($uuid, 16, 2);
  465.                                         $hex = '<abbr title="The highest 2 bits are used by the UUID variant (10xx)">'.$hex[0].'</abbr>'.substr($hex,1);
  466.                                         echo sprintf("%-32s %s\n", "Clock ID:", "[0x$hex] $dec");
  467.  
  468.                                         $x = substr($uuid, 20, 12);
  469.                                         $nodeid = '';
  470.                                         for ($i=0; $i<6; $i++) {
  471.                                                 $nodeid .= substr($x, $i*2, 2);
  472.                                                 if ($i != 5) $nodeid .= '-';
  473.                                         }
  474.                                         $nodeid = strtoupper($nodeid);
  475.                                         echo sprintf("%-32s %s\n", "Node ID:", "[0x$x] $nodeid");
  476.  
  477.                                         echo "\n<u>In case that this Node ID is a MAC address, here is the interpretation of that MAC address:</u>\n\n";
  478.                                         decode_mac(strtoupper($nodeid));
  479.  
  480.                                         break;
  481.                                 case 3:
  482.                                         /*
  483.                                         Variant 1, Version 3 UUID
  484.                                         - 48 bit Hash High
  485.                                         -  4 bit Version (fix 0x3)
  486.                                         - 12 bit Hash Mid
  487.                                         -  2 bit Variant (fix 0b10)
  488.                                         - 62 bit Hash Low
  489.                                         */
  490.  
  491.                                         echo sprintf("%-32s %s\n", "Version:", "[0x3] Name-based (MD5 hash)");
  492.  
  493.                                         $hash = str_replace('-', '', strtolower($uuid));
  494.  
  495.                                         $hash[12] = '?'; // was overwritten by version
  496.  
  497.                                         $var16a = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b0000));
  498.                                         $var16b = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b0100));
  499.                                         $var16c = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b1000));
  500.                                         $var16d = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b1100));
  501.                                         $hash[16] = '?'; // was partially overwritten by variant
  502.  
  503.                                         $p = 16;
  504.                                         $hash = substr($hash,0,$p)."<abbr title=\"$var16a, $var16b, $var16c, or $var16d\">".substr($hash,$p,1).'</abbr>'.substr($hash,$p+1);
  505.                                         echo sprintf("%-32s %s\n", "MD5(Namespace+Subject):", "[0x$hash]");
  506.  
  507.                                         break;
  508.                                 case 4:
  509.                                         /*
  510.                                         Variant 1, Version 4 UUID
  511.                                         - 48 bit Random High
  512.                                         -  4 bit Version (fix 0x4)
  513.                                         - 12 bit Random Mid
  514.                                         -  2 bit Variant (fix 0b10)
  515.                                         - 62 bit Random Low
  516.                                         */
  517.  
  518.                                         echo sprintf("%-32s %s\n", "Version:", "[0x4] Random");
  519.  
  520.                                         $rand_line1 = '';
  521.                                         $rand_line2 = '';
  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) {
  527.                                                         // was overwritten by version
  528.                                                         $bin[0] = '?';
  529.                                                         $bin[1] = '?';
  530.                                                         $bin[2] = '?';
  531.                                                         $bin[3] = '?';
  532.                                                 } else if ($i == 8) {
  533.                                                         // was partially overwritten by variant
  534.                                                         $bin[0] = '?';
  535.                                                         $bin[1] = '?';
  536.                                                 }
  537.  
  538.                                                 if ($i<8) $rand_line1 .= "$bin ";
  539.                                                 if ($i>=8) $rand_line2 .= "$bin ";
  540.                                         }
  541.                                         echo sprintf("%-32s %s\n", "Random bits:", trim($rand_line1));
  542.                                         echo sprintf("%-32s %s\n", "",             trim($rand_line2));
  543.  
  544.                                         $rand_bytes = str_replace('-', '', strtolower($uuid));
  545.                                         $rand_bytes[12] = '?'; // was overwritten by version
  546.                                         $var16a = strtoupper(dechex(hexdec($rand_bytes[16]) & 0b0011 | 0b0000));
  547.                                         $var16b = strtoupper(dechex(hexdec($rand_bytes[16]) & 0b0011 | 0b0100));
  548.                                         $var16c = strtoupper(dechex(hexdec($rand_bytes[16]) & 0b0011 | 0b1000));
  549.                                         $var16d = strtoupper(dechex(hexdec($rand_bytes[16]) & 0b0011 | 0b1100));
  550.                                         $rand_bytes[16] = '?'; // was partially overwritten by variant
  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);
  554.                                         echo sprintf("%-32s %s\n", "Random bytes:", "[0x$rand_bytes]");
  555.  
  556.                                         break;
  557.                                 case 5:
  558.                                         /*
  559.                                         Variant 1, Version 5 UUID
  560.                                         - 48 bit Hash High
  561.                                         -  4 bit Version (fix 0x5)
  562.                                         - 12 bit Hash Mid
  563.                                         -  2 bit Variant (fix 0b10)
  564.                                         - 62 bit Hash Low
  565.                                         */
  566.  
  567.                                         echo sprintf("%-32s %s\n", "Version:", "[0x5] Name-based (SHA-1 hash)");
  568.  
  569.                                         $hash = str_replace('-', '', strtolower($uuid));
  570.  
  571.                                         $hash[12] = '?'; // was overwritten by version
  572.  
  573.                                         $var16a = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b0000));
  574.                                         $var16b = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b0100));
  575.                                         $var16c = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b1000));
  576.                                         $var16d = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b1100));
  577.                                         $hash[16] = '?'; // was partially overwritten by variant
  578.  
  579.                                         $hash .= '????????'; // was cut off
  580.  
  581.                                         $p = 16;
  582.                                         $hash = substr($hash,0,$p)."<abbr title=\"$var16a, $var16b, $var16c, or $var16d\">".substr($hash,$p,1).'</abbr>'.substr($hash,$p+1);
  583.                                         echo sprintf("%-32s %s\n", "SHA1(Namespace+Subject):", "[0x$hash]");
  584.  
  585.                                         break;
  586.                                 case 7:
  587.                                         /*
  588.                                         Variant 1, Version 7 UUID
  589.                                         - 48 bit Unix Time in milliseconds
  590.                                         -  4 bit Version (fix 0x7)
  591.                                         - 12 bit Data
  592.                                         -  2 bit Variant (fix 0b10)
  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
  602.                                         */
  603.  
  604.                                         echo sprintf("%-32s %s\n", "Version:", "[0x7] Unix Epoch Time");
  605.  
  606.                                         $timestamp = substr($uuid, 0, 12);
  607.  
  608.                                         // Timestamp: Split into seconds and milliseconds
  609.                                         $ts = gmp_init($timestamp, 16);
  610.                                         $ms = gmp_mod($ts, gmp_init("1000", 10));
  611.                                         $ts = gmp_div($ts, gmp_init("1000", 10));
  612.                                         $ts = gmp_strval($ts, 10);
  613.                                         $ms = gmp_strval($ms, 10);
  614.                                         $ts = gmdate('Y-m-d H:i:s', intval($ts))."'".str_pad($ms, 3/*ms*/, '0', STR_PAD_LEFT).' GMT';
  615.                                         echo sprintf("%-32s %s\n", "Timestamp:", "[0x$timestamp] $ts");
  616.  
  617.                                         $rand = '';
  618.                                         for ($i=6; $i<16; $i++) {
  619.                                                 $bin = base_convert(substr($uuid, $i*2, 2), 16, 2);
  620.                                                 $bin = str_pad($bin, 8, "0", STR_PAD_LEFT);
  621.  
  622.                                                 if ($i == 6) {
  623.                                                         // was overwritten by version
  624.                                                         $bin[0] = '?';
  625.                                                         $bin[1] = '?';
  626.                                                         $bin[2] = '?';
  627.                                                         $bin[3] = '?';
  628.                                                 } else if ($i == 8) {
  629.                                                         // was partially overwritten by variant
  630.                                                         $bin[0] = '?';
  631.                                                         $bin[1] = '?';
  632.                                                 }
  633.  
  634.                                                 $rand .= "$bin ";
  635.                                         }
  636.                                         echo sprintf("%-32s %s\n", "Random bits:", trim($rand));
  637.  
  638.                                         $rand_bytes = substr(str_replace('-', '', strtolower($uuid)),13);
  639.                                         $var16a = strtoupper(dechex(hexdec($rand_bytes[3]) & 0b0011 | 0b0000));
  640.                                         $var16b = strtoupper(dechex(hexdec($rand_bytes[3]) & 0b0011 | 0b0100));
  641.                                         $var16c = strtoupper(dechex(hexdec($rand_bytes[3]) & 0b0011 | 0b1000));
  642.                                         $var16d = strtoupper(dechex(hexdec($rand_bytes[3]) & 0b0011 | 0b1100));
  643.                                         $rand_bytes[3] = '?'; // was partially overwritten by variant
  644.  
  645.                                         $p = 3;
  646.                                         $rand_bytes = substr($rand_bytes,0,$p)."<abbr title=\"$var16a, $var16b, $var16c, or $var16d\">".substr($rand_bytes,$p,1).'</abbr>'.substr($rand_bytes,$p+1);
  647.                                         echo sprintf("%-32s %s\n", "Random bytes:", "[0x$rand_bytes]");
  648.  
  649.                                         // TODO: convert to and from Base32 CROCKFORD ULID (make 2 methods in uuid_utils.inc.php)
  650.                                         // e.g. ULID: 01GCZ05N3JFRKBRWKNGCQZGP44
  651.                                         // "Be aware that all version 7 UUIDs may be converted to ULIDs but not all ULIDs may be converted to UUIDs."
  652.  
  653.                                         break;
  654.                                 case 8:
  655.                                         /*
  656.                                         Variant 1, Version 8 UUID
  657.                                         - 48 bit Custom data [Block 1+2]
  658.                                         -  4 bit Version (fix 0x8)
  659.                                         - 12 bit Custom data [Block 3]
  660.                                         -  2 bit Variant (fix 0b10)
  661.                                         - 62 bit Custom data [Block 4+5]
  662.                                         */
  663.  
  664.                                         echo sprintf("%-32s %s\n", "Version:", "[0x8] Custom implementation");
  665.  
  666.                                         $custom_data = substr($uuid,0,12).substr($uuid,13); // exclude version nibble
  667.                                         $custom_data[15] = dechex(hexdec($custom_data[15]) & 0b0011); // nibble was partially overwritten by variant
  668.                                         $custom_data = strtolower($custom_data);
  669.  
  670.                                         $custom_block1 = substr($uuid,  0, 8);
  671.                                         $custom_block2 = substr($uuid,  8, 4);
  672.                                         $custom_block3 = substr($uuid, 12, 4);
  673.                                         $custom_block4 = substr($uuid, 16, 4);
  674.                                         $custom_block5 = substr($uuid, 20);
  675.  
  676.                                         $custom_block3 = substr($custom_block3, 1); // remove version
  677.                                         $custom_block4[0] = dechex(hexdec($custom_block4[0]) & 0b0011); // remove variant
  678.  
  679.                                         echo sprintf("%-32s %s\n", "Custom data:", "[0x$custom_data]");
  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]");
  685.  
  686.                                         // START: Check if Custom UUIDv8 is likely an OIDplus 2.0 Custom UUID
  687.  
  688.                                         $oidplus_systemid_hex = $custom_block1;
  689.                                         $oidplus_systemid_int = hexdec($oidplus_systemid_hex); // 31 bit hash of public key
  690.                                         $oidplus_systemid_valid = hexdec($custom_block1) < 0x80000000;
  691.  
  692.                                         $oidplus_creation_hex = $custom_block2;
  693.                                         $oidplus_creation_int = hexdec($oidplus_creation_hex); // days since 1 January 1970, or 0 if unknown
  694.                                         //$oidplus_creation_valid = ($oidplus_creation_int >= 14610/*1 Jan 2010*/) && ($oidplus_creation_int <= floor(time()/24/60/60)/*Today*/);
  695.                                         $oidplus_creation_unknown = $oidplus_creation_int == 0;
  696.  
  697.                                         $oidplus_reserved_hex = $custom_block3;
  698.                                         $oidplus_reserved_int = hexdec($oidplus_reserved_hex);
  699.  
  700.                                         $oidplus_namespace_hex = $custom_block4;
  701.                                         $oidplus_namespace_int = hexdec($oidplus_namespace_hex);
  702.  
  703.                                         $oidplus_data_hex = $custom_block5;
  704.                                         $oidplus_data_int = (PHP_INT_SIZE == 4) ? gmp_strval(gmp_init($oidplus_data_hex,16),10) : hexdec($custom_block5);
  705.  
  706.                                         if ($oidplus_systemid_valid && ($oidplus_reserved_int == 0)) {
  707.                                                 if (($oidplus_namespace_int == 0) && $oidplus_creation_unknown && (strtolower($oidplus_data_hex) == '1890afd80709')) {
  708.                                                         // System GUID, e.g. 6e932dd7-0000-8000-8000-1890afd80709
  709.                                                         echo "\n<u>Interpretation of <a href=\"https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md\">OIDplus 2.0 Custom UUID</a></u>\n\n";
  710.                                                         echo sprintf("%-32s %s\n", "System ID:", "[0x$oidplus_systemid_hex] ".$oidplus_systemid_int);
  711.                                                         echo sprintf("%-32s %s\n", "Creation time:", "[0x$oidplus_creation_hex] ".($oidplus_creation_unknown ? 'Unknown' : date('Y-m-d', $oidplus_creation_int*24*60*60))); /**@phpstan-ignore-line*/
  712.                                                         echo sprintf("%-32s %s\n", "Reserved:", "[0x$oidplus_reserved_hex]");
  713.                                                         echo sprintf("%-32s %s\n", "Namespace:", "[0x$oidplus_namespace_hex] $oidplus_namespace_int=System");
  714.                                                         echo sprintf("%-32s %s\n", "Data (empty string hash):", "[0x$oidplus_data_hex] SHA1('') = ????????????????????????????$oidplus_data_hex");
  715.                                                 }
  716.                                                 else if (($oidplus_namespace_int == 1) && $oidplus_creation_unknown) {
  717.                                                         // User GUID, e.g. 6e932dd7-0000-8000-8001-2938f50e857e (User), 6e932dd7-0000-8000-8001-000000000000 (Admin)
  718.                                                         echo "\n<u>Interpretation of <a href=\"https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md\">OIDplus 2.0 Custom UUID</a></u>\n\n";
  719.                                                         echo sprintf("%-32s %s\n", "System ID:", "[0x$oidplus_systemid_hex] ".$oidplus_systemid_int);
  720.                                                         echo sprintf("%-32s %s\n", "Creation time:", "[0x$oidplus_creation_hex] ".($oidplus_creation_unknown ? 'Unknown' : date('Y-m-d', $oidplus_creation_int*24*60*60)));  /**@phpstan-ignore-line*/
  721.                                                         echo sprintf("%-32s %s\n", "Reserved:", "[0x$oidplus_reserved_hex]");
  722.                                                         echo sprintf("%-32s %s\n", "Namespace:", "[0x$oidplus_namespace_hex] $oidplus_namespace_int=User");
  723.                                                         if ($oidplus_data_int == 0) {
  724.                                                                 echo sprintf("%-32s %s\n", "Data (Username):", "[0x$oidplus_data_hex] 0=Admin");
  725.                                                         } else {
  726.                                                                 echo sprintf("%-32s %s\n", "Data (Username):", "[0x$oidplus_data_hex] SHA1(UserName) = ????????????????????????????$oidplus_data_hex");
  727.                                                         }
  728.                                                 }
  729.                                                 else if (($oidplus_namespace_int == 2)/* && $oidplus_creation_valid*/) {
  730.                                                         // Log entry GUID, e.g. 6e932dd7-458c-8000-8002-0000000004d2
  731.                                                         echo "\n<u>Interpretation of <a href=\"https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md\">OIDplus 2.0 Custom UUID</a></u>\n\n";
  732.                                                         echo sprintf("%-32s %s\n", "System ID:", "[0x$oidplus_systemid_hex] ".$oidplus_systemid_int);
  733.                                                         echo sprintf("%-32s %s\n", "Creation time:", "[0x$oidplus_creation_hex] ".($oidplus_creation_unknown ? 'Unknown' : date('Y-m-d', $oidplus_creation_int*24*60*60)));
  734.                                                         echo sprintf("%-32s %s\n", "Reserved:", "[0x$oidplus_reserved_hex]");
  735.                                                         echo sprintf("%-32s %s\n", "Namespace:", "[0x$oidplus_namespace_hex] $oidplus_namespace_int=Log Entry");
  736.                                                         echo sprintf("%-32s %s\n", "Data (Sequence number):", "[0x$oidplus_data_hex] $oidplus_data_int");
  737.                                                 }
  738.                                                 else if (($oidplus_namespace_int == 3) && $oidplus_creation_unknown) {
  739.                                                         // Configuration entry GUID, e.g. 6e932dd7-0000-8000-8003-f14dda42862a
  740.                                                         echo "\n<u>Interpretation of <a href=\"https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md\">OIDplus 2.0 Custom UUID</a></u>\n\n";
  741.                                                         echo sprintf("%-32s %s\n", "System ID:", "[0x$oidplus_systemid_hex] ".$oidplus_systemid_int);
  742.                                                         echo sprintf("%-32s %s\n", "Creation time:", "[0x$oidplus_creation_hex] ".($oidplus_creation_unknown ? 'Unknown' : date('Y-m-d', $oidplus_creation_int*24*60*60))); /**@phpstan-ignore-line*/
  743.                                                         echo sprintf("%-32s %s\n", "Reserved:", "[0x$oidplus_reserved_hex]");
  744.                                                         echo sprintf("%-32s %s\n", "Namespace:", "[0x$oidplus_namespace_hex] $oidplus_namespace_int=Configuration Entry");
  745.                                                         echo sprintf("%-32s %s\n", "Data (Setting name hash):", "[0x$oidplus_data_hex] SHA1(SettingName) = ????????????????????????????$oidplus_data_hex");
  746.                                                 }
  747.                                                 else if ($oidplus_namespace_int == 4) {
  748.                                                         // ASN.1 Alpahnumeric identifier GUID, e.g. 6e932dd7-0000-8000-8004-208ded8a3f8f
  749.                                                         echo "\n<u>Interpretation of <a href=\"https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md\">OIDplus 2.0 Custom UUID</a></u>\n\n";
  750.                                                         echo sprintf("%-32s %s\n", "System ID:", "[0x$oidplus_systemid_hex] ".$oidplus_systemid_int);
  751.                                                         echo sprintf("%-32s %s\n", "Creation time:", "[0x$oidplus_creation_hex] ".($oidplus_creation_unknown ? 'Unknown' : date('Y-m-d', $oidplus_creation_int*24*60*60)));
  752.                                                         echo sprintf("%-32s %s\n", "Reserved:", "[0x$oidplus_reserved_hex]");
  753.                                                         echo sprintf("%-32s %s\n", "Namespace:", "[0x$oidplus_namespace_hex] $oidplus_namespace_int=ASN.1 Alphanumeric ID");
  754.                                                         $oidplus_data_24hi_hex = substr($oidplus_data_hex, 0, 6);
  755.                                                         $oidplus_data_24lo_hex = substr($oidplus_data_hex, 6, 6);
  756.                                                         echo sprintf("%-32s %s\n", "Data (OID hash):", "[0x$oidplus_data_24hi_hex] SHA1(OID) = ????????????????????????????$oidplus_data_24hi_hex");
  757.                                                         echo sprintf("%-32s %s\n", "Data (Name hash):", "[0x$oidplus_data_24lo_hex] SHA1(AlphaNumId) = ????????????????????????????$oidplus_data_24lo_hex");
  758.                                                 }
  759.                                                 else if ($oidplus_namespace_int == 5) {
  760.                                                         // Unicode Label entry GUID, e.g. 6e932dd7-0000-8000-8005-208dedaf9a96
  761.                                                         echo "\n<u>Interpretation of <a href=\"https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md\">OIDplus 2.0 Custom UUID</a></u>\n\n";
  762.                                                         echo sprintf("%-32s %s\n", "System ID:", "[0x$oidplus_systemid_hex] ".$oidplus_systemid_int);
  763.                                                         echo sprintf("%-32s %s\n", "Creation time:", "[0x$oidplus_creation_hex] ".($oidplus_creation_unknown ? 'Unknown' : date('Y-m-d', $oidplus_creation_int*24*60*60)));
  764.                                                         echo sprintf("%-32s %s\n", "Reserved:", "[0x$oidplus_reserved_hex]");
  765.                                                         echo sprintf("%-32s %s\n", "Namespace:", "[0x$oidplus_namespace_hex] $oidplus_namespace_int=Unicode Label");
  766.                                                         $oidplus_data_24hi_hex = substr($oidplus_data_hex, 0, 6);
  767.                                                         $oidplus_data_24lo_hex = substr($oidplus_data_hex, 6, 6);
  768.                                                         echo sprintf("%-32s %s\n", "Data (OID hash):", "[0x$oidplus_data_24hi_hex] SHA1(OID) = ????????????????????????????$oidplus_data_24hi_hex");
  769.                                                         echo sprintf("%-32s %s\n", "Data (Name hash):", "[0x$oidplus_data_24lo_hex] SHA1(UnicodeLabel) = ????????????????????????????$oidplus_data_24lo_hex");
  770.                                                 }
  771.                                                 else if (($oidplus_namespace_int >= 6) && ($oidplus_namespace_int <= 0xF)) {
  772.                                                         // System reserved
  773.                                                         echo "\n<u>Interpretation of <a href=\"https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md\">OIDplus 2.0 Custom UUID</a></u>\n\n";
  774.                                                         echo sprintf("%-32s %s\n", "System ID:", "[0x$oidplus_systemid_hex] ".$oidplus_systemid_int);
  775.                                                         echo sprintf("%-32s %s\n", "Creation time:", "[0x$oidplus_creation_hex] ".($oidplus_creation_unknown ? 'Unknown' : date('Y-m-d', $oidplus_creation_int*24*60*60)));
  776.                                                         echo sprintf("%-32s %s\n", "Reserved:", "[0x$oidplus_reserved_hex]");
  777.                                                         echo sprintf("%-32s %s\n", "Namespace:", "[0x$oidplus_namespace_hex] $oidplus_namespace_int=Unknown (System Reserved)");
  778.                                                         echo sprintf("%-32s %s\n", "Data (Setting name hash):", "[0x$oidplus_data_hex] Unknown");
  779.                                                 }
  780.                                                 else if ($oidplus_namespace_int > 0xF) {
  781.                                                         // Information Object GUID, e.g. 6e932dd7-458c-8000-b9e9-c1e3894d1105
  782.                                                         $known_objecttype_plugins = array(
  783.                                                                 // Latest list here: https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md
  784.                                                                 '1.3.6.1.4.1.37476.2.5.2.4.8.1' => 'doi (ViaThinkSoft plugin)',
  785.                                                                 '1.3.6.1.4.1.37476.2.5.2.4.8.2' => 'gs1 (ViaThinkSoft plugin)',
  786.                                                                 '1.3.6.1.4.1.37476.2.5.2.4.8.3' => 'guid (ViaThinkSoft plugin)',
  787.                                                                 '1.3.6.1.4.1.37476.2.5.2.4.8.4' => 'ipv4 (ViaThinkSoft plugin)',
  788.                                                                 '1.3.6.1.4.1.37476.2.5.2.4.8.5' => 'ipv6 (ViaThinkSoft plugin)',
  789.                                                                 '1.3.6.1.4.1.37476.2.5.2.4.8.6' => 'java (ViaThinkSoft plugin)',
  790.                                                                 '1.3.6.1.4.1.37476.2.5.2.4.8.7' => 'oid (ViaThinkSoft plugin)',
  791.                                                                 '1.3.6.1.4.1.37476.2.5.2.4.8.8' => 'other (ViaThinkSoft plugin)',
  792.                                                                 '1.3.6.1.4.1.37476.2.5.2.4.8.9' => 'domain (ViaThinkSoft plugin)',
  793.                                                                 '1.3.6.1.4.1.37476.2.5.2.4.8.10' => 'fourcc (ViaThinkSoft plugin)',
  794.                                                                 '1.3.6.1.4.1.37476.2.5.2.4.8.11' => 'aid (ViaThinkSoft plugin)',
  795.                                                                 '1.3.6.1.4.1.37476.2.5.2.4.8.12' => 'php (ViaThinkSoft plugin)',
  796.                                                                 '1.3.6.1.4.1.37476.2.5.2.4.8.13' => 'mac (ViaThinkSoft plugin)',
  797.                                                                 '1.3.6.1.4.1.37553.8.1.8.8.53354196964.27255728261' => 'circuit (Frdlweb plugin)',
  798.                                                                 '1.3.6.1.4.1.37476.9000.108.19361.856' => 'ns (Frdlweb plugin)',
  799.                                                                 '1.3.6.1.4.1.37553.8.1.8.8.53354196964.32927' => 'pen (Frdlweb plugin)',
  800.                                                                 '1.3.6.1.4.1.37553.8.1.8.8.53354196964.39870' => 'uri (Frdlweb plugin)',
  801.                                                                 '1.3.6.1.4.1.37553.8.1.8.8.53354196964.1958965295' => 'web+fan (Frdlweb plugin)'
  802.                                                         );
  803.                                                         $namespace_desc = 'Unknown object type';
  804.                                                         foreach ($known_objecttype_plugins as $oid => $name) {
  805.                                                                 if ((hexdec(substr(sha1($oid), -4)) & 0x3fff) == $oidplus_namespace_int) $namespace_desc = "$oid = $name";
  806.                                                         }
  807.                                                         echo "\n<u>Interpretation of <a href=\"https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md\">OIDplus 2.0 Custom UUID</a></u>\n\n";
  808.                                                         echo sprintf("%-32s %s\n", "System ID:", "[0x$oidplus_systemid_hex] ".$oidplus_systemid_int);
  809.                                                         echo sprintf("%-32s %s\n", "Creation time:", "[0x$oidplus_creation_hex] ".($oidplus_creation_unknown ? 'Unknown' : date('Y-m-d', $oidplus_creation_int*24*60*60)));
  810.                                                         echo sprintf("%-32s %s\n", "Reserved:", "[0x$oidplus_reserved_hex]");
  811.                                                         echo sprintf("%-32s %s\n", "Namespace (Obj.type OID hash):", "[0x$oidplus_namespace_hex] $namespace_desc");
  812.                                                         echo sprintf("%-32s %s\n", "Data (Object name hash):", "[0x$oidplus_data_hex] SHA1(ObjectName) = ????????????????????????????$oidplus_data_hex");
  813.                                                 }
  814.                                         }
  815.  
  816.                                         // END: OIDplus 2.0 Custom UUID Interpretation
  817.  
  818.                                         break;
  819.                                 default:
  820.                                         echo sprintf("%-32s %s\n", "Version:", "[0x".dechex($version)."] Unknown");
  821.                                         break;
  822.                         }
  823.  
  824.                         break;
  825.                 case 2:
  826.                         // TODO: Show byte order: 00112233-4455-6677-8899-aabbccddeeff => 33 22 11 00 55 44 77 66 88 99 aa bb cc dd ee ff
  827.  
  828.                         // TODO: Is there any scheme in that legacy Microsoft GUIDs?
  829.                         echo sprintf("%-32s %s\n", "Variant:", "[0b110] Reserved for Microsoft Corporation");
  830.                         break;
  831.                 case 3:
  832.                         echo sprintf("%-32s %s\n", "Variant:", "[0b111] Reserved for future use");
  833.                         break;
  834.         }
  835.  
  836.         // START: HickelSOFT UUID
  837.  
  838.         // Block 5
  839.         $signature = substr($uuid,20,12);
  840.         if (strtolower($signature) == '5ce32bd83b96') {
  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-5ce32bd83b96
  844.                 // Block 4
  845.                 $unused2bits = hexdec(substr($uuid,16,1)) & 0x3;
  846.                 $year = hexdec(substr($uuid,17,3));
  847.                 // Block 3
  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'));
  851.                 // Block 2
  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);
  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
  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);
  869.                 if (strpos($utc_time,'X') === false) {
  870.                         $deviation = "(deviation -2ms..2ms)";
  871.                         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";
  872.                         echo sprintf("%-32s %s\n", "Random 16 bits:", "[0x$rnd16bits] 0b".str_pad("".base_convert($rnd16bits, 16, 2), 16, '0', STR_PAD_LEFT));
  873.                         echo sprintf("%-32s %s\n", "Milliseconds:", "[0x".substr($uuid,4,2)."] $milliseconds $deviation");
  874.                         echo sprintf("%-32s %s\n", "Seconds:", "[0x".substr($uuid,6,2)."] $seconds");
  875.                         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).")");
  876.                         echo sprintf("%-32s %s\n", "Day of year:", "[0x".substr($uuid,13,3)."] $dayOfYear (Day=$day, Month=$month)");
  877.                         echo sprintf("%-32s %s\n", "Unused 2 bits:", "[$unused2bits] 0b".str_pad("".base_convert("$unused2bits", 16, 2), 2, '0', STR_PAD_LEFT));
  878.                         echo sprintf("%-32s %s\n", "Year:", "[0x".substr($uuid,17,3)."] $year");
  879.                         echo sprintf("%-32s %s\n", "Signature:", "[0x".substr($uuid,20,12)."] HickelSOFT \"SQL Server Sortable Custom UUID\", Version 2 (very likely)");
  880.                         echo sprintf("%-32s %s\n", "UTC Date Time:", "$utc_time $deviation");
  881.                 }
  882.         } else if (strtolower($signature) == '000000000000') {
  883.                 // HickelSOFT "SQL Server sortable UUID in C#"
  884.                 // Version 1: Resolution of 1 milliseconds, random part of 16 bits, local timezone, NOT UUIDv8 conform.
  885.                 // Example: ff38da51-1301-0903-2420-000000000000
  886.                 // Block 4
  887.                 $year = substr($uuid,18,2) . substr($uuid,16,2);
  888.                 $year = (!is_numeric($year) || ($year < 2000) || ($year > 2999)) ? "XXXX" : $year = intval($year);
  889.                 // Block 3
  890.                 $day = substr($uuid,12,2);
  891.                 $day = (!is_numeric($day) || ($day < 0) || ($day >= 60)) ? "XX" : intval($day);
  892.                 $month = substr($uuid,14,2);
  893.                 $month = (!is_numeric($month) || ($month < 0) || ($month >= 60)) ? "XX" : intval($month);
  894.                 // Block 2
  895.                 $minutes = substr($uuid,8,2);
  896.                 $minutes = (!is_numeric($minutes) || ($minutes < 0) || ($minutes >= 60)) ? "XX" : intval($minutes);
  897.                 $hours = substr($uuid,10,2);
  898.                 $hours = (!is_numeric($hours) || ($hours < 0) || ($hours >= 60)) ? "XX" : intval($hours);
  899.                 // Block 1
  900.                 $rnd16bits = substr($uuid,0,4);
  901.                 $millisecond8bits = hexdec(substr($uuid,4,2));
  902.                 $milliseconds = round($millisecond8bits / 255 * 999);
  903.                 $seconds = substr($uuid,6,2);
  904.                 $seconds = (!is_numeric($seconds) || ($seconds < 0) || ($seconds >= 60)) ? "XX" : intval($seconds);
  905.                 // Verbose info
  906.                 $local_time =
  907.                         str_pad("$year",4,'0',STR_PAD_LEFT).'-'.
  908.                         str_pad("$month",2,'0',STR_PAD_LEFT).'-'.
  909.                         str_pad("$day",2,'0',STR_PAD_LEFT).' '.
  910.                         str_pad("$hours",2,'0',STR_PAD_LEFT).':'.
  911.                         str_pad("$minutes",2,'0',STR_PAD_LEFT).':'.
  912.                         str_pad("$seconds",2,'0',STR_PAD_LEFT)."'".
  913.                         str_pad("$milliseconds",2,'0',STR_PAD_LEFT);
  914.                 if (strpos($local_time,'X') === false) {
  915.                         $deviation = "(deviation -4ms..0ms)";
  916.                         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";
  917.                         echo sprintf("%-32s %s\n", "Random 16 bits:", "[0x$rnd16bits] 0b".str_pad(base_convert($rnd16bits, 16, 2), 16, '0', STR_PAD_LEFT));
  918.                         echo sprintf("%-32s %s\n", "Milliseconds:", "[0x".substr($uuid,4,2)."] $milliseconds $deviation");
  919.                         echo sprintf("%-32s %s\n", "Seconds:", "[0x".substr($uuid,6,2)."] $seconds");
  920.                         echo sprintf("%-32s %s\n", "Minutes:", "[0x".substr($uuid,8,2)."] $minutes");
  921.                         echo sprintf("%-32s %s\n", "Hours:", "[0x".substr($uuid,10,2)."] $hours");
  922.                         echo sprintf("%-32s %s\n", "Day:", "[0x".substr($uuid,12,2)."] $day");
  923.                         echo sprintf("%-32s %s\n", "Month:", "[0x".substr($uuid,14,2)."] $month");
  924.                         echo sprintf("%-32s %s\n", "Year:", "[0x".substr($uuid,16,4)."] $year");
  925.                         echo sprintf("%-32s %s\n", "Signature:", "[0x".substr($uuid,20,12)."] HickelSOFT \"SQL Server Sortable Custom UUID\", Version 1 (maybe)");
  926.                         echo sprintf("%-32s %s\n", "Generator's Local Date Time:", "$local_time $deviation");
  927.                 }
  928.         }
  929.  
  930.         // END: HickelSOFT UUID
  931.  
  932.         if (!$echo) {
  933.                 $out = ob_get_contents();
  934.                 ob_end_clean();
  935.                 return $out;
  936.         } else {
  937.                 return true;
  938.         }
  939. }
  940.  
  941. function uuid_canonize($uuid) {
  942.         if (!uuid_valid($uuid)) return false;
  943.         return oid_to_uuid(uuid_to_oid($uuid));
  944. }
  945.  
  946. /*
  947. assert(oid_to_uuid('2.25.111325678376819997685911819737516232943')=='53c08bb6-b2eb-5038-bf28-ad41a08c50ef');
  948. assert(oid_to_uuid('1.2.840.113556.1.8000.2554.21440.35766.45803.20536.48936.11354528.9195759')=='53c08bb6-b2eb-5038-bf28-ad41a08c50ef');
  949. assert(oid_to_uuid('1.3.6.1.4.1.54392.1.6034977117478539320.13774449957690691823')=='53c08bb6-b2eb-5038-bf28-ad41a08c50ef');
  950. assert(oid_to_uuid('1.3.6.1.4.1.54392.2.1405127606.3001765944.3207114049.2693550319')=='53c08bb6-b2eb-5038-bf28-ad41a08c50ef');
  951. 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');
  952. */
  953. function oid_to_uuid($oid) {
  954.         if (substr($oid,0,1) == '.') $oid = substr($oid, 1); // remove leading dot
  955.  
  956.         // Information about Microsoft and Waterjuice UUID-OID: https://waterjuiceweb.wordpress.com/2019/09/24/guids-to-oids/
  957.  
  958.         $ary = explode('.', $oid);
  959.         if ((count($ary) == 3) && (strpos($oid, '2.25.') === 0)) {
  960.                 // ISO/ITU-T UUID-to-OID
  961.                 // Example: {53c08bb6-b2eb-5038-bf28-ad41a08c50ef} = 2.25.111325678376819997685911819737516232943
  962.                 $val = $ary[2];
  963.                 $dec = gmp_init($val, 10);
  964.                 $hex = gmp_strval($dec, 16);
  965.                 $hex = str_pad($hex, 32, "0", STR_PAD_LEFT);
  966.                 return substr($hex,0,8).'-'.substr($hex,8,4).'-'.substr($hex,12,4).'-'.substr($hex,16,4).'-'.substr($hex,20,12);
  967.         } else if ((count($ary) == 14) && (strpos($oid, '1.2.840.113556.1.8000.2554.') === 0)) {
  968.                 // Microsoft UUID-to-OID
  969.                 // Example: {53c08bb6-b2eb-5038-bf28-ad41a08c50ef} = 1.2.840.113556.1.8000.2554.21440.35766.45803.20536.48936.11354528.9195759
  970.                 $a = intval($ary[7]);
  971.                 $b = intval($ary[8]);
  972.                 $c = intval($ary[9]);
  973.                 $d = intval($ary[10]);
  974.                 $e = intval($ary[11]);
  975.                 $f = intval($ary[12]);
  976.                 $g = intval($ary[13]);
  977.                 return dechex($a).dechex($b).'-'.dechex($c).'-'.dechex($d).'-'.dechex($e).'-'.dechex($f).dechex($g);
  978.         } else if ((count($ary) == 10) && (strpos($oid, '1.3.6.1.4.1.54392.1.') === 0)) {
  979.                 // Waterjuice UUID-to-OID 2x64 Bits
  980.                 // Example: {53c08bb6-b2eb-5038-bf28-ad41a08c50ef} = 1.3.6.1.4.1.54392.1.6034977117478539320.13774449957690691823
  981.                 $a1 = gmp_strval(gmp_init($ary[8],10),16); if (strlen($a1)>16) return false;
  982.                 $a2 = gmp_strval(gmp_init($ary[9],10),16); if (strlen($a2)>16) return false;
  983.                 $hex =
  984.                         str_pad($a1, 16, "0", STR_PAD_LEFT).
  985.                         str_pad($a2, 16, "0", STR_PAD_LEFT);
  986.                 return substr($hex,0,8).'-'.substr($hex,8,4).'-'.substr($hex,12,4).'-'.substr($hex,16,4).'-'.substr($hex,20,12);
  987.         } else if ((count($ary) == 12) && (strpos($oid, '1.3.6.1.4.1.54392.2.') === 0)) {
  988.                 // Waterjuice UUID-to-OID 4x32 Bits
  989.                 // Example: {53c08bb6-b2eb-5038-bf28-ad41a08c50ef} = 1.3.6.1.4.1.54392.2.1405127606.3001765944.3207114049.2693550319
  990.                 $a1 = gmp_strval(gmp_init($ary[8],10),16); if (strlen($a1)>8) return false;
  991.                 $a2 = gmp_strval(gmp_init($ary[9],10),16); if (strlen($a2)>8) return false;
  992.                 $a3 = gmp_strval(gmp_init($ary[10],10),16); if (strlen($a3)>8) return false;
  993.                 $a4 = gmp_strval(gmp_init($ary[11],10),16); if (strlen($a4)>8) return false;
  994.                 $hex =
  995.                         str_pad($a1, 8, "0", STR_PAD_LEFT).
  996.                         str_pad($a2, 8, "0", STR_PAD_LEFT).
  997.                         str_pad($a3, 8, "0", STR_PAD_LEFT).
  998.                         str_pad($a4, 8, "0", STR_PAD_LEFT);
  999.                 return substr($hex,0,8).'-'.substr($hex,8,4).'-'.substr($hex,12,4).'-'.substr($hex,16,4).'-'.substr($hex,20,12);
  1000.         } else if ((count($ary) == 16) && (strpos($oid, '1.3.6.1.4.1.54392.3.') === 0)) {
  1001.                 // Waterjuice UUID-to-OID 8x16 Bits
  1002.                 // Example: {53c08bb6-b2eb-5038-bf28-ad41a08c50ef} = 1.3.6.1.4.1.54392.3.21440.35766.45803.20536.48936.44353.41100.20719
  1003.                 $a1 = gmp_strval(gmp_init($ary[8],10),16); if (strlen($a1)>4) return false;
  1004.                 $a2 = gmp_strval(gmp_init($ary[9],10),16); if (strlen($a2)>4) return false;
  1005.                 $a3 = gmp_strval(gmp_init($ary[10],10),16); if (strlen($a3)>4) return false;
  1006.                 $a4 = gmp_strval(gmp_init($ary[11],10),16); if (strlen($a4)>4) return false;
  1007.                 $a5 = gmp_strval(gmp_init($ary[12],10),16); if (strlen($a5)>4) return false;
  1008.                 $a6 = gmp_strval(gmp_init($ary[13],10),16); if (strlen($a6)>4) return false;
  1009.                 $a7 = gmp_strval(gmp_init($ary[14],10),16); if (strlen($a7)>4) return false;
  1010.                 $a8 = gmp_strval(gmp_init($ary[15],10),16); if (strlen($a8)>4) return false;
  1011.                 $hex =
  1012.                         str_pad($a1, 4, "0", STR_PAD_LEFT).
  1013.                         str_pad($a2, 4, "0", STR_PAD_LEFT).
  1014.                         str_pad($a3, 4, "0", STR_PAD_LEFT).
  1015.                         str_pad($a4, 4, "0", STR_PAD_LEFT).
  1016.                         str_pad($a5, 4, "0", STR_PAD_LEFT).
  1017.                         str_pad($a6, 4, "0", STR_PAD_LEFT).
  1018.                         str_pad($a7, 4, "0", STR_PAD_LEFT).
  1019.                         str_pad($a8, 4, "0", STR_PAD_LEFT);
  1020.                 return substr($hex,0,8).'-'.substr($hex,8,4).'-'.substr($hex,12,4).'-'.substr($hex,16,4).'-'.substr($hex,20,12);
  1021.         } else {
  1022.                 return false;
  1023.         }
  1024. }
  1025.  
  1026. function is_uuid_oid($oid, $only_allow_root=false) {
  1027.         if (substr($oid,0,1) == '.') $oid = substr($oid, 1); // remove leading dot
  1028.  
  1029.         if ($only_allow_root) {
  1030.                 return oid_to_uuid($oid) !== false;
  1031.         } else {
  1032.                 // TODO: Check range of the components (e.g. max 128 bits for 2.25)
  1033.                 if (strpos($oid,'2.25.') === 0) return true;
  1034.                 if (strpos($oid,'1.2.840.113556.1.8000.2554.') === 0) return true;
  1035.                 if (strpos($oid,'1.3.6.1.4.1.54392.1.') === 0) return true;
  1036.                 if (strpos($oid,'1.3.6.1.4.1.54392.2.') === 0) return true;
  1037.                 if (strpos($oid,'1.3.6.1.4.1.54392.3.') === 0) return true;
  1038.                 return false;
  1039.         }
  1040. }
  1041.  
  1042. function uuid_to_oid($uuid, $base='2.25') {
  1043.         if (!uuid_valid($uuid)) return false;
  1044.         #$base = oid_sanitize($base);
  1045.  
  1046.         $uuid = str_replace(array('-', '{', '}'), '', $uuid);
  1047.  
  1048.         // Information about Microsoft and Waterjuice UUID-OID: https://waterjuiceweb.wordpress.com/2019/09/24/guids-to-oids/
  1049.  
  1050.         if ($base == '2.25') {
  1051.                 $x = gmp_init($uuid, 16);
  1052.                 return $base.'.'.gmp_strval($x, 10);
  1053.         } else if ($base == '1.2.840.113556.1.8000.2554') {
  1054.                 return $base.'.'.
  1055.                         gmp_strval(gmp_init(substr($uuid,0,4),16),10).'.'.
  1056.                         gmp_strval(gmp_init(substr($uuid,4,4),16),10).'.'.
  1057.                         gmp_strval(gmp_init(substr($uuid,8,4),16),10).'.'.
  1058.                         gmp_strval(gmp_init(substr($uuid,12,4),16),10).'.'.
  1059.                         gmp_strval(gmp_init(substr($uuid,16,4),16),10).'.'.
  1060.                         gmp_strval(gmp_init(substr($uuid,20,6),16),10).'.'.
  1061.                         gmp_strval(gmp_init(substr($uuid,26,6),16),10);
  1062.         } else if ($base == '1.3.6.1.4.1.54392.1') {
  1063.                 return $base.'.'.
  1064.                         gmp_strval(gmp_init(substr($uuid,0,16),16),10).'.'.
  1065.                         gmp_strval(gmp_init(substr($uuid,16,16),16),10);
  1066.         } else if ($base == '1.3.6.1.4.1.54392.2') {
  1067.                 return $base.'.'.
  1068.                         gmp_strval(gmp_init(substr($uuid,0,8),16),10).'.'.
  1069.                         gmp_strval(gmp_init(substr($uuid,8,8),16),10).'.'.
  1070.                         gmp_strval(gmp_init(substr($uuid,16,8),16),10).'.'.
  1071.                         gmp_strval(gmp_init(substr($uuid,24,8),16),10);
  1072.         } else if ($base == '1.3.6.1.4.1.54392.3') {
  1073.                 return $base.'.'.
  1074.                         gmp_strval(gmp_init(substr($uuid,0,4),16),10).'.'.
  1075.                         gmp_strval(gmp_init(substr($uuid,4,4),16),10).'.'.
  1076.                         gmp_strval(gmp_init(substr($uuid,8,4),16),10).'.'.
  1077.                         gmp_strval(gmp_init(substr($uuid,12,4),16),10).'.'.
  1078.                         gmp_strval(gmp_init(substr($uuid,16,4),16),10).'.'.
  1079.                         gmp_strval(gmp_init(substr($uuid,20,4),16),10).'.'.
  1080.                         gmp_strval(gmp_init(substr($uuid,24,4),16),10).'.'.
  1081.                         gmp_strval(gmp_init(substr($uuid,28,4),16),10);
  1082.         } else {
  1083.                 throw new Exception("Unsupported UUID-to-OID base");
  1084.         }
  1085. }
  1086.  
  1087. function uuid_numeric_value($uuid) {
  1088.         $oid = uuid_to_oid($uuid);
  1089.         if (!$oid) return false;
  1090.         return substr($oid, strlen('2.25.'));
  1091. }
  1092.  
  1093. function uuid_c_syntax($uuid) {
  1094.         $uuid = str_replace('{', '', $uuid);
  1095.         return '{ 0x' . substr($uuid, 0, 8) .
  1096.                 ', 0x' . substr($uuid, 9, 4) .
  1097.                 ', 0x' . substr($uuid, 14, 4) .
  1098.                 ', { 0x' . substr($uuid, 19, 2).
  1099.                 ', 0x' . substr($uuid, 21, 2) .
  1100.                 ', 0x' . substr($uuid, 24, 2) .
  1101.                 ', 0x' . substr($uuid, 26, 2) .
  1102.                 ', 0x' . substr($uuid, 28, 2) .
  1103.                 ', 0x' . substr($uuid, 30, 2) .
  1104.                 ', 0x' . substr($uuid, 32, 2) .
  1105.                 ', 0x' . substr($uuid, 34, 2) . ' } }';
  1106. }
  1107.  
  1108. function gen_uuid($prefer_mac_address_based = true) {
  1109.         $uuid = $prefer_mac_address_based ? gen_uuid_reordered()/*UUIDv6*/ : false;
  1110.         if ($uuid === false) $uuid = gen_uuid_unix_epoch()/*UUIDv7*/;
  1111.         return $uuid;
  1112. }
  1113.  
  1114. # --------------------------------------
  1115. // Variant 1, Version 1 (Time based) UUID
  1116. # --------------------------------------
  1117.  
  1118. function gen_uuid_v1() {
  1119.         return gen_uuid_timebased();
  1120. }
  1121. function gen_uuid_timebased($force_php_implementation=false) {
  1122.         # On Debian: apt-get install php-uuid
  1123.         # extension_loaded('uuid')
  1124.         if (!$force_php_implementation && function_exists('uuid_create')) {
  1125.                 # OSSP uuid extension like seen in php5-uuid at Debian 8
  1126.                 /*
  1127.                 $x = uuid_create($context);
  1128.                 uuid_make($context, UUID_MAKE_V1);
  1129.                 uuid_export($context, UUID_FMT_STR, $uuid);
  1130.                 return trim($uuid);
  1131.                 */
  1132.  
  1133.                 # PECL uuid extension like seen in php-uuid at Debian 9
  1134.                 return trim(uuid_create(UUID_TYPE_TIME));
  1135.         }
  1136.  
  1137.         # On Debian: apt-get install uuid-runtime
  1138.         if (!$force_php_implementation && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
  1139.                 $out = array();
  1140.                 $ec = -1;
  1141.                 exec('uuidgen -t 2>/dev/null', $out, $ec);
  1142.                 if ($ec == 0) return trim($out[0]);
  1143.         }
  1144.  
  1145.         # If we hadn't any success yet, then implement the time based generation routine ourselves!
  1146.         # Based on https://github.com/fredriklindberg/class.uuid.php/blob/master/class.uuid.php
  1147.  
  1148.         $uuid = array(
  1149.                 'time_low' => 0,                /* 32-bit */
  1150.                 'time_mid' => 0,                /* 16-bit */
  1151.                 'time_hi' => 0,                 /* 16-bit */
  1152.                 'clock_seq_hi' => 0,            /*  8-bit */
  1153.                 'clock_seq_low' => 0,           /*  8-bit */
  1154.                 'node' => array()               /* 48-bit */
  1155.         );
  1156.  
  1157.         /*
  1158.          * Get current time in 100 ns intervals. The magic value
  1159.          * is the offset between UNIX epoch and the UUID UTC
  1160.          * time base October 15, 1582.
  1161.          */
  1162.         if (time_nanosleep(0,100) !== true) usleep(1); // Wait 100ns, to make sure that the time part changes if multiple UUIDs are generated
  1163.         $tp = gettimeofday();
  1164.         if (PHP_INT_SIZE == 4) {
  1165.                 $tp['sec'] = gmp_init($tp['sec'],10);
  1166.                 $tp['usec'] = gmp_init($tp['usec'],10);
  1167.                 $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));
  1168.                 $uuid['time_low'] = gmp_and($time, gmp_init('ffffffff',16));
  1169.                 $high = gmp_shiftr($time,32);
  1170.                 $uuid['time_mid'] = gmp_and($high, gmp_init('ffff',16));
  1171.                 $uuid['time_hi'] = gmp_intval(gmp_and(gmp_shiftr($high,16),gmp_init('fff',16))) | (1/*TimeBased*/ << 12);
  1172.         } else {
  1173.                 $time = ($tp['sec'] * 10000000) + ($tp['usec'] * 10) + 0x01B21DD213814000;
  1174.                 $uuid['time_low'] = $time & 0xffffffff;
  1175.                 /* Work around PHP 32-bit bit-operation limits */
  1176.                 $high = intval($time / 0xffffffff);
  1177.                 $uuid['time_mid'] = $high & 0xffff;
  1178.                 $uuid['time_hi'] = (($high >> 16) & 0xfff) | (1/*TimeBased*/ << 12);
  1179.         }
  1180.  
  1181.         /*
  1182.          * We don't support saved state information and generate
  1183.          * a random clock sequence each time.
  1184.          */
  1185.         $uuid['clock_seq_hi'] = _random_int(0, 255) & 0b00111111 | 0b10000000; // set variant to 0b10__ (RFC 4122)
  1186.         $uuid['clock_seq_low'] = _random_int(0, 255);
  1187.  
  1188.         /*
  1189.          * Node should be set to the 48-bit IEEE node identifier
  1190.          */
  1191.         $mac = get_mac_address();
  1192.         if ($mac) {
  1193.                 $node = str_replace('-','',str_replace(':','',$mac));
  1194.                 for ($i = 0; $i < 6; $i++) {
  1195.                         $uuid['node'][$i] = hexdec(substr($node, $i*2, 2));
  1196.                 }
  1197.         } else {
  1198.                 // If we cannot get a MAC address, then generate a random AAI
  1199.                 // RFC 4122 requires the multicast bit to be set, to make sure
  1200.                 // that a UUID from a system with network card never conflicts
  1201.                 // with a UUID from a system without network ard.
  1202.                 // We are additionally defining the other 3 bits as AAI,
  1203.                 // to avoid that we are misusing the CID or OUI from other vendors
  1204.                 // if we would create multicast ELI (based on CID) or EUI (based on OUI).
  1205.                 $uuid['node'] = explode('-', gen_aai(48, true/*Multicast*/));
  1206.                 $uuid['node'] = array_map('hexdec', $uuid['node']);
  1207.         }
  1208.  
  1209.         /*
  1210.          * Now output the UUID
  1211.          */
  1212.         return sprintf(
  1213.                 '%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x',
  1214.                 ($uuid['time_low']), ($uuid['time_mid']), ($uuid['time_hi']),
  1215.                 $uuid['clock_seq_hi'], $uuid['clock_seq_low'],
  1216.                 $uuid['node'][0], $uuid['node'][1], $uuid['node'][2],
  1217.                 $uuid['node'][3], $uuid['node'][4], $uuid['node'][5]);
  1218. }
  1219.  
  1220. # --------------------------------------
  1221. // Variant 1, Version 2 (DCE Security) UUID
  1222. # --------------------------------------
  1223.  
  1224. define('DCE_DOMAIN_PERSON', 0);
  1225. define('DCE_DOMAIN_GROUP', 1);
  1226. define('DCE_DOMAIN_ORG', 2);
  1227. function gen_uuid_v2($domain, $id) {
  1228.         return gen_uuid_dce($domain, $id);
  1229. }
  1230. function gen_uuid_dce($domain, $id) {
  1231.         if (($domain ?? '') === '') throw new Exception("Domain ID missing");
  1232.         if (!is_numeric($domain)) throw new Exception("Invalid Domain ID");
  1233.         if (($domain < 0) || ($domain > 0xFF)) throw new Exception("Domain ID must be in range 0..255");
  1234.  
  1235.         if (($id ?? '') === '') throw new Exception("ID value missing");
  1236.         if (!is_numeric($id)) throw new Exception("Invalid ID value");
  1237.         if (($id < 0) || ($id > 0xFFFFFFFF)) throw new Exception("ID value must be in range 0..4294967295");
  1238.  
  1239.         # Start with a version 1 UUID
  1240.         $uuid = gen_uuid_timebased();
  1241.  
  1242.         # Add Domain Number
  1243.         $uuid = str_pad(dechex($id), 8, '0', STR_PAD_LEFT) . substr($uuid, 8);
  1244.  
  1245.         # Add Domain (this overwrites part of the clock sequence)
  1246.         $uuid = substr($uuid,0,21) . str_pad(dechex($domain), 2, '0', STR_PAD_LEFT) . substr($uuid, 23);
  1247.  
  1248.         # Change version to 2
  1249.         $uuid[14] = '2';
  1250.  
  1251.         return $uuid;
  1252. }
  1253.  
  1254. # --------------------------------------
  1255. // Variant 1, Version 3 (MD5 name based) UUID
  1256. # --------------------------------------
  1257.  
  1258. function gen_uuid_v3($namespace_uuid, $name) {
  1259.         return gen_uuid_md5_namebased($namespace_uuid, $name);
  1260. }
  1261. function gen_uuid_md5_namebased($namespace_uuid, $name) {
  1262.         if (($namespace_uuid ?? '') === '') throw new Exception("Namespace UUID missing");
  1263.         if (!uuid_valid($namespace_uuid)) throw new Exception("Invalid namespace UUID '$namespace_uuid'");
  1264.  
  1265.         $namespace_uuid = uuid_canonize($namespace_uuid);
  1266.         $namespace_uuid = str_replace('-', '', $namespace_uuid);
  1267.         $namespace_uuid = hex2bin($namespace_uuid);
  1268.  
  1269.         $hash = md5($namespace_uuid.$name);
  1270.         $hash[12] = '3'; // Set version: 3 = MD5
  1271.         $hash[16] = dechex(hexdec($hash[16]) & 0b0011 | 0b1000); // Set variant to "10xx" (RFC4122)
  1272.  
  1273.         return substr($hash,  0, 8).'-'.
  1274.                substr($hash,  8, 4).'-'.
  1275.                substr($hash, 12, 4).'-'.
  1276.                substr($hash, 16, 4).'-'.
  1277.                substr($hash, 20, 12);
  1278. }
  1279.  
  1280. # --------------------------------------
  1281. // Variant 1, Version 4 (Random) UUID
  1282. # --------------------------------------
  1283.  
  1284. function gen_uuid_v4() {
  1285.         return gen_uuid_random();
  1286. }
  1287. function gen_uuid_random() {
  1288.         # On Windows: Requires
  1289.         #    extension_dir = "C:\php-8.0.3-nts-Win32-vs16-x64\ext"
  1290.         #    extension=com_dotnet
  1291.         if (function_exists('com_create_guid')) {
  1292.                 $uuid = trim(com_create_guid(), '{}');
  1293.                 if (uuid_version($uuid) === '4') { // <-- just to make 100% sure that Windows's CoCreateGuid() did output UUIDv4
  1294.                         return strtolower($uuid);
  1295.                 }
  1296.         }
  1297.  
  1298.         # On Debian: apt-get install php-uuid
  1299.         # extension_loaded('uuid')
  1300.         if (function_exists('uuid_create')) {
  1301.                 # OSSP uuid extension like seen in php5-uuid at Debian 8
  1302.                 /*
  1303.                 $x = uuid_create($context);
  1304.                 uuid_make($context, UUID_MAKE_V4);
  1305.                 uuid_export($context, UUID_FMT_STR, $uuid);
  1306.                 return trim($uuid);
  1307.                 */
  1308.  
  1309.                 # PECL uuid extension like seen in php-uuid at Debian 9
  1310.                 return trim(uuid_create(UUID_TYPE_RANDOM));
  1311.         }
  1312.  
  1313.         if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
  1314.                 # On Debian Jessie: UUID V4 (Random)
  1315.                 if (file_exists($uuidv4_file = '/proc/sys/kernel/random/uuid')) {
  1316.                         $uuid = file_get_contents($uuidv4_file);
  1317.                         if (uuid_version($uuid) === '4') { // <-- just to make 100% sure that it did output UUIDv4
  1318.                                 return $uuid;
  1319.                         }
  1320.                 }
  1321.  
  1322.                 # On Debian: apt-get install uuid-runtime
  1323.                 $out = array();
  1324.                 $ec = -1;
  1325.                 exec('uuidgen -r 2>/dev/null', $out, $ec);
  1326.                 if ($ec == 0) return trim($out[0]);
  1327.         }
  1328.  
  1329.         # Make the UUID by ourselves
  1330.  
  1331.         if (function_exists('openssl_random_pseudo_bytes')) {
  1332.                 // Source: https://www.php.net/manual/en/function.com-create-guid.php#119168
  1333.                 $data = openssl_random_pseudo_bytes(16);
  1334.                 $data[6] = chr(ord($data[6]) & 0x0f | 0x40);    // set version to 0100
  1335.                 $data[8] = chr(ord($data[8]) & 0x3f | 0x80);    // set bits 6-7 to 10
  1336.                 return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
  1337.         } else {
  1338.                 // Source: http://rogerstringer.com/2013/11/15/generate-uuids-php
  1339.                 return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
  1340.                         _random_int( 0, 0xffff ), _random_int( 0, 0xffff ),
  1341.                         _random_int( 0, 0xffff ),
  1342.                         _random_int( 0, 0x0fff ) | 0x4000,
  1343.                         _random_int( 0, 0x3fff ) | 0x8000,
  1344.                         _random_int( 0, 0xffff ), _random_int( 0, 0xffff ), _random_int( 0, 0xffff )
  1345.                 );
  1346.         }
  1347. }
  1348.  
  1349. # --------------------------------------
  1350. // Variant 1, Version 5 (SHA1 name based) UUID
  1351. # --------------------------------------
  1352.  
  1353. function gen_uuid_v5($namespace_uuid, $name) {
  1354.         return gen_uuid_sha1_namebased($namespace_uuid, $name);
  1355. }
  1356. function gen_uuid_sha1_namebased($namespace_uuid, $name) {
  1357.         if (($namespace_uuid ?? '') === '') throw new Exception("Namespace UUID missing");
  1358.         if (!uuid_valid($namespace_uuid)) throw new Exception("Invalid namespace UUID '$namespace_uuid'");
  1359.  
  1360.         $namespace_uuid = str_replace('-', '', $namespace_uuid);
  1361.         $namespace_uuid = hex2bin($namespace_uuid);
  1362.  
  1363.         $hash = sha1($namespace_uuid.$name);
  1364.         $hash[12] = '5'; // Set version: 5 = SHA1
  1365.         $hash[16] = dechex(hexdec($hash[16]) & 0b0011 | 0b1000); // Set variant to "0b10__" (RFC4122/DCE1.1)
  1366.  
  1367.         return substr($hash,  0, 8).'-'.
  1368.                substr($hash,  8, 4).'-'.
  1369.                substr($hash, 12, 4).'-'.
  1370.                substr($hash, 16, 4).'-'.
  1371.                substr($hash, 20, 12);
  1372. }
  1373.  
  1374. # --------------------------------------
  1375. // Variant 1, Version 6 (Reordered) UUID
  1376. # --------------------------------------
  1377.  
  1378. function gen_uuid_v6() {
  1379.         return gen_uuid_reordered();
  1380. }
  1381. function gen_uuid_reordered() {
  1382.         // Start with a UUIDv1
  1383.         $uuid = gen_uuid_timebased();
  1384.  
  1385.         // Convert to UUIDv6
  1386.         return uuid1_to_uuid6($uuid);
  1387. }
  1388. function uuid6_to_uuid1($hex) {
  1389.         $hex = uuid_canonize($hex);
  1390.         if ($hex === false) return false;
  1391.         $hex = preg_replace('@[^0-9A-F]@i', '', $hex);
  1392.         $hex = substr($hex, 7, 5).
  1393.                substr($hex, 13, 3).
  1394.                substr($hex, 3, 4).
  1395.                '1' . substr($hex, 0, 3).
  1396.                substr($hex, 16);
  1397.         return substr($hex,  0, 8).'-'.
  1398.                substr($hex,  8, 4).'-'.
  1399.                substr($hex, 12, 4).'-'.
  1400.                substr($hex, 16, 4).'-'.
  1401.                substr($hex, 20, 12);
  1402. }
  1403. function uuid1_to_uuid6($hex) {
  1404.         $hex = uuid_canonize($hex);
  1405.         if ($hex === false) return false;
  1406.         $hex = preg_replace('@[^0-9A-F]@i', '', $hex);
  1407.         $hex = substr($hex, 13, 3).
  1408.                substr($hex, 8, 4).
  1409.                substr($hex, 0, 5).
  1410.                '6' . substr($hex, 5, 3).
  1411.                substr($hex, 16);
  1412.         return substr($hex,  0, 8).'-'.
  1413.                substr($hex,  8, 4).'-'.
  1414.                substr($hex, 12, 4).'-'.
  1415.                substr($hex, 16, 4).'-'.
  1416.                substr($hex, 20, 12);
  1417. }
  1418.  
  1419. # --------------------------------------
  1420. // Variant 1, Version 7 (Unix Epoch) UUID
  1421. # --------------------------------------
  1422.  
  1423. function gen_uuid_v7(int $num_ms_frac_bits=12) {
  1424.         return gen_uuid_unix_epoch($num_ms_frac_bits);
  1425. }
  1426. function gen_uuid_unix_epoch(int $num_ms_frac_bits=12) {
  1427.         $uuid_nibbles = '';
  1428.  
  1429.         // Add the timestamp (milliseconds Unix)
  1430.         if (function_exists('gmp_init')) {
  1431.                 list($ms,$sec) = explode(' ', microtime(false));
  1432.                 $sec = gmp_init($sec, 10);
  1433.                 $ms = gmp_init(substr($ms,2,3), 10);
  1434.                 $unix_ts = gmp_strval(gmp_add(gmp_mul($sec, '1000'), $ms),16);
  1435.         } else {
  1436.                 $unix_ts = dechex((int)ceil(microtime(true)*1000));
  1437.         }
  1438.         $unix_ts = str_pad($unix_ts, 12, '0', STR_PAD_LEFT);
  1439.         $uuid_nibbles = $unix_ts;
  1440.  
  1441.         // Version = 7
  1442.         $uuid_nibbles .= '7';
  1443.  
  1444.         // Optional: millisecond fraction (max 12 bits)
  1445.         if (($num_ms_frac_bits < 0) || ($num_ms_frac_bits > 12)) throw new Exception("Invalid msec frac bits (must be 0..12)");
  1446.         $resolution_ns = 1000000 / pow(2,$num_ms_frac_bits);
  1447.         if ($num_ms_frac_bits > 0) {
  1448.                 $seconds_fraction = (float)explode(' ',microtime(false))[0]; // <sec=0>,<msec>
  1449.  
  1450.                 $ms_fraction = $seconds_fraction * 1000; // <msec>,<us>
  1451.                 $ms_fraction -= floor($ms_fraction); // <msec=0>,<us>
  1452.  
  1453.                 $ns_fraction = $ms_fraction * 1000000; // <ns>
  1454.                 $val = (int)ceil($ns_fraction / $resolution_ns);
  1455.  
  1456.                 // Currently, for the output we only allow frac bits 0, 4, 8, 12 (0-3 nibbles),
  1457.                 // since UUIDs are usually sorted in their hex notation, and one of the main
  1458.                 // reasons for using the sub-millisecond fractions it to increase monotonicity
  1459.                 $num_nibbles = (int)ceil($num_ms_frac_bits/4);
  1460.                 $uuid_nibbles .= str_pad(dechex($val), $num_nibbles, '0', STR_PAD_LEFT);
  1461.         }
  1462.  
  1463.         // TODO Not implemented: Optional counter (to be defined as parameter to this method)
  1464.         // The counter bits need to be spread before and after the variant bits
  1465.  
  1466.         // Fill with random bits (and the variant bits)
  1467.         $uuid = gen_uuid_random();
  1468.         $uuid = str_replace('-', '', $uuid);
  1469.         for ($i=0; $i<strlen($uuid_nibbles); $i++) $uuid[$i] = $uuid_nibbles[$i];
  1470.  
  1471.         // Wait to make sure that the time part changes if multiple UUIDs are generated
  1472.         if (time_nanosleep(0,(int)ceil($resolution_ns)) !== true) usleep((int)ceil($resolution_ns/1000));
  1473.  
  1474.         // Output
  1475.         return substr($uuid,  0, 8).'-'.
  1476.                substr($uuid,  8, 4).'-'.
  1477.                substr($uuid, 12, 4).'-'.
  1478.                substr($uuid, 16, 4).'-'.
  1479.                substr($uuid, 20, 12);
  1480. }
  1481.  
  1482. # --------------------------------------
  1483. // Variant 1, Version 8 (Custom) UUID
  1484. # --------------------------------------
  1485.  
  1486. function gen_uuid_v8($block1_32bit, $block2_16bit, $block3_12bit, $block4_14bit, $block5_48bit) {
  1487.         return gen_uuid_custom($block1_32bit, $block2_16bit, $block3_12bit, $block4_14bit, $block5_48bit);
  1488. }
  1489. function gen_uuid_custom($block1_32bit, $block2_16bit, $block3_12bit, $block4_14bit, $block5_48bit) {
  1490.         if (preg_replace('@[0-9A-F]@i', '', $block1_32bit) != '') throw new Exception("Invalid data for block 1. Must be hex input");
  1491.         if (preg_replace('@[0-9A-F]@i', '', $block2_16bit) != '') throw new Exception("Invalid data for block 2. Must be hex input");
  1492.         if (preg_replace('@[0-9A-F]@i', '', $block3_12bit) != '') throw new Exception("Invalid data for block 3. Must be hex input");
  1493.         if (preg_replace('@[0-9A-F]@i', '', $block4_14bit) != '') throw new Exception("Invalid data for block 4. Must be hex input");
  1494.         if (preg_replace('@[0-9A-F]@i', '', $block5_48bit) != '') throw new Exception("Invalid data for block 5. Must be hex input");
  1495.  
  1496.         $block1 = str_pad(substr($block1_32bit, -8),  8, '0', STR_PAD_LEFT);
  1497.         $block2 = str_pad(substr($block2_16bit, -4),  4, '0', STR_PAD_LEFT);
  1498.         $block3 = str_pad(substr($block3_12bit, -4),  4, '0', STR_PAD_LEFT);
  1499.         $block4 = str_pad(substr($block4_14bit, -4),  4, '0', STR_PAD_LEFT);
  1500.         $block5 = str_pad(substr($block5_48bit,-12), 12, '0', STR_PAD_LEFT);
  1501.  
  1502.         $block3[0] = '8'; // Version 8 = Custom
  1503.         $block4[0] = dechex(hexdec($block4[0]) & 0b0011 | 0b1000); // Variant 0b10__ = RFC4122
  1504.  
  1505.         return strtolower($block1.'-'.$block2.'-'.$block3.'-'.$block4.'-'.$block5);
  1506. }
  1507.  
  1508. function gen_uuid_v8_namebased($hash_algo, $namespace_uuid, $name) {
  1509.         if (($hash_algo ?? '') === '') throw new Exception("Hash algorithm argument missing");
  1510.  
  1511.         if (($namespace_uuid ?? '') === '') throw new Exception("Namespace UUID missing");
  1512.         if (!uuid_valid($namespace_uuid)) throw new Exception("Invalid namespace UUID '$namespace_uuid'");
  1513.  
  1514.         $uuid1 = uuid_valid($hash_algo) ? hex2bin(str_replace('-','',uuid_canonize($hash_algo))) : ''; // old "hash space" concept (dropped in Internet Draft 12)
  1515.         $uuid2 = hex2bin(str_replace('-','',uuid_canonize($namespace_uuid)));
  1516.         $payload = $uuid1 . $uuid2 . $name;
  1517.  
  1518.         if (uuid_valid($hash_algo)) {
  1519.                 if (uuid_equal($hash_algo, '59031ca3-fbdb-47fb-9f6c-0f30e2e83145')) $hash_algo = 'sha224';
  1520.                 if (uuid_equal($hash_algo, '3fb32780-953c-4464-9cfd-e85dbbe9843d')) $hash_algo = 'sha256';
  1521.                 if (uuid_equal($hash_algo, 'e6800581-f333-484b-8778-601ff2b58da8')) $hash_algo = 'sha384';
  1522.                 if (uuid_equal($hash_algo, '0fde22f2-e7ba-4fd1-9753-9c2ea88fa3f9')) $hash_algo = 'sha512';
  1523.                 if (uuid_equal($hash_algo, '003c2038-c4fe-4b95-a672-0c26c1b79542')) $hash_algo = 'sha512/224';
  1524.                 if (uuid_equal($hash_algo, '9475ad00-3769-4c07-9642-5e7383732306')) $hash_algo = 'sha512/256';
  1525.                 if (uuid_equal($hash_algo, '9768761f-ac5a-419e-a180-7ca239e8025a')) $hash_algo = 'sha3-224';
  1526.                 if (uuid_equal($hash_algo, '2034d66b-4047-4553-8f80-70e593176877')) $hash_algo = 'sha3-256';
  1527.                 if (uuid_equal($hash_algo, '872fb339-2636-4bdd-bda6-b6dc2a82b1b3')) $hash_algo = 'sha3-384';
  1528.                 if (uuid_equal($hash_algo, 'a4920a5d-a8a6-426c-8d14-a6cafbe64c7b')) $hash_algo = 'sha3-512';
  1529.                 if (uuid_equal($hash_algo, '7ea218f6-629a-425f-9f88-7439d63296bb')) $hash_algo = 'shake128';
  1530.                 if (uuid_equal($hash_algo, '2e7fc6a4-2919-4edc-b0ba-7d7062ce4f0a')) $hash_algo = 'shake256';
  1531.         }
  1532.  
  1533.         if ($hash_algo == 'shake128') $hash = shake128($payload, 16/*min. required bytes*/, false);
  1534.         else if ($hash_algo == 'shake256') $hash = shake256($payload, 16/*min. required bytes*/, false);
  1535.         else $hash = hash($hash_algo, $payload, false);
  1536.  
  1537.         if ($hash == null) {
  1538.                 throw new Exception("Unknown Hash Algorithm $hash_algo");
  1539.         }
  1540.  
  1541.         $hash = str_pad($hash, 32, '0', STR_PAD_RIGHT); // fill short hashes with zeros to the right
  1542.  
  1543.         $hash[12] = '8'; // Set version: 8 = Custom
  1544.         $hash[16] = dechex(hexdec($hash[16]) & 0b0011 | 0b1000); // Set variant to "0b10__" (RFC4122/DCE1.1)
  1545.  
  1546.         return substr($hash,  0, 8).'-'.
  1547.                substr($hash,  8, 4).'-'.
  1548.                substr($hash, 12, 4).'-'.
  1549.                substr($hash, 16, 4).'-'.
  1550.                substr($hash, 20, 12);
  1551. }
  1552.  
  1553. /**
  1554.  * The sorting of SQL Server is rather confusing and incompatible with UUIDv6 and UUIDv7.
  1555.  * Therefore this method generates UUID which are sortable by SQL Server.
  1556.  * Version 1: Resolution of 1 milliseconds, random part of 16 bits, local timezone, 48 zero bits "signature", NOT UUIDv8 conform.
  1557.  * Version 2: Resolution of 1 milliseconds, random part of 16 bits, UTC time, 48 bit random "signature", UUIDv8 conform.
  1558.  * C# implementation: https://gist.github.com/danielmarschall/7fafd270a3bc107d38e8449ce7420c25
  1559.  * PHP implementation: https://github.com/danielmarschall/uuid_mac_utils/blob/master/includes/uuid_utils.inc.php
  1560.  *
  1561.  * @param int      $hickelUuidVersion (optional)
  1562.  * @param DateTime $dt                (optional)
  1563.  * @return string The UUID
  1564.  */
  1565. function gen_uuid_v8_sqlserver_sortable(int $hickelUuidVersion = 2, DateTime $dt = null): string {
  1566.         // The sorting in SQL Server is like this:
  1567.  
  1568.         if ($dt == null) $dt = new DateTime();
  1569.  
  1570.         // First Sort block 5, nibbles from left to right (i.e. 000000000001 < 000000000010 < ... < 010000000000 < 100000000000)
  1571.         if ($hickelUuidVersion == 1) {
  1572.                 $block5 = "000000000000";
  1573.         } else if ($hickelUuidVersion == 2) {
  1574.                 $block5 = "5ce32bd83b96";
  1575.         } else {
  1576.                 throw new Exception("Invalid version");
  1577.         }
  1578.  
  1579.         // Then: Sort block 4, nibbles from left to right
  1580.         if ($hickelUuidVersion == 1) {
  1581.                 $year = $dt->format('Y');
  1582.                 $block4 = substr($year, 2, 2).substr($year, 0, 2); // Example: 0x2420 = 2024
  1583.         } else {
  1584.                 $variant = 0x8; // First nibble needs to be 0b10_ (0x8-0xB) for "RFC 4122bis". We use it to store 2 more random bits.
  1585.                 $unused2bits = 0; // Cannot be used for random, because it would affect the sorting
  1586.                 $year = $dt->format('Y');
  1587.                 $block4 = sprintf('%01x%03x', $variant + ($unused2bits & 0x3), $year);
  1588.         }
  1589.  
  1590.         // Then: Sort block 3, bytes from right to left (i.e. 0100 < 1000 < 0001 < 0010)
  1591.         if ($hickelUuidVersion == 1) {
  1592.                 $block3 = $dt->format('dm');
  1593.         } else {
  1594.                 $uuidVersion = 8; // First nibble needs to be "8" for "UUIDv8 = Custom UUID"
  1595.                 $dayOfYear = intval($dt->format('z')) + 1; /* 1..366 */
  1596.                 $block3 = sprintf('%01x%03x', $uuidVersion, $dayOfYear);
  1597.         }
  1598.  
  1599.         // Then: Sort block 2, bytes from right to left
  1600.         if ($hickelUuidVersion == 1) {
  1601.                 $block2 = $dt->format('ih');
  1602.         } else {
  1603.                 $minuteOfDay = (intval($dt->format('i')) + intval($dt->format('h')) * 60) + 1; // 1..1440
  1604.                 $block2 = sprintf('%04x', $minuteOfDay);
  1605.         }
  1606.  
  1607.         // Then: Sort block 1, bytes from right to left
  1608.         if ($hickelUuidVersion == 1) {
  1609.                 $millisecond8bits = ceil(($dt->format('v') / 999) * 255); // deviation -4ms..0ms
  1610.                 $rnd16bits = _random_int(0x0000, 0xFFFF-1);
  1611.                 $block1 = sprintf('%04x%02x', $rnd16bits, $millisecond8bits).$dt->format('s');
  1612.         } else {
  1613.                 $millisecond8bits = round(($dt->format('v') / 999) * 255); // deviation -2ms..2ms
  1614.                 $rnd16bits = _random_int(0x0000, 0xFFFF);
  1615.                 $block1 = sprintf('%04x%02x%02x', $rnd16bits, $millisecond8bits, $dt->format('s'));
  1616.         }
  1617.  
  1618.         $sleep_ms = (int)ceil(999 / 255); // Make sure that "millisecond" is not repeated on this system
  1619.         if (time_nanosleep(0,$sleep_ms*1000*1000) !== true) usleep($sleep_ms*1000);
  1620.  
  1621.         return strtolower("$block1-$block2-$block3-$block4-$block5");
  1622. }
  1623.  
  1624. # --------------------------------------
  1625.  
  1626. // http://php.net/manual/de/function.hex2bin.php#113057
  1627. if (!function_exists('hex2bin')) {
  1628.     function hex2bin($str) {
  1629.         $sbin = "";
  1630.         $len = strlen($str);
  1631.         for ( $i = 0; $i < $len; $i += 2 ) {
  1632.             $sbin .= pack("H*", substr($str, $i, 2));
  1633.         }
  1634.         return $sbin;
  1635.     }
  1636. }
  1637.  
  1638. // https://stackoverflow.com/questions/72127764/shift-right-left-bitwise-operators-in-php7-gmp-extension
  1639. if (!function_exists('gmp_shiftl')) {
  1640.     function gmp_shiftl($x,$n) { // shift left
  1641.         return(gmp_mul($x,gmp_pow(2,$n)));
  1642.     }
  1643. }
  1644.  
  1645. if (!function_exists('gmp_shiftr')) {
  1646.     function gmp_shiftr($x,$n) { // shift right
  1647.         return(gmp_div_q($x,gmp_pow(2,$n)));
  1648.     }
  1649. }
  1650.  
  1651. function shake128(string $msg, int $outputLength=512, bool $binary=false): string {
  1652.         include_once __DIR__.'/SHA3.php';
  1653.         $sponge = SHA3::init(SHA3::SHAKE128);
  1654.         $sponge->absorb($msg);
  1655.         $bin = $sponge->squeeze($outputLength);
  1656.         return $binary ? $bin : bin2hex($bin);
  1657. }
  1658.  
  1659. function shake256(string $msg, int $outputLength=512, bool $binary=false): string {
  1660.         include_once __DIR__.'/SHA3.php';
  1661.         $sponge = SHA3::init(SHA3::SHAKE256);
  1662.         $sponge->absorb($msg);
  1663.         $bin = $sponge->squeeze($outputLength);
  1664.         return $binary ? $bin : bin2hex($bin);
  1665. }
  1666.  
  1667. /**
  1668.  * Converts the day of year of a year into a DateTime object
  1669.  * @param int $year The year
  1670.  * @param int $dayOfYear The day of year (value 1 till 365 or 1 till 366 for leap years)
  1671.  * @return \DateTime The resulting date
  1672.  */
  1673. function getDateFromDay(int $year, int $dayOfYear): \DateTime {
  1674.         // Note: "Y z" and "z Y" make a difference for leap years (last tested with PHP 8.0.3)
  1675.         $date = \DateTime::createFromFormat('Y z', strval($year) . ' ' . strval($dayOfYear-1));
  1676.         return $date;
  1677. }
  1678.  
  1679.