Subversion Repositories uuid_mac_utils

Rev

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