Subversion Repositories uuid_mac_utils

Rev

Rev 50 | Rev 52 | 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-07-15
  7.  *
  8.  * Licensed under the Apache License, Version 2.0 (the "License");
  9.  * you may not use this file except in compliance with the License.
  10.  * You may obtain a copy of the License at
  11.  *
  12.  *     http://www.apache.org/licenses/LICENSE-2.0
  13.  *
  14.  * Unless required by applicable law or agreed to in writing, software
  15.  * distributed under the License is distributed on an "AS IS" BASIS,
  16.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17.  * See the License for the specific language governing permissions and
  18.  * limitations under the License.
  19.  */
  20.  
  21. # This library requires either the GMP extension (or BCMath if gmp_supplement.inc.php is present)
  22. // TODO: If we are on 64 bit PHP (PHP_INT_SIZE > 4), then replace GMP with normal PHP operations
  23.  
  24. if (file_exists($f = __DIR__ . '/mac_utils.inc.php')) include_once $f;
  25. else if (file_exists($f = __DIR__ . '/mac_utils.inc.phps')) include_once $f;
  26.  
  27. if (file_exists($f = __DIR__ . '/gmp_supplement.inc.php')) include_once $f;
  28. else if (file_exists($f = __DIR__ . '/gmp_supplement.inc.phps')) include_once $f;
  29.  
  30. if (file_exists($f = __DIR__ . '/OidDerConverter.class.php')) include_once $f;
  31. else if (file_exists($f = __DIR__ . '/OidDerConverter.class.phps')) include_once $f;
  32.  
  33. const UUID_NAMEBASED_NS_DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; // FQDN
  34. const UUID_NAMEBASED_NS_URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
  35. const UUID_NAMEBASED_NS_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8';
  36. const UUID_NAMEBASED_NS_X500_DN = '6ba7b814-9dad-11d1-80b4-00c04fd430c8'; // "DER or text encoding" according to RFC4122bis
  37.  
  38. if (!function_exists('_random_int')) {
  39.         function _random_int($min, $max) {
  40.                 // This function tries a CSRNG and falls back to a RNG if no CSRNG is available
  41.                 try {
  42.                         return random_int($min, $max);
  43.                 } catch (Exception $e) {
  44.                         return mt_rand($min, $max);
  45.                 }
  46.         }
  47. }
  48.  
  49. function uuid_valid($uuid) {
  50.         $uuid = str_replace(array('-', '{', '}'), '', $uuid);
  51.         $uuid = strtoupper($uuid);
  52.         #$uuid = trim($uuid);
  53.  
  54.         if (strlen($uuid) != 32) return false;
  55.  
  56.         $uuid = preg_replace('@[0-9A-F]@i', '', $uuid);
  57.  
  58.         return ($uuid == '');
  59. }
  60.  
  61. function uuid_version($uuid) {
  62.         $uuid = uuid_canonize($uuid);
  63.         if (!$uuid) return false;
  64.         return substr($uuid, 19, 1);
  65. }
  66.  
  67. function uuid_info($uuid, $echo=true) {
  68.         if (!uuid_valid($uuid)) return false;
  69.  
  70.         $oid = uuid_to_oid($uuid);
  71.  
  72.         echo sprintf("%-32s %s\n", "Your input:", $uuid);
  73.         echo "\n";
  74.         echo "<u>Various notations:</u>\n";
  75.         echo "\n";
  76.         echo sprintf("%-32s %s\n", "URN:", 'urn:uuid:' . strtolower(oid_to_uuid(uuid_to_oid($uuid))));
  77.         echo sprintf("%-32s %s\n", "URI:", 'uuid:' . strtolower(oid_to_uuid(uuid_to_oid($uuid))));
  78.         echo sprintf("%-32s %s\n", "Microsoft GUID syntax:", '{' . strtoupper(oid_to_uuid(uuid_to_oid($uuid))) . '}');
  79.         echo sprintf("%-32s %s\n", "C++ struct syntax:", uuid_c_syntax($uuid));
  80.         echo "\n";
  81.         echo sprintf("%-32s %s\n", "As OID:", $oid);
  82.         if (class_exists('OidDerConverter')) {
  83.                 echo sprintf("%-32s %s\n", "DER encoding of OID:", OidDerConverter::hexarrayToStr(OidDerConverter::oidToDER($oid)));
  84.         }
  85.         echo "\n";
  86.         echo "<u>Interpretation of the UUID:</u>\n\n";
  87.  
  88.         if (!$echo) ob_start();
  89.  
  90.         #$uuid = trim($uuid);
  91.         # $uuid = str_replace(array('-', '{', '}'), '', $uuid);
  92.         $uuid = strtolower($uuid);
  93.         $uuid = preg_replace('@[^0-9A-F]@i', '', $uuid);
  94.  
  95.         $x = hexdec(substr($uuid, 16, 1));
  96.              if ($x >= 14 /* 0b1110 */) $variant = 3;
  97.         else if ($x >= 12 /* 0b110_ */) $variant = 2;
  98.         else if ($x >=  8 /* 0b10__ */) $variant = 1;
  99.         else if ($x >=  0 /* 0b0___ */) $variant = 0;
  100.         else $variant = -1; // should not happen
  101.  
  102.         if ($uuid == '00000000000000000000000000000000') {
  103.                 echo sprintf("%-32s %s\n", "Special Use:", "Nil UUID");
  104.                 echo "\n";
  105.         }
  106.         else if ($uuid == 'ffffffffffffffffffffffffffffffff') {
  107.                 echo sprintf("%-32s %s\n", "Special Use:", "Max UUID");
  108.                 echo "\n";
  109.         }
  110.  
  111.         switch ($variant) {
  112.                 case 0:
  113.                         echo sprintf("%-32s %s\n", "Variant:", "[0b0__] Network Computing System (NCS)");
  114.  
  115.                         /*
  116.                          * Internal structure of variant #0 UUIDs
  117.                          *
  118.                          * The first 6 octets are the number of 4 usec units of time that have
  119.                          * passed since 1/1/80 0000 GMT.  The next 2 octets are reserved for
  120.                          * future use.  The next octet is an address family.  The next 7 octets
  121.                          * are a host ID in the form allowed by the specified address family.
  122.                          *
  123.                          * Note that while the family field (octet 8) was originally conceived
  124.                          * of as being able to hold values in the range [0..255], only [0..13]
  125.                          * were ever used.  Thus, the 2 MSB of this field are always 0 and are
  126.                          * used to distinguish old and current UUID forms.
  127.                          */
  128.  
  129.                         /*
  130.                         Variant 0 UUID
  131.                         - 32 bit High Time
  132.                         - 16 bit Low Time
  133.                         - 16 bit Reserved
  134.                         -  1 bit Variant (fix 0b0)
  135.                         -  7 bit Family
  136.                         - 56 bit Node
  137.                         */
  138.  
  139.                         // Example of an UUID: 333a2276-0000-0000-0d00-00809c000000
  140.  
  141.                         // TODO: also show legacy format, e.g. 458487b55160.02.c0.64.02.03.00.00.00
  142.  
  143.                         # see also some notes at See https://github.com/cjsv/uuid/blob/master/Doc
  144.  
  145.                         /*
  146.                         NOTE: A generator is not possible, because there are no timestamps left!
  147.                         The last possible timestamp was:
  148.                             [0xFFFFFFFFFFFF] 2015-09-05 05:58:26'210655 GMT
  149.                         That is in the following UUID:
  150.                             ffffffff-ffff-0000-027f-000001000000
  151.                         Current timestamp generator:
  152.                             echo dechex(round((microtime(true)+315532800)*250000));
  153.                         */
  154.  
  155.                         # Timestamp: Count of 4us intervals since 01 Jan 1980 00:00:00 GMT
  156.                         # 1/0,000004 = 250000
  157.                         # Seconds between 1970 and 1980 : 315532800
  158.                         # 250000*315532800=78883200000000
  159.                         $timestamp = substr($uuid, 0, 12);
  160.                         $ts = gmp_init($timestamp, 16);
  161.                         $ts = gmp_add($ts, gmp_init("78883200000000", 10));
  162.                         $ms = gmp_mod($ts, gmp_init("250000", 10));
  163.                         $ts = gmp_div($ts, gmp_init("250000", 10));
  164.                         $ts = gmp_strval($ts, 10);
  165.                         $ms = gmp_strval($ms, 10);
  166.                         $ts = gmdate('Y-m-d H:i:s', intval($ts))."'".str_pad($ms, 6/*us*/, '0', STR_PAD_LEFT).' GMT';
  167.                         echo sprintf("%-32s %s\n", "Timestamp:", "[0x$timestamp] $ts");
  168.  
  169.                         $reserved = substr($uuid, 12, 4);
  170.                         echo sprintf("%-32s %s\n", "Reserved:", "[0x$reserved]");
  171.  
  172.                         $family_hex = substr($uuid, 16, 2);
  173.                         $family_dec = hexdec($family_hex);
  174.                         $nodeid_hex = substr($uuid, 18, 14);
  175.                         $nodeid_dec = hexdec($nodeid_hex);
  176.  
  177.                         // Sources:
  178.                         // - https://bitsavers.org/pdf/ibm/rs6000/aix_3.0/SC23-2206-0_AIX_Version_3_for_RS6000_Communications_Programming_Concepts_199003.pdf
  179.                         // - (For comparison) https://github.com/uuid6/uuid6-ietf-draft/issues/26#issuecomment-1062164457
  180.                         // - (For comparison) https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.addressfamily?view=net-7.0 [numbers 0..13 are mostly identical]
  181.  
  182.                         if ($family_dec == 0) {
  183.                                 # Microsoft's AdressFamily: Unspecified 0       Unspecified address family.
  184.                                 # AIX 3.0 Manual:  0   unspec = Unspecified
  185.                                 $family_name = 'socket_$unspec (Unspecified)';
  186.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  187.                         }
  188.                         else if ($family_dec == 1) {
  189.                                 # Microsoft's AdressFamily: Unix        1       Unix local to host address.
  190.                                 # AIX 3.0 Manual:  1   unix = Local to host (pipes, portals)
  191.                                 $family_name = 'socket_$unix (Local to host, e.g. pipes, portals)';
  192.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  193.                         }
  194.                         else if ($family_dec == 2) {
  195.                                 # Microsoft's AdressFamily: InterNetwork        2       Address for IP version 4.
  196.                                 # AIX 3.0 Manual:  2   ip = Internet Protocols
  197.                                 $family_name = 'socket_$internet (Internet Protocols, e.g. IPv4)';
  198.                                 // 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
  199.                                 // := [
  200.                                 //    time_high := 16#458487df,
  201.                                 //    time_low := 16#9fb2,
  202.                                 //    reserved := 16#000,
  203.                                 //    family := chr(16#02),
  204.                                 //    host := [chr(16#c0), chr(16#64), chr(16#02), chr(16#03),
  205.                                 //             chr(16#00), chr(16#00), chr(16#00)]
  206.                                 //    ]
  207.                                 // This means that the IP address is 32 bits hex, and 32 bits are unused
  208.                                 $nodeid_desc = hexdec(substr($nodeid_hex,0,2)).'.'.
  209.                                                hexdec(substr($nodeid_hex,2,2)).'.'.
  210.                                                hexdec(substr($nodeid_hex,4,2)).'.'.
  211.                                                hexdec(substr($nodeid_hex,6,2));
  212.                                 $rest = substr($nodeid_hex,8,6);
  213.                                 if ($rest != '000000') $nodeid_desc .= " + unexpected rest 0x$rest";
  214.                         }
  215.                         else if ($family_dec == 3) {
  216.                                 # Microsoft's AdressFamily: ImpLink     3       ARPANET IMP address.
  217.                                 # AIX 3.0 Manual:  3   implink = ARPANET imp addresses
  218.                                 $family_name = 'socket_$implink (ARPANET imp addresses)';
  219.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  220.                         }
  221.                         else if ($family_dec == 4) {
  222.                                 # Microsoft's AdressFamily: Pup 4       Address for PUP protocols.
  223.                                 # AIX 3.0 Manual:  4   pup = Pup protocols (for example, BSP)
  224.                                 $family_name = 'socket_$pup (Pup protocols, e.g. BSP)';
  225.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  226.                         }
  227.                         else if ($family_dec == 5) {
  228.                                 # Microsoft's AdressFamily: Chaos       5       Address for MIT CHAOS protocols.
  229.                                 # AIX 3.0 Manual:  5   chaos = MIT CHAOS protocols
  230.                                 $family_name = 'socket_$chaos (MIT CHAOS protocols)';
  231.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  232.                         }
  233.                         else if ($family_dec == 6) {
  234.                                 # Microsoft's AdressFamily: NS  6       Address for Xerox NS protocols.
  235.                                 # Microsoft's AdressFamily: Ipx 6       IPX or SPX address.
  236.                                 # AIX 3.0 Manual:  6   ns = XEROX NS protocols
  237.                                 $family_name = 'socket_$ns (XEROX NS protocols)';
  238.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  239.                         }
  240.                         else if ($family_dec == 7) {
  241.                                 # Microsoft's AdressFamily: Osi 7       Address for OSI protocols.
  242.                                 # Microsoft's AdressFamily: Iso 7       Address for ISO protocols.
  243.                                 # AIX 3.0 Manual:  7   nbs = NBS protocols
  244.                                 $family_name = 'socket_$nbs (NBS protocols)';
  245.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  246.                         }
  247.                         else if ($family_dec == 8) {
  248.                                 # Microsoft's AdressFamily: Ecma        8       European Computer Manufacturers Association (ECMA) address.
  249.                                 # AIX 3.0 Manual:  8   ecma = European computer manufacturers
  250.                                 $family_name = 'socket_$ecma (European computer manufacturers protocols)';
  251.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  252.                         }
  253.                         else if ($family_dec == 9) {
  254.                                 # Microsoft's AdressFamily: DataKit     9       Address for Datakit protocols.
  255.                                 # AIX 3.0 Manual:  9   datakit = Datakit protocols
  256.                                 $family_name = 'socket_$datakit (Datakit protocols)';
  257.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  258.                         }
  259.                         else if ($family_dec == 10) {
  260.                                 # Microsoft's AdressFamily: Ccitt       10      Addresses for CCITT protocols, such as X.25.
  261.                                 # AIX 3.0 Manual:  A   ccitt = CCITT protocols (for example, X.25)
  262.                                 $family_name = 'socket_$ccitt (CCITT protocols, e.g. X.25)';
  263.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  264.                         }
  265.                         else if ($family_dec == 11) {
  266.                                 # Microsoft's AdressFamily: Sna 11      IBM SNA address.
  267.                                 # AIX 3.0 Manual:  B   sna = IBM SNA
  268.                                 $family_name = 'socket_$sna (IBM SNA)';
  269.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  270.                         }
  271.                         else if ($family_dec == 12) {
  272.                                 # Microsoft's AdressFamily: DecNet      12      DECnet address.
  273.                                 # AIX 3.0 Manual:  C   unspec2 = Unspecified
  274.                                 $family_name = 'socket_$unspec2 (Unspecified)';
  275.                                 $nodeid_desc = ''; // TODO: how to interprete the Node-ID of that family?
  276.                         }
  277.                         else if ($family_dec == 13) {
  278.                                 # Microsoft's AdressFamily: DataLink    13      Direct data-link interface address.
  279.                                 # AIX 3.0 Manual:  D   dds = Domain DDS protocol
  280.                                 # Some also call this "Data Link" ... Is that correct?
  281.                                 $family_name = 'socket_$dds (Domain DDS protocol)';
  282.                                 // 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
  283.                                 // = { 0x34dc23af,
  284.                                 //    0xf000,
  285.                                 //    0x0000,
  286.                                 //    0x0d,
  287.                                 //    {0x00, 0x00, 0x7c, 0x5f, 0x00, 0x00, 0x00} };
  288.                                 // https://github.com/cjsv/uuid/blob/master/Doc writes:
  289.                                 //    "Family 13 (dds) looks like node is 00 | nnnnnn 000000."
  290.  
  291.                                 $nodeid_desc = '';
  292.  
  293.                                 $start = substr($nodeid_hex,0,2);
  294.                                 if ($start != '00') $nodeid_desc .= "unexpected start 0x$start + ";
  295.  
  296.                                 $nodeid_desc .= ($nodeid_dec >> 24) & 0xFFFFFF;
  297.  
  298.                                 $rest = substr($nodeid_hex,8,6);
  299.                                 if ($rest != '000000') $nodeid_desc .= " + unexpected rest 0x$rest";
  300.                         } else {
  301.                                 $family_name = "Unknown (Family $family_dec)"; # There are probably no more families
  302.                                 $nodeid_desc = "Unknown";
  303.                         }
  304.                         echo sprintf("%-32s %s\n", "Family:", "[0x$family_hex] $family_name");
  305.  
  306.                         echo sprintf("%-32s %s\n", "Node ID:", "[0x$nodeid_hex] $nodeid_desc");
  307.  
  308.                         break;
  309.                 case 1:
  310.                         // 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
  311.  
  312.                         $version = hexdec(substr($uuid, 12, 1));
  313.  
  314.                         if ($version <= 2) {
  315.                                 echo sprintf("%-32s %s\n", "Variant:", "[0b10_] RFC 4122 (Leach-Mealling-Salz) / DCE 1.1");
  316.                         } else if (($version >= 3) && ($version <= 5)) {
  317.                                 echo sprintf("%-32s %s\n", "Variant:", "[0b10_] RFC 4122 (Leach-Mealling-Salz)");
  318.                         } else if (($version >= 6) && ($version <= 8)) {
  319.                                 echo sprintf("%-32s %s\n", "Variant:", "[0b10_] RFC 4122bis (Leach-Mealling-Peabody-Davis)");
  320.                         } else {
  321.                                 echo sprintf("%-32s %s\n", "Variant:", "[0b10_] RFC 4122?");
  322.                         }
  323.  
  324.                         switch ($version) {
  325.                                 case 6:
  326.                                         /*
  327.                                         Variant 1, Version 6 UUID
  328.                                         - 48 bit High Time
  329.                                         -  4 bit Version (fix 0x6)
  330.                                         - 12 bit Low Time
  331.                                         -  2 bit Variant (fix 0b10)
  332.                                         -  6 bit Clock Sequence High
  333.                                         -  8 bit Clock Sequence Low
  334.                                         - 48 bit MAC Address
  335.                                         */
  336.                                         echo sprintf("%-32s %s\n", "Version:", "[0x6] Reordered Time-Based");
  337.                                         $uuid = substr($uuid,  0, 8).'-'.
  338.                                                 substr($uuid,  8, 4).'-'.
  339.                                                 substr($uuid, 12, 4).'-'.
  340.                                                 substr($uuid, 16, 4).'-'.
  341.                                                 substr($uuid, 20, 12);
  342.                                         $uuid = uuid6_to_uuid1($uuid);
  343.                                         $uuid = str_replace('-', '', $uuid);
  344.  
  345.                                 /* fallthrough */
  346.                                 case 1:
  347.                                         /*
  348.                                         Variant 1, Version 1 UUID
  349.                                         - 32 bit Low Time
  350.                                         - 16 bit Mid Time
  351.                                         -  4 bit Version (fix 0x1)
  352.                                         - 12 bit High Time
  353.                                         -  2 bit Variant (fix 0b10)
  354.                                         -  6 bit Clock Sequence High
  355.                                         -  8 bit Clock Sequence Low
  356.                                         - 48 bit MAC Address
  357.                                         */
  358.  
  359.                                         if ($version == 1) echo sprintf("%-32s %s\n", "Version:", "[0x1] Time-based with unique host identifier");
  360.  
  361.                                         # Timestamp: Count of 100ns intervals since 15 Oct 1582 00:00:00
  362.                                         # 1/0,0000001 = 10000000
  363.                                         $timestamp = substr($uuid, 13, 3).substr($uuid, 8, 4).substr($uuid, 0, 8);
  364.                                         $ts = gmp_init($timestamp, 16);
  365.                                         $ts = gmp_sub($ts, gmp_init("122192928000000000", 10));
  366.                                         $ms = gmp_mod($ts, gmp_init("10000000", 10));
  367.                                         $ts = gmp_div($ts, gmp_init("10000000", 10));
  368.                                         $ts = gmp_strval($ts, 10);
  369.                                         $ms = gmp_strval($ms, 10);
  370.                                         $ts = gmdate('Y-m-d H:i:s', intval($ts))."'".str_pad($ms, 7/*0.1us*/, '0', STR_PAD_LEFT).' GMT';
  371.                                         echo sprintf("%-32s %s\n", "Timestamp:", "[0x$timestamp] $ts");
  372.  
  373.                                         $x = hexdec(substr($uuid, 16, 4));
  374.                                         $dec = $x & 0x3FFF; // The highest 2 bits are used by "variant" (10x)
  375.                                         $hex = substr($uuid, 16, 4);
  376.                                         $hex = '<abbr title="The highest 2 bits are used by the UUID variant (10xx)">'.$hex[0].'</abbr>'.substr($hex,1);
  377.                                         echo sprintf("%-32s %s\n", "Clock ID:", "[0x$hex] $dec");
  378.  
  379.                                         $x = substr($uuid, 20, 12);
  380.                                         $nodeid = '';
  381.                                         for ($i=0; $i<6; $i++) {
  382.                                                 $nodeid .= substr($x, $i*2, 2);
  383.                                                 if ($i != 5) $nodeid .= '-';
  384.                                         }
  385.                                         $nodeid = strtoupper($nodeid);
  386.                                         echo sprintf("%-32s %s\n", "Node ID:", "[0x$x] $nodeid");
  387.  
  388.                                         echo "\n<u>In case that this Node ID is a MAC address, here is the interpretation of that MAC address:</u>\n\n";
  389.                                         decode_mac(strtoupper($nodeid));
  390.  
  391.                                         break;
  392.                                 case 2:
  393.                                         /*
  394.                                         Variant 1, Version 2 UUID
  395.                                         - 32 bit Local Domain Number
  396.                                         - 16 bit Mid Time
  397.                                         -  4 bit Version (fix 0x2)
  398.                                         - 12 bit High Time
  399.                                         -  2 bit Variant (fix 0b10)
  400.                                         -  6 bit Clock Sequence
  401.                                         -  8 bit Local Domain
  402.                                         - 48 bit MAC Address
  403.                                         */
  404.  
  405.                                         // see also https://unicorn-utterances.com/posts/what-happened-to-uuid-v2
  406.  
  407.                                         echo sprintf("%-32s %s\n", "Version:", "[0x2] DCE Security version");
  408.  
  409.                                         # 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".
  410.                                         $x = substr($uuid, 18, 2);
  411.                                         if ($x == '00') $domain_info = 'Person (e.g. POSIX UID)';
  412.                                         else if ($x == '01') $domain_info = 'Group (e.g. POSIX GID)';
  413.                                         else if ($x == '02') $domain_info = 'Organization';
  414.                                         else $domain_info = 'site-defined (Domain '.hexdec($x).')';
  415.                                         echo sprintf("%-32s %s\n", "Local Domain:", "[0x$x] $domain_info");
  416.  
  417.                                         # 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.
  418.                                         $x = substr($uuid, 0, 8);
  419.                                         $dec = hexdec($x);
  420.                                         echo sprintf("%-32s %s\n", "Local Domain Number:", "[0x$x] $dec");
  421.  
  422.                                         # Timestamp: Count of 100ns intervals since 15 Oct 1582 00:00:00
  423.                                         # 1/0,0000001 = 10000000
  424.                                         $timestamp = substr($uuid, 13, 3).substr($uuid, 8, 4).'00000000';
  425.                                         $ts = gmp_init($timestamp, 16);
  426.                                         $ts = gmp_sub($ts, gmp_init("122192928000000000", 10));
  427.                                         $ms = gmp_mod($ts, gmp_init("10000000", 10));
  428.                                         $ts = gmp_div($ts, gmp_init("10000000", 10));
  429.                                         $ts = gmp_strval($ts, 10);
  430.                                         $ms = gmp_strval($ms, 10);
  431.                                         $ts_min = gmdate('Y-m-d H:i:s', intval($ts))."'".str_pad($ms, 7/*0.1us*/, '0', STR_PAD_LEFT).' GMT';
  432.  
  433.                                         $timestamp = substr($uuid, 13, 3).substr($uuid, 8, 4).'FFFFFFFF';
  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_max = 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)/*.'xxxxxxxx'*/;
  443.                                         echo sprintf("%-32s %s\n", "Timestamp:", "[0x$timestamp] $ts_min - $ts_max");
  444.  
  445.                                         $x = hexdec(substr($uuid, 16, 2));
  446.                                         $dec = $x & 0x3F; // The highest 2 bits are used by "variant" (10xx)
  447.                                         $hex = substr($uuid, 16, 2);
  448.                                         $hex = '<abbr title="The highest 2 bits are used by the UUID variant (10xx)">'.$hex[0].'</abbr>'.substr($hex,1);
  449.                                         echo sprintf("%-32s %s\n", "Clock ID:", "[0x$hex] $dec");
  450.  
  451.                                         $x = substr($uuid, 20, 12);
  452.                                         $nodeid = '';
  453.                                         for ($i=0; $i<6; $i++) {
  454.                                                 $nodeid .= substr($x, $i*2, 2);
  455.                                                 if ($i != 5) $nodeid .= '-';
  456.                                         }
  457.                                         $nodeid = strtoupper($nodeid);
  458.                                         echo sprintf("%-32s %s\n", "Node ID:", "[0x$x] $nodeid");
  459.  
  460.                                         echo "\n<u>In case that this Node ID is a MAC address, here is the interpretation of that MAC address:</u>\n\n";
  461.                                         decode_mac(strtoupper($nodeid));
  462.  
  463.                                         break;
  464.                                 case 3:
  465.                                         /*
  466.                                         Variant 1, Version 3 UUID
  467.                                         - 48 bit Hash High
  468.                                         -  4 bit Version (fix 0x3)
  469.                                         - 12 bit Hash Mid
  470.                                         -  2 bit Variant (fix 0b10)
  471.                                         - 62 bit Hash Low
  472.                                         */
  473.  
  474.                                         echo sprintf("%-32s %s\n", "Version:", "[0x3] Name-based (MD5 hash)");
  475.  
  476.                                         $hash = str_replace('-', '', strtolower($uuid));
  477.  
  478.                                         $hash[12] = '?'; // was overwritten by version
  479.  
  480.                                         $var16a = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b0000));
  481.                                         $var16b = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b0100));
  482.                                         $var16c = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b1000));
  483.                                         $var16d = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b1100));
  484.                                         $hash[16] = '?'; // was partially overwritten by variant
  485.  
  486.                                         $p = 16;
  487.                                         $hash = substr($hash,0,$p)."<abbr title=\"$var16a, $var16b, $var16c, or $var16d\">".substr($hash,$p,1).'</abbr>'.substr($hash,$p+1);
  488.                                         echo sprintf("%-32s %s\n", "MD5(Namespace+Subject):", "[0x$hash]");
  489.  
  490.                                         break;
  491.                                 case 4:
  492.                                         /*
  493.                                         Variant 1, Version 4 UUID
  494.                                         - 48 bit Random High
  495.                                         -  4 bit Version (fix 0x4)
  496.                                         - 12 bit Random Mid
  497.                                         -  2 bit Variant (fix 0b10)
  498.                                         - 62 bit Random Low
  499.                                         */
  500.  
  501.                                         echo sprintf("%-32s %s\n", "Version:", "[0x4] Random");
  502.  
  503.                                         $rand_line1 = '';
  504.                                         $rand_line2 = '';
  505.                                         for ($i=0; $i<16; $i++) {
  506.                                                 $bin = base_convert(substr($uuid, $i*2, 2), 16, 2);
  507.                                                 $bin = str_pad($bin, 8, "0", STR_PAD_LEFT);
  508.  
  509.                                                 if ($i == 6) {
  510.                                                         // was overwritten by version
  511.                                                         $bin[0] = '?';
  512.                                                         $bin[1] = '?';
  513.                                                         $bin[2] = '?';
  514.                                                         $bin[3] = '?';
  515.                                                 } else if ($i == 8) {
  516.                                                         // was partially overwritten by variant
  517.                                                         $bin[0] = '?';
  518.                                                         $bin[1] = '?';
  519.                                                 }
  520.  
  521.                                                 if ($i<8) $rand_line1 .= "$bin ";
  522.                                                 if ($i>=8) $rand_line2 .= "$bin ";
  523.                                         }
  524.                                         echo sprintf("%-32s %s\n", "Random bits:", trim($rand_line1));
  525.                                         echo sprintf("%-32s %s\n", "",             trim($rand_line2));
  526.  
  527.                                         $rand_bytes = str_replace('-', '', strtolower($uuid));
  528.                                         $rand_bytes[12] = '?'; // was overwritten by version
  529.                                         $var16a = strtoupper(dechex(hexdec($rand_bytes[16]) & 0b0011 | 0b0000));
  530.                                         $var16b = strtoupper(dechex(hexdec($rand_bytes[16]) & 0b0011 | 0b0100));
  531.                                         $var16c = strtoupper(dechex(hexdec($rand_bytes[16]) & 0b0011 | 0b1000));
  532.                                         $var16d = strtoupper(dechex(hexdec($rand_bytes[16]) & 0b0011 | 0b1100));
  533.                                         $rand_bytes[16] = '?'; // was partially overwritten by variant
  534.  
  535.                                         $p = 16;
  536.                                         $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);
  537.                                         echo sprintf("%-32s %s\n", "Random bytes:", "[0x$rand_bytes]");
  538.  
  539.                                         break;
  540.                                 case 5:
  541.                                         /*
  542.                                         Variant 1, Version 5 UUID
  543.                                         - 48 bit Hash High
  544.                                         -  4 bit Version (fix 0x5)
  545.                                         - 12 bit Hash Mid
  546.                                         -  2 bit Variant (fix 0b10)
  547.                                         - 62 bit Hash Low
  548.                                         */
  549.  
  550.                                         echo sprintf("%-32s %s\n", "Version:", "[0x5] Name-based (SHA-1 hash)");
  551.  
  552.                                         $hash = str_replace('-', '', strtolower($uuid));
  553.  
  554.                                         $hash[12] = '?'; // was overwritten by version
  555.  
  556.                                         $var16a = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b0000));
  557.                                         $var16b = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b0100));
  558.                                         $var16c = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b1000));
  559.                                         $var16d = strtoupper(dechex(hexdec($hash[16]) & 0b0011 | 0b1100));
  560.                                         $hash[16] = '?'; // was partially overwritten by variant
  561.  
  562.                                         $hash .= '????????'; // was cut off
  563.  
  564.                                         $p = 16;
  565.                                         $hash = substr($hash,0,$p)."<abbr title=\"$var16a, $var16b, $var16c, or $var16d\">".substr($hash,$p,1).'</abbr>'.substr($hash,$p+1);
  566.                                         echo sprintf("%-32s %s\n", "SHA1(Namespace+Subject):", "[0x$hash]");
  567.  
  568.                                         break;
  569.                                 case 7:
  570.                                         /*
  571.                                         Variant 1, Version 7 UUID
  572.                                         - 48 bit Unix Time in milliseconds
  573.                                         -  4 bit Version (fix 0x7)
  574.                                         - 12 bit Random
  575.                                         -  2 bit Variant (fix 0b10)
  576.                                         - 62 bit Random
  577.                                         */
  578.  
  579.                                         echo sprintf("%-32s %s\n", "Version:", "[0x7] Unix Epoch Time");
  580.  
  581.                                         $timestamp = substr($uuid, 0, 12);
  582.  
  583.                                         // Timestamp: Split into seconds and milliseconds
  584.                                         $ts = gmp_init($timestamp, 16);
  585.                                         $ms = gmp_mod($ts, gmp_init("1000", 10));
  586.                                         $ts = gmp_div($ts, gmp_init("1000", 10));
  587.                                         $ts = gmp_strval($ts, 10);
  588.                                         $ms = gmp_strval($ms, 10);
  589.                                         $ts = gmdate('Y-m-d H:i:s', intval($ts))."'".str_pad($ms, 3/*ms*/, '0', STR_PAD_LEFT).' GMT';
  590.                                         echo sprintf("%-32s %s\n", "Timestamp:", "[0x$timestamp] $ts");
  591.  
  592.                                         $rand = '';
  593.                                         for ($i=6; $i<16; $i++) {
  594.                                                 $bin = base_convert(substr($uuid, $i*2, 2), 16, 2);
  595.                                                 $bin = str_pad($bin, 8, "0", STR_PAD_LEFT);
  596.  
  597.                                                 if ($i == 6) {
  598.                                                         // was overwritten by version
  599.                                                         $bin[0] = '?';
  600.                                                         $bin[1] = '?';
  601.                                                         $bin[2] = '?';
  602.                                                         $bin[3] = '?';
  603.                                                 } else if ($i == 8) {
  604.                                                         // was partially overwritten by variant
  605.                                                         $bin[0] = '?';
  606.                                                         $bin[1] = '?';
  607.                                                 }
  608.  
  609.                                                 $rand .= "$bin ";
  610.                                         }
  611.                                         echo sprintf("%-32s %s\n", "Random bits:", trim($rand));
  612.  
  613.                                         $rand_bytes = substr(str_replace('-', '', strtolower($uuid)),13);
  614.                                         $var16a = strtoupper(dechex(hexdec($rand_bytes[3]) & 0b0011 | 0b0000));
  615.                                         $var16b = strtoupper(dechex(hexdec($rand_bytes[3]) & 0b0011 | 0b0100));
  616.                                         $var16c = strtoupper(dechex(hexdec($rand_bytes[3]) & 0b0011 | 0b1000));
  617.                                         $var16d = strtoupper(dechex(hexdec($rand_bytes[3]) & 0b0011 | 0b1100));
  618.                                         $rand_bytes[3] = '?'; // was partially overwritten by variant
  619.  
  620.                                         $p = 3;
  621.                                         $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);
  622.                                         echo sprintf("%-32s %s\n", "Random bytes:", "[0x$rand_bytes]");
  623.  
  624.                                         // TODO: convert to and from Base32 CROCKFORD ULID (make 2 methods in uuid_utils.inc.php)
  625.                                         // e.g. ULID: 01GCZ05N3JFRKBRWKNGCQZGP44
  626.                                         // "Be aware that all version 7 UUIDs may be converted to ULIDs but not all ULIDs may be converted to UUIDs."
  627.  
  628.                                         break;
  629.                                 case 8:
  630.                                         /*
  631.                                         Variant 1, Version 8 UUID
  632.                                         - 48 bit Custom data
  633.                                         -  4 bit Version (fix 0x8)
  634.                                         - 12 bit Custom data
  635.                                         -  2 bit Variant (fix 0b10)
  636.                                         - 62 bit Custom data
  637.                                         */
  638.  
  639.                                         echo sprintf("%-32s %s\n", "Version:", "[0x8] Custom implementation");
  640.  
  641.                                         $custom_data = substr($uuid,0,12).substr($uuid,13); // exclude version nibble
  642.                                         $custom_data[15] = dechex(hexdec($custom_data[15]) & 0b0011); // nibble was partially overwritten by variant
  643.                                         $custom_data = strtolower($custom_data);
  644.  
  645.                                         $custom_block1 = substr($uuid,  0, 8);
  646.                                         $custom_block2 = substr($uuid,  8, 4);
  647.                                         $custom_block3 = substr($uuid, 12, 4);
  648.                                         $custom_block4 = substr($uuid, 16, 4);
  649.                                         $custom_block5 = substr($uuid, 20);
  650.  
  651.                                         $custom_block3 = substr($custom_block3, 1); // remove version
  652.                                         $custom_block4[0] = dechex(hexdec($custom_block4[0]) & 0b0011); // remove variant
  653.  
  654.                                         echo sprintf("%-32s %s\n", "Custom data:", "[0x$custom_data]");
  655.                                         echo sprintf("%-32s %s\n", "Custom data block1 (32 bit):", "[0x$custom_block1]");
  656.                                         echo sprintf("%-32s %s\n", "Custom data block2 (16 bit):", "[0x$custom_block2]");
  657.                                         echo sprintf("%-32s %s\n", "Custom data block3 (12 bit):", "[0x$custom_block3]");
  658.                                         echo sprintf("%-32s %s\n", "Custom data block4 (14 bit):", "[0x$custom_block4]");
  659.                                         echo sprintf("%-32s %s\n", "Custom data block5 (48 bit):", "[0x$custom_block5]");
  660.  
  661.                                         // Check if Custom UUIDv8 is likely an OIDplus 2.0 Information Object UUID
  662.                                         // Details here: https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md
  663.                                         $min_day = 14610; // 1 Jan 2010
  664.                                         $max_day = floor(time()/24/60/60); // Today
  665.                                         if (($custom_block3 == '000') && (hexdec($custom_block2) >= $min_day) && (hexdec($custom_block2) <= $max_day)) {
  666.                                                 echo "\n<u>Interpretation of <a href=\"https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md\">OIDplus 2.0 Information Object UUID</a></u>\n\n";
  667.  
  668.                                                 $known_objecttype_plugins = array(
  669.                                                         // Latest list here: https://github.com/danielmarschall/oidplus/blob/master/doc/oidplus_custom_guid.md
  670.                                                         '1.3.6.1.4.1.37476.2.5.2.4.8.1' => 'doi (ViaThinkSoft plugin)',
  671.                                                         '1.3.6.1.4.1.37476.2.5.2.4.8.2' => 'gs1 (ViaThinkSoft plugin)',
  672.                                                         '1.3.6.1.4.1.37476.2.5.2.4.8.3' => 'guid (ViaThinkSoft plugin)',
  673.                                                         '1.3.6.1.4.1.37476.2.5.2.4.8.4' => 'ipv4 (ViaThinkSoft plugin)',
  674.                                                         '1.3.6.1.4.1.37476.2.5.2.4.8.5' => 'ipv6 (ViaThinkSoft plugin)',
  675.                                                         '1.3.6.1.4.1.37476.2.5.2.4.8.6' => 'java (ViaThinkSoft plugin)',
  676.                                                         '1.3.6.1.4.1.37476.2.5.2.4.8.7' => 'oid (ViaThinkSoft plugin)',
  677.                                                         '1.3.6.1.4.1.37476.2.5.2.4.8.8' => 'other (ViaThinkSoft plugin)',
  678.                                                         '1.3.6.1.4.1.37476.2.5.2.4.8.9' => 'domain (ViaThinkSoft plugin)',
  679.                                                         '1.3.6.1.4.1.37476.2.5.2.4.8.10' => 'fourcc (ViaThinkSoft plugin)',
  680.                                                         '1.3.6.1.4.1.37476.2.5.2.4.8.11' => 'aid (ViaThinkSoft plugin)',
  681.                                                         '1.3.6.1.4.1.37476.2.5.2.4.8.12' => 'php (ViaThinkSoft plugin)',
  682.                                                         '1.3.6.1.4.1.37476.2.5.2.4.8.13' => 'mac (ViaThinkSoft plugin)',
  683.                                                         '1.3.6.1.4.1.37553.8.1.8.8.53354196964.27255728261' => 'circuit (Frdlweb plugin)',
  684.                                                         '1.3.6.1.4.1.37476.9000.108.19361.856' => 'ns (Frdlweb plugin)',
  685.                                                         '1.3.6.1.4.1.37553.8.1.8.8.53354196964.32927' => 'pen (Frdlweb plugin)',
  686.                                                         '1.3.6.1.4.1.37553.8.1.8.8.53354196964.39870' => 'uri (Frdlweb plugin)',
  687.                                                         '1.3.6.1.4.1.37553.8.1.8.8.53354196964.1958965295' => 'web+fan (Frdlweb plugin)'
  688.                                                 );
  689.                                                 $namespace_desc = 'Unknown object type';
  690.                                                 foreach ($known_objecttype_plugins as $oid => $name) {
  691.                                                         if ((hexdec(substr(sha1($oid),-4)) & 0x3fff) == hexdec($custom_block4)) $namespace_desc = $name;
  692.                                                 }
  693.  
  694.                                                 echo sprintf("%-32s %s\n", "System ID:", "[0x$custom_block1] ".hexdec($custom_block1));
  695.                                                 echo sprintf("%-32s %s\n", "Creation time:", "[0x$custom_block2] ".date('Y-m-d', hexdec($custom_block2)*24*60*60));
  696.                                                 echo sprintf("%-32s %s\n", "Reserved:", "[0x$custom_block3]");
  697.                                                 echo sprintf("%-32s %s\n", "Namespace hash:", "[0x$custom_block4] $namespace_desc");
  698.                                                 echo sprintf("%-32s %s\n", "Object ID hash:", "[0x$custom_block5] SHA1 = ????????????????????????????$custom_block5");
  699.  
  700.                                         }
  701.  
  702.                                         break;
  703.                                 default:
  704.                                         echo sprintf("%-32s %s\n", "Version:", "[0x".dechex($version)."] Unknown");
  705.                                         break;
  706.                         }
  707.  
  708.                         break;
  709.                 case 2:
  710.                         // 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
  711.  
  712.                         // TODO: Is there any scheme in that legacy Microsoft GUIDs?
  713.                         echo sprintf("%-32s %s\n", "Variant:", "[0b110] Reserved for Microsoft Corporation");
  714.                         break;
  715.                 case 3:
  716.                         echo sprintf("%-32s %s\n", "Variant:", "[0b111] Reserved for future use");
  717.                         break;
  718.         }
  719.  
  720.         if (!$echo) {
  721.                 $out = ob_get_contents();
  722.                 ob_end_clean();
  723.                 return $out;
  724.         } else {
  725.                 return true;
  726.         }
  727. }
  728.  
  729. function uuid_canonize($uuid) {
  730.         if (!uuid_valid($uuid)) return false;
  731.         return oid_to_uuid(uuid_to_oid($uuid));
  732. }
  733.  
  734. function oid_to_uuid($oid) {
  735.         if (!is_uuid_oid($oid,true)) return false;
  736.  
  737.         if (substr($oid,0,1) == '.') {
  738.                 $oid = substr($oid, 1);
  739.         }
  740.         $ary = explode('.', $oid);
  741.  
  742.         if (!isset($ary[2])) return false;
  743.  
  744.         $val = $ary[2];
  745.  
  746.         $x = gmp_init($val, 10);
  747.         $y = gmp_strval($x, 16);
  748.         $y = str_pad($y, 32, "0", STR_PAD_LEFT);
  749.         return substr($y,  0, 8).'-'.
  750.                substr($y,  8, 4).'-'.
  751.                substr($y, 12, 4).'-'.
  752.                substr($y, 16, 4).'-'.
  753.                substr($y, 20, 12);
  754. }
  755.  
  756. function is_uuid_oid($oid, $only_allow_root=false) {
  757.         if (substr($oid,0,1) == '.') $oid = substr($oid, 1); // remove leading dot
  758.  
  759.         $ary = explode('.', $oid);
  760.  
  761.         if ($only_allow_root) {
  762.                 if (count($ary) != 3) return false;
  763.         } else {
  764.                 if (count($ary) < 3) return false;
  765.         }
  766.  
  767.         if ($ary[0] != '2') return false;
  768.         if ($ary[1] != '25') return false;
  769.         for ($i=2; $i<count($ary); $i++) {
  770.                 $v = $ary[$i];
  771.                 if (!is_numeric($v)) return false;
  772.                 if ($i == 2) {
  773.                         // Must be in the range of 128 bit UUID
  774.                         $test = gmp_init($v, 10);
  775.                         if (strlen(gmp_strval($test, 16)) > 32) return false;
  776.                 }
  777.                 if ($v < 0) return false;
  778.         }
  779.  
  780.         return true;
  781. }
  782.  
  783. function uuid_to_oid($uuid) {
  784.         if (!uuid_valid($uuid)) return false;
  785.  
  786.         $uuid = str_replace(array('-', '{', '}'), '', $uuid);
  787.         $x = gmp_init($uuid, 16);
  788.         return '2.25.'.gmp_strval($x, 10);
  789. }
  790.  
  791. function uuid_numeric_value($uuid) {
  792.         $oid = uuid_to_oid($uuid);
  793.         if (!$oid) return false;
  794.         return substr($oid, strlen('2.25.'));
  795. }
  796.  
  797. function uuid_c_syntax($uuid) {
  798.         $uuid = str_replace('{', '', $uuid);
  799.         return '{ 0x' . substr($uuid, 0, 8) .
  800.                 ', 0x' . substr($uuid, 9, 4) .
  801.                 ', 0x' . substr($uuid, 14, 4) .
  802.                 ', { 0x' . substr($uuid, 19, 2).
  803.                 ', 0x' . substr($uuid, 21, 2) .
  804.                 ', 0x' . substr($uuid, 24, 2) .
  805.                 ', 0x' . substr($uuid, 26, 2) .
  806.                 ', 0x' . substr($uuid, 28, 2) .
  807.                 ', 0x' . substr($uuid, 30, 2) .
  808.                 ', 0x' . substr($uuid, 32, 2) .
  809.                 ', 0x' . substr($uuid, 34, 2) . ' } }';
  810. }
  811.  
  812. function gen_uuid($prefer_mac_address_based = true) {
  813.         $uuid = $prefer_mac_address_based ? gen_uuid_reordered()/*UUIDv6*/ : false;
  814.         if ($uuid === false) $uuid = gen_uuid_unix_epoch()/*UUIDv7*/;
  815.         return $uuid;
  816. }
  817.  
  818. # --------------------------------------
  819. // Variant 1, Version 1 (Time based) UUID
  820. # --------------------------------------
  821.  
  822. function gen_uuid_v1() {
  823.         return gen_uuid_timebased();
  824. }
  825. function gen_uuid_timebased($force_php_implementation=false) {
  826.         # On Debian: apt-get install php-uuid
  827.         # extension_loaded('uuid')
  828.         if (!$force_php_implementation && function_exists('uuid_create')) {
  829.                 # OSSP uuid extension like seen in php5-uuid at Debian 8
  830.                 /*
  831.                 $x = uuid_create($context);
  832.                 uuid_make($context, UUID_MAKE_V1);
  833.                 uuid_export($context, UUID_FMT_STR, $uuid);
  834.                 return trim($uuid);
  835.                 */
  836.  
  837.                 # PECL uuid extension like seen in php-uuid at Debian 9
  838.                 return trim(uuid_create(UUID_TYPE_TIME));
  839.         }
  840.  
  841.         # On Debian: apt-get install uuid-runtime
  842.         if (!$force_php_implementation && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
  843.                 $out = array();
  844.                 $ec = -1;
  845.                 exec('uuidgen -t 2>/dev/null', $out, $ec);
  846.                 if ($ec == 0) return trim($out[0]);
  847.         }
  848.  
  849.         # If we hadn't any success yet, then implement the time based generation routine ourselves!
  850.         # Based on https://github.com/fredriklindberg/class.uuid.php/blob/master/class.uuid.php
  851.  
  852.         $uuid = array(
  853.                 'time_low' => 0,                /* 32-bit */
  854.                 'time_mid' => 0,                /* 16-bit */
  855.                 'time_hi' => 0,                 /* 16-bit */
  856.                 'clock_seq_hi' => 0,            /*  8-bit */
  857.                 'clock_seq_low' => 0,           /*  8-bit */
  858.                 'node' => array()               /* 48-bit */
  859.         );
  860.  
  861.         /*
  862.          * Get current time in 100 ns intervals. The magic value
  863.          * is the offset between UNIX epoch and the UUID UTC
  864.          * time base October 15, 1582.
  865.          */
  866.         if (time_nanosleep(0,100) !== true) usleep(1); // Wait 100ns, to make sure that the time part changes if multiple UUIDs are generated
  867.         $tp = gettimeofday();
  868.         if (PHP_INT_SIZE == 4) {
  869.                 $tp['sec'] = gmp_init($tp['sec'],10);
  870.                 $tp['usec'] = gmp_init($tp['usec'],10);
  871.                 $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));
  872.                 $uuid['time_low'] = gmp_and($time, gmp_init('ffffffff',16));
  873.                 $high = gmp_shiftr($time,32);
  874.                 $uuid['time_mid'] = gmp_and($high, gmp_init('ffff',16));
  875.                 $uuid['time_hi'] = intval(gmp_and(gmp_shiftr($high,16),gmp_init('fff',16)),10) | (1/*TimeBased*/ << 12);
  876.         } else {
  877.                 $time = ($tp['sec'] * 10000000) + ($tp['usec'] * 10) + 0x01B21DD213814000;
  878.                 $uuid['time_low'] = $time & 0xffffffff;
  879.                 /* Work around PHP 32-bit bit-operation limits */
  880.                 $high = intval($time / 0xffffffff);
  881.                 $uuid['time_mid'] = $high & 0xffff;
  882.                 $uuid['time_hi'] = (($high >> 16) & 0xfff) | (1/*TimeBased*/ << 12);
  883.         }
  884.  
  885.         /*
  886.          * We don't support saved state information and generate
  887.          * a random clock sequence each time.
  888.          */
  889.         $uuid['clock_seq_hi'] = _random_int(0, 255) & 0b00111111 | 0b10000000; // set variant to 0b10__ (RFC 4122)
  890.         $uuid['clock_seq_low'] = _random_int(0, 255);
  891.  
  892.         /*
  893.          * Node should be set to the 48-bit IEEE node identifier
  894.          */
  895.         $mac = get_mac_address();
  896.         if ($mac) {
  897.                 $node = str_replace('-','',str_replace(':','',$mac));
  898.                 for ($i = 0; $i < 6; $i++) {
  899.                         $uuid['node'][$i] = hexdec(substr($node, $i*2, 2));
  900.                 }
  901.         } else {
  902.                 // If we cannot get a MAC address, then generate a random AAI
  903.                 $uuid['node'] = explode('-', gen_aai(48, false));
  904.                 $uuid['node'] = array_map('hexdec', $uuid['node']);
  905.         }
  906.  
  907.         /*
  908.          * Now output the UUID
  909.          */
  910.         return sprintf(
  911.                 '%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x',
  912.                 ($uuid['time_low']), ($uuid['time_mid']), ($uuid['time_hi']),
  913.                 $uuid['clock_seq_hi'], $uuid['clock_seq_low'],
  914.                 $uuid['node'][0], $uuid['node'][1], $uuid['node'][2],
  915.                 $uuid['node'][3], $uuid['node'][4], $uuid['node'][5]);
  916. }
  917.  
  918. # --------------------------------------
  919. // Variant 1, Version 2 (DCE Security) UUID
  920. # --------------------------------------
  921.  
  922. define('DCE_DOMAIN_PERSON', 0);
  923. define('DCE_DOMAIN_GROUP', 1);
  924. define('DCE_DOMAIN_ORG', 2);
  925. function gen_uuid_v2($domain, $id) {
  926.         return gen_uuid_dce($domain, $id);
  927. }
  928. function gen_uuid_dce($domain, $id) {
  929.         if (($domain ?? '') === '') throw new Exception("Domain ID missing");
  930.         if (!is_numeric($domain)) throw new Exception("Invalid Domain ID");
  931.         if (($domain < 0) || ($domain > 255)) throw new Exception("Domain ID must be in range 0..255");
  932.  
  933.         if (($id ?? '') === '') throw new Exception("ID value missing");
  934.         if (!is_numeric($id)) throw new Exception("Invalid ID value");
  935.         if (($id < 0) || ($id > 4294967295)) throw new Exception("ID value must be in range 0..4294967295");
  936.  
  937.         # Start with a version 1 UUID
  938.         $uuid = gen_uuid_timebased();
  939.  
  940.         # Add Domain Number
  941.         $uuid = str_pad(dechex($id), 8, '0', STR_PAD_LEFT) . substr($uuid, 8);
  942.  
  943.         # Add Domain (this overwrites part of the clock sequence)
  944.         $uuid = substr($uuid,0,21) . str_pad(dechex($domain), 2, '0', STR_PAD_LEFT) . substr($uuid, 23);
  945.  
  946.         # Change version to 2
  947.         $uuid[14] = '2';
  948.  
  949.         return $uuid;
  950. }
  951.  
  952. # --------------------------------------
  953. // Variant 1, Version 3 (MD5 name based) UUID
  954. # --------------------------------------
  955.  
  956. function gen_uuid_v3($namespace_uuid, $name) {
  957.         return gen_uuid_md5_namebased($namespace_uuid, $name);
  958. }
  959. function gen_uuid_md5_namebased($namespace_uuid, $name) {
  960.         if (($namespace_uuid ?? '') === '') throw new Exception("Namespace UUID missing");
  961.         if (!uuid_valid($namespace_uuid)) throw new Exception("Invalid namespace UUID '$namespace_uuid'");
  962.  
  963.         $namespace_uuid = uuid_canonize($namespace_uuid);
  964.         $namespace_uuid = str_replace('-', '', $namespace_uuid);
  965.         $namespace_uuid = hex2bin($namespace_uuid);
  966.  
  967.         $hash = md5($namespace_uuid.$name);
  968.         $hash[12] = '3'; // Set version: 3 = MD5
  969.         $hash[16] = dechex(hexdec($hash[16]) & 0b0011 | 0b1000); // Set variant to "10xx" (RFC4122)
  970.  
  971.         return substr($hash,  0, 8).'-'.
  972.                substr($hash,  8, 4).'-'.
  973.                substr($hash, 12, 4).'-'.
  974.                substr($hash, 16, 4).'-'.
  975.                substr($hash, 20, 12);
  976. }
  977.  
  978. # --------------------------------------
  979. // Variant 1, Version 4 (Random) UUID
  980. # --------------------------------------
  981.  
  982. function gen_uuid_v4() {
  983.         return gen_uuid_random();
  984. }
  985. function gen_uuid_random() {
  986.         # On Windows: Requires
  987.         #    extension_dir = "C:\php-8.0.3-nts-Win32-vs16-x64\ext"
  988.         #    extension=com_dotnet
  989.         if (function_exists('com_create_guid')) {
  990.                 $uuid = trim(com_create_guid(), '{}');
  991.                 if (uuid_version($uuid) === '4') { // <-- just to make 100% sure that Windows's CoCreateGuid() did output UUIDv4
  992.                         return strtolower($uuid);
  993.                 }
  994.         }
  995.  
  996.         # On Debian: apt-get install php-uuid
  997.         # extension_loaded('uuid')
  998.         if (function_exists('uuid_create')) {
  999.                 # OSSP uuid extension like seen in php5-uuid at Debian 8
  1000.                 /*
  1001.                 $x = uuid_create($context);
  1002.                 uuid_make($context, UUID_MAKE_V4);
  1003.                 uuid_export($context, UUID_FMT_STR, $uuid);
  1004.                 return trim($uuid);
  1005.                 */
  1006.  
  1007.                 # PECL uuid extension like seen in php-uuid at Debian 9
  1008.                 return trim(uuid_create(UUID_TYPE_RANDOM));
  1009.         }
  1010.  
  1011.         if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
  1012.                 # On Debian: apt-get install uuid-runtime
  1013.                 $out = array();
  1014.                 $ec = -1;
  1015.                 exec('uuidgen -r 2>/dev/null', $out, $ec);
  1016.                 if ($ec == 0) return trim($out[0]);
  1017.  
  1018.                 # On Debian Jessie: UUID V4 (Random)
  1019.                 if (file_exists('/proc/sys/kernel/random/uuid')) {
  1020.                         return trim(file_get_contents('/proc/sys/kernel/random/uuid'));
  1021.                 }
  1022.         }
  1023.  
  1024.         # Make the UUID by ourselves
  1025.  
  1026.         if (function_exists('openssl_random_pseudo_bytes')) {
  1027.                 // Source: https://www.php.net/manual/en/function.com-create-guid.php#119168
  1028.                 $data = openssl_random_pseudo_bytes(16);
  1029.                 $data[6] = chr(ord($data[6]) & 0x0f | 0x40);    // set version to 0100
  1030.                 $data[8] = chr(ord($data[8]) & 0x3f | 0x80);    // set bits 6-7 to 10
  1031.                 return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
  1032.         } else {
  1033.                 // Source: http://rogerstringer.com/2013/11/15/generate-uuids-php
  1034.                 return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
  1035.                         _random_int( 0, 0xffff ), _random_int( 0, 0xffff ),
  1036.                         _random_int( 0, 0xffff ),
  1037.                         _random_int( 0, 0x0fff ) | 0x4000,
  1038.                         _random_int( 0, 0x3fff ) | 0x8000,
  1039.                         _random_int( 0, 0xffff ), _random_int( 0, 0xffff ), _random_int( 0, 0xffff )
  1040.                 );
  1041.         }
  1042. }
  1043.  
  1044. # --------------------------------------
  1045. // Variant 1, Version 5 (SHA1 name based) UUID
  1046. # --------------------------------------
  1047.  
  1048. function gen_uuid_v5($namespace_uuid, $name) {
  1049.         return gen_uuid_sha1_namebased($namespace_uuid, $name);
  1050. }
  1051. function gen_uuid_sha1_namebased($namespace_uuid, $name) {
  1052.         if (($namespace_uuid ?? '') === '') throw new Exception("Namespace UUID missing");
  1053.         if (!uuid_valid($namespace_uuid)) throw new Exception("Invalid namespace UUID '$namespace_uuid'");
  1054.  
  1055.         $namespace_uuid = str_replace('-', '', $namespace_uuid);
  1056.         $namespace_uuid = hex2bin($namespace_uuid);
  1057.  
  1058.         $hash = sha1($namespace_uuid.$name);
  1059.         $hash[12] = '5'; // Set version: 5 = SHA1
  1060.         $hash[16] = dechex(hexdec($hash[16]) & 0b0011 | 0b1000); // Set variant to "0b10__" (RFC4122/DCE1.1)
  1061.  
  1062.         return substr($hash,  0, 8).'-'.
  1063.                substr($hash,  8, 4).'-'.
  1064.                substr($hash, 12, 4).'-'.
  1065.                substr($hash, 16, 4).'-'.
  1066.                substr($hash, 20, 12);
  1067. }
  1068.  
  1069. # --------------------------------------
  1070. // Variant 1, Version 6 (Reordered) UUID
  1071. # --------------------------------------
  1072.  
  1073. function gen_uuid_v6() {
  1074.         return gen_uuid_reordered();
  1075. }
  1076. function gen_uuid_reordered() {
  1077.         // Start with a UUIDv1
  1078.         $uuid = gen_uuid_timebased();
  1079.  
  1080.         // Convert to UUIDv6
  1081.         return uuid1_to_uuid6($uuid);
  1082. }
  1083. function uuid6_to_uuid1($hex) {
  1084.         $hex = uuid_canonize($hex);
  1085.         if ($hex === false) return false;
  1086.         $hex = preg_replace('@[^0-9A-F]@i', '', $hex);
  1087.         $hex = substr($hex, 7, 5).
  1088.                substr($hex, 13, 3).
  1089.                substr($hex, 3, 4).
  1090.                '1' . substr($hex, 0, 3).
  1091.                substr($hex, 16);
  1092.         return substr($hex,  0, 8).'-'.
  1093.                substr($hex,  8, 4).'-'.
  1094.                substr($hex, 12, 4).'-'.
  1095.                substr($hex, 16, 4).'-'.
  1096.                substr($hex, 20, 12);
  1097. }
  1098. function uuid1_to_uuid6($hex) {
  1099.         $hex = uuid_canonize($hex);
  1100.         if ($hex === false) return false;
  1101.         $hex = preg_replace('@[^0-9A-F]@i', '', $hex);
  1102.         $hex = substr($hex, 13, 3).
  1103.                substr($hex, 8, 4).
  1104.                substr($hex, 0, 5).
  1105.                '6' . substr($hex, 5, 3).
  1106.                substr($hex, 16);
  1107.         return substr($hex,  0, 8).'-'.
  1108.                substr($hex,  8, 4).'-'.
  1109.                substr($hex, 12, 4).'-'.
  1110.                substr($hex, 16, 4).'-'.
  1111.                substr($hex, 20, 12);
  1112. }
  1113.  
  1114. # --------------------------------------
  1115. // Variant 1, Version 7 (Unix Epoch) UUID
  1116. # --------------------------------------
  1117.  
  1118. function gen_uuid_v7() {
  1119.         return gen_uuid_unix_epoch();
  1120. }
  1121. function gen_uuid_unix_epoch() {
  1122.         // Start with an UUIDv4
  1123.         $uuid = gen_uuid_random();
  1124.  
  1125.         // Add the timestamp
  1126.         usleep(1000); // Wait 1ms, to make sure that the time part changes if multiple UUIDs are generated
  1127.         if (function_exists('gmp_init')) {
  1128.                 list($ms,$sec) = explode(' ', microtime(false));
  1129.                 $sec = gmp_init($sec, 10);
  1130.                 $ms = gmp_init(substr($ms,2,3), 10);
  1131.                 $unix_ts = gmp_strval(gmp_add(gmp_mul($sec, '1000'), $ms),16);
  1132.         } else {
  1133.                 $unix_ts = dechex((int)round(microtime(true)*1000));
  1134.         }
  1135.         $unix_ts = str_pad($unix_ts, 12, '0', STR_PAD_LEFT);
  1136.         for ($i=0;$i<8;$i++) $uuid[$i] = substr($unix_ts, $i, 1);
  1137.         for ($i=0;$i<4;$i++) $uuid[9+$i] = substr($unix_ts, 8+$i, 1);
  1138.  
  1139.         // set version
  1140.         $uuid[14] = '7';
  1141.  
  1142.         return $uuid;
  1143. }
  1144.  
  1145. # --------------------------------------
  1146. // Variant 1, Version 8 (Custom) UUID
  1147. # --------------------------------------
  1148.  
  1149. function gen_uuid_v8($block1_32bit, $block2_16bit, $block3_12bit, $block4_14bit, $block5_48bit) {
  1150.         return gen_uuid_custom($block1_32bit, $block2_16bit, $block3_12bit, $block4_14bit, $block5_48bit);
  1151. }
  1152. function gen_uuid_custom($block1_32bit, $block2_16bit, $block3_12bit, $block4_14bit, $block5_48bit) {
  1153.         if (preg_replace('@[0-9A-F]@i', '', $block1_32bit) != '') throw new Exception("Invalid data for block 1. Must be hex input");
  1154.         if (preg_replace('@[0-9A-F]@i', '', $block2_16bit) != '') throw new Exception("Invalid data for block 2. Must be hex input");
  1155.         if (preg_replace('@[0-9A-F]@i', '', $block3_12bit) != '') throw new Exception("Invalid data for block 3. Must be hex input");
  1156.         if (preg_replace('@[0-9A-F]@i', '', $block4_14bit) != '') throw new Exception("Invalid data for block 4. Must be hex input");
  1157.         if (preg_replace('@[0-9A-F]@i', '', $block5_48bit) != '') throw new Exception("Invalid data for block 5. Must be hex input");
  1158.  
  1159.         $block1 = str_pad(substr($block1_32bit, -8),  8, '0', STR_PAD_LEFT);
  1160.         $block2 = str_pad(substr($block2_16bit, -4),  4, '0', STR_PAD_LEFT);
  1161.         $block3 = str_pad(substr($block3_12bit, -4),  4, '0', STR_PAD_LEFT);
  1162.         $block4 = str_pad(substr($block4_14bit, -4),  4, '0', STR_PAD_LEFT);
  1163.         $block5 = str_pad(substr($block5_48bit,-12), 12, '0', STR_PAD_LEFT);
  1164.  
  1165.         $block3[0] = '8'; // Version 8 = Custom
  1166.         $block4[0] = dechex(hexdec($block4[0]) & 0b0011 | 0b1000); // Variant 0b10__ = RFC4122
  1167.  
  1168.         return strtolower($block1.'-'.$block2.'-'.$block3.'-'.$block4.'-'.$block5);
  1169. }
  1170.  
  1171. # --------------------------------------
  1172.  
  1173. // http://php.net/manual/de/function.hex2bin.php#113057
  1174. if (!function_exists('hex2bin')) {
  1175.     function hex2bin($str) {
  1176.         $sbin = "";
  1177.         $len = strlen($str);
  1178.         for ( $i = 0; $i < $len; $i += 2 ) {
  1179.             $sbin .= pack("H*", substr($str, $i, 2));
  1180.         }
  1181.         return $sbin;
  1182.     }
  1183. }
  1184.  
  1185. // https://stackoverflow.com/questions/72127764/shift-right-left-bitwise-operators-in-php7-gmp-extension
  1186. if (!function_exists('gmp_shiftl')) {
  1187.     function gmp_shiftl($x,$n) { // shift left
  1188.         return(gmp_mul($x,gmp_pow(2,$n)));
  1189.     }
  1190. }
  1191.  
  1192. if (!function_exists('gmp_shiftr')) {
  1193.     function gmp_shiftr($x,$n) { // shift right
  1194.         return(gmp_div_q($x,gmp_pow(2,$n)));
  1195.     }
  1196. }
  1197.