Subversion Repositories oidplus

Rev

Rev 1411 | Rev 1424 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

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