Subversion Repositories uuid_mac_utils

Rev

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