Subversion Repositories oidinfo_api

Rev

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