Subversion Repositories oidplus

Rev

Rev 1438 | Rev 1470 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 1438 Rev 1469
Line 1... Line 1...
1
<?php
1
<?php
2
 
2
 
3
/*
3
/*
4
 * UUID utils for PHP
4
 * UUID utils for PHP
5
 * Copyright 2011 - 2023 Daniel Marschall, ViaThinkSoft
5
 * Copyright 2011 - 2024 Daniel Marschall, ViaThinkSoft
6
 * Version 2023-11-18
6
 * Version 2024-03-09
7
 *
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with 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
10
 * You may obtain a copy of the License at
11
 *
11
 *
Line 831... Line 831...
831
                case 3:
831
                case 3:
832
                        echo sprintf("%-32s %s\n", "Variant:", "[0b111] Reserved for future use");
832
                        echo sprintf("%-32s %s\n", "Variant:", "[0b111] Reserved for future use");
833
                        break;
833
                        break;
834
        }
834
        }
835
 
835
 
-
 
836
        // START: HickelSOFT UUID
-
 
837
 
-
 
838
        // Block 5
-
 
839
        $signature = substr($uuid,20,12);
-
 
840
        if (strtolower($signature) == '5ce32bd83b96') {
-
 
841
                // HickelSOFT "SQL Server sortable UUID in C#"
-
 
842
                // Version 2: Resolution of 1 milliseconds, random part of 18 bits, UTC time, UUIDv8 conform.
-
 
843
                // Example: 2088dc33-000d-8045-87e8-5ce32bd83b96
-
 
844
                // Block 4
-
 
845
                $unused2bits = hexdec(substr($uuid,16,1)) & 0x3;
-
 
846
                $year = hexdec(substr($uuid,17,3));
-
 
847
                // Block 3
-
 
848
                $dayOfYear = hexdec(substr($uuid,13,3)); // 1..366
-
 
849
                $day = (($dayOfYear < 1) || ($dayOfYear > 366)) ? "XX" : intval(getDateFromDay($year, $dayOfYear)->format('d'));
-
 
850
                $month = (($dayOfYear < 1) || ($dayOfYear > 366)) ? "XX" : intval(getDateFromDay($year, $dayOfYear)->format('m'));
-
 
851
                // Block 2
-
 
852
                $minuteOfDay = hexdec(substr($uuid,8,4)); // 1..1440
-
 
853
                $minutes = (($minuteOfDay < 1) || ($minuteOfDay > 1440)) ? "XX" : ($minuteOfDay-1) % 60;
-
 
854
                $hours = (($minuteOfDay < 1) || ($minuteOfDay > 1440)) ? "XX" : (int)floor(($minuteOfDay-1) / 60);
-
 
855
                // Block 1
-
 
856
                $rnd16bits = substr($uuid,0,4);
-
 
857
                $millisecond8bits = hexdec(substr($uuid,4,2));
-
 
858
                $milliseconds = round($millisecond8bits / 255 * 999);
-
 
859
                $seconds = hexdec(substr($uuid,6,2));
-
 
860
                // Verbose info
-
 
861
                $utc_time =
-
 
862
                        str_pad("$year",4,'0',STR_PAD_LEFT).'-'.
-
 
863
                        str_pad("$month",2,'0',STR_PAD_LEFT).'-'.
-
 
864
                        str_pad("$day",2,'0',STR_PAD_LEFT).' '.
-
 
865
                        str_pad("$hours",2,'0',STR_PAD_LEFT).':'.
-
 
866
                        str_pad("$minutes",2,'0',STR_PAD_LEFT).':'.
-
 
867
                        str_pad("$seconds",2,'0',STR_PAD_LEFT)."'".
-
 
868
                        str_pad("$milliseconds",2,'0',STR_PAD_LEFT);
-
 
869
                if (strpos($utc_time,'X') === false) {
-
 
870
                        $deviation = "(deviation -2ms..2ms)";
-
 
871
                        echo "\n<u>Interpretation of <a href=\"https://gist.github.com/danielmarschall/7fafd270a3bc107d38e8449ce7420c25\">HickelSOFT \"SQL Server Sortable Custom UUID\", Version 2</a></u>\n\n";
-
 
872
                        echo sprintf("%-32s %s\n", "Random 16 bits:", "[0x$rnd16bits] 0b".str_pad("".base_convert($rnd16bits, 16, 2), 16, '0', STR_PAD_LEFT));
-
 
873
                        echo sprintf("%-32s %s\n", "Milliseconds:", "[0x".substr($uuid,4,2)."] $milliseconds $deviation");
-
 
874
                        echo sprintf("%-32s %s\n", "Seconds:", "[0x".substr($uuid,6,2)."] $seconds");
-
 
875
                        echo sprintf("%-32s %s\n", "Minute of day:", "[0x".substr($uuid,8,4)."] $minuteOfDay (".str_pad("$hours",2,'0',STR_PAD_LEFT).":".str_pad("$minutes",2,'0',STR_PAD_LEFT).")");
-
 
876
                        echo sprintf("%-32s %s\n", "Day of year:", "[0x".substr($uuid,13,3)."] $dayOfYear (Day=$day, Month=$month)");
-
 
877
                        echo sprintf("%-32s %s\n", "Unused 2 bits:", "[$unused2bits] 0b".str_pad("".base_convert("$unused2bits", 16, 2), 2, '0', STR_PAD_LEFT));
-
 
878
                        echo sprintf("%-32s %s\n", "Year:", "[0x".substr($uuid,17,3)."] $year");
-
 
879
                        echo sprintf("%-32s %s\n", "Signature:", "[0x".substr($uuid,20,12)."] HickelSOFT \"SQL Server Sortable Custom UUID\", Version 2 (very likely)");
-
 
880
                        echo sprintf("%-32s %s\n", "UTC Date Time:", "$utc_time $deviation");
-
 
881
                }
-
 
882
        } else if (strtolower($signature) == '000000000000') {
-
 
883
                // HickelSOFT "SQL Server sortable UUID in C#"
-
 
884
                // Version 1: Resolution of 1 milliseconds, random part of 16 bits, local timezone, NOT UUIDv8 conform.
-
 
885
                // Example: ff38da51-1301-0903-2420-000000000000
-
 
886
                // Block 4
-
 
887
                $year = substr($uuid,18,2) . substr($uuid,16,2);
-
 
888
                $year = (!is_numeric($year) || ($year < 2000) || ($year > 2999)) ? "XXXX" : $year = intval($year);
-
 
889
                // Block 3
-
 
890
                $day = substr($uuid,12,2);
-
 
891
                $day = (!is_numeric($day) || ($day < 0) || ($day >= 60)) ? "XX" : intval($day);
-
 
892
                $month = substr($uuid,14,2);
-
 
893
                $month = (!is_numeric($month) || ($month < 0) || ($month >= 60)) ? "XX" : intval($month);
-
 
894
                // Block 2
-
 
895
                $minutes = substr($uuid,8,2);
-
 
896
                $minutes = (!is_numeric($minutes) || ($minutes < 0) || ($minutes >= 60)) ? "XX" : intval($minutes);
-
 
897
                $hours = substr($uuid,10,2);
-
 
898
                $hours = (!is_numeric($hours) || ($hours < 0) || ($hours >= 60)) ? "XX" : intval($hours);
-
 
899
                // Block 1
-
 
900
                $rnd16bits = substr($uuid,0,4);
-
 
901
                $millisecond8bits = hexdec(substr($uuid,4,2));
-
 
902
                $milliseconds = round($millisecond8bits / 255 * 999);
-
 
903
                $seconds = substr($uuid,6,2);
-
 
904
                $seconds = (!is_numeric($seconds) || ($seconds < 0) || ($seconds >= 60)) ? "XX" : intval($seconds);
-
 
905
                // Verbose info
-
 
906
                $local_time =
-
 
907
                        str_pad("$year",4,'0',STR_PAD_LEFT).'-'.
-
 
908
                        str_pad("$month",2,'0',STR_PAD_LEFT).'-'.
-
 
909
                        str_pad("$day",2,'0',STR_PAD_LEFT).' '.
-
 
910
                        str_pad("$hours",2,'0',STR_PAD_LEFT).':'.
-
 
911
                        str_pad("$minutes",2,'0',STR_PAD_LEFT).':'.
-
 
912
                        str_pad("$seconds",2,'0',STR_PAD_LEFT)."'".
-
 
913
                        str_pad("$milliseconds",2,'0',STR_PAD_LEFT);
-
 
914
                if (strpos($local_time,'X') === false) {
-
 
915
                        $deviation = "(deviation -4ms..0ms)";
-
 
916
                        echo "\n<u>Interpretation of <a href=\"https://gist.github.com/danielmarschall/7fafd270a3bc107d38e8449ce7420c25\">HickelSOFT \"SQL Server Sortable Custom UUID\", Version 1</a></u>\n\n";
-
 
917
                        echo sprintf("%-32s %s\n", "Random 16 bits:", "[0x$rnd16bits] 0b".str_pad(base_convert($rnd16bits, 16, 2), 16, '0', STR_PAD_LEFT));
-
 
918
                        echo sprintf("%-32s %s\n", "Milliseconds:", "[0x".substr($uuid,4,2)."] $milliseconds $deviation");
-
 
919
                        echo sprintf("%-32s %s\n", "Seconds:", "[0x".substr($uuid,6,2)."] $seconds");
-
 
920
                        echo sprintf("%-32s %s\n", "Minutes:", "[0x".substr($uuid,8,2)."] $minutes");
-
 
921
                        echo sprintf("%-32s %s\n", "Hours:", "[0x".substr($uuid,10,2)."] $hours");
-
 
922
                        echo sprintf("%-32s %s\n", "Day:", "[0x".substr($uuid,12,2)."] $day");
-
 
923
                        echo sprintf("%-32s %s\n", "Month:", "[0x".substr($uuid,14,2)."] $month");
-
 
924
                        echo sprintf("%-32s %s\n", "Year:", "[0x".substr($uuid,16,4)."] $year");
-
 
925
                        echo sprintf("%-32s %s\n", "Signature:", "[0x".substr($uuid,20,12)."] HickelSOFT \"SQL Server Sortable Custom UUID\", Version 1 (maybe)");
-
 
926
                        echo sprintf("%-32s %s\n", "Generator's Local Date Time:", "$local_time $deviation");
-
 
927
                }
-
 
928
        }
-
 
929
 
-
 
930
        // END: HickelSOFT UUID
-
 
931
 
836
        if (!$echo) {
932
        if (!$echo) {
837
                $out = ob_get_contents();
933
                $out = ob_get_contents();
838
                ob_end_clean();
934
                ob_end_clean();
839
                return $out;
935
                return $out;
840
        } else {
936
        } else {
Line 869... Line 965...
869
                $hex = str_pad($hex, 32, "0", STR_PAD_LEFT);
965
                $hex = str_pad($hex, 32, "0", STR_PAD_LEFT);
870
                return substr($hex,0,8).'-'.substr($hex,8,4).'-'.substr($hex,12,4).'-'.substr($hex,16,4).'-'.substr($hex,20,12);
966
                return substr($hex,0,8).'-'.substr($hex,8,4).'-'.substr($hex,12,4).'-'.substr($hex,16,4).'-'.substr($hex,20,12);
871
        } else if ((count($ary) == 14) && (strpos($oid, '1.2.840.113556.1.8000.2554.') === 0)) {
967
        } else if ((count($ary) == 14) && (strpos($oid, '1.2.840.113556.1.8000.2554.') === 0)) {
872
                // Microsoft UUID-to-OID
968
                // Microsoft UUID-to-OID
873
                // Example: {53c08bb6-b2eb-5038-bf28-ad41a08c50ef} = 1.2.840.113556.1.8000.2554.21440.35766.45803.20536.48936.11354528.9195759
969
                // Example: {53c08bb6-b2eb-5038-bf28-ad41a08c50ef} = 1.2.840.113556.1.8000.2554.21440.35766.45803.20536.48936.11354528.9195759
874
                $a = $ary[7];
970
                $a = intval($ary[7]);
875
                $b = $ary[8];
971
                $b = intval($ary[8]);
876
                $c = $ary[9];
972
                $c = intval($ary[9]);
877
                $d = $ary[10];
973
                $d = intval($ary[10]);
878
                $e = $ary[11];
974
                $e = intval($ary[11]);
879
                $f = $ary[12];
975
                $f = intval($ary[12]);
880
                $g = $ary[13];
976
                $g = intval($ary[13]);
881
                return dechex($a).dechex($b).'-'.dechex($c).'-'.dechex($d).'-'.dechex($e).'-'.dechex($f).dechex($g);
977
                return dechex($a).dechex($b).'-'.dechex($c).'-'.dechex($d).'-'.dechex($e).'-'.dechex($f).dechex($g);
882
        } else if ((count($ary) == 10) && (strpos($oid, '1.3.6.1.4.1.54392.1.') === 0)) {
978
        } else if ((count($ary) == 10) && (strpos($oid, '1.3.6.1.4.1.54392.1.') === 0)) {
883
                // Waterjuice UUID-to-OID 2x64 Bits
979
                // Waterjuice UUID-to-OID 2x64 Bits
884
                // Example: {53c08bb6-b2eb-5038-bf28-ad41a08c50ef} = 1.3.6.1.4.1.54392.1.6034977117478539320.13774449957690691823
980
                // Example: {53c08bb6-b2eb-5038-bf28-ad41a08c50ef} = 1.3.6.1.4.1.54392.1.6034977117478539320.13774449957690691823
885
                $a1 = gmp_strval(gmp_init($ary[8],10),16); if (strlen($a1)>16) return false;
981
                $a1 = gmp_strval(gmp_init($ary[8],10),16); if (strlen($a1)>16) return false;
Line 1452... Line 1548...
1452
               substr($hash, 12, 4).'-'.
1548
               substr($hash, 12, 4).'-'.
1453
               substr($hash, 16, 4).'-'.
1549
               substr($hash, 16, 4).'-'.
1454
               substr($hash, 20, 12);
1550
               substr($hash, 20, 12);
1455
}
1551
}
1456
 
1552
 
-
 
1553
/**
-
 
1554
 * The sorting of SQL Server is rather confusing and incompatible with UUIDv6 and UUIDv7.
-
 
1555
 * Therefore this method generates UUID which are sortable by SQL Server.
-
 
1556
 * Version 1: Resolution of 1 milliseconds, random part of 16 bits, local timezone, 48 zero bits "signature", NOT UUIDv8 conform.
-
 
1557
 * Version 2: Resolution of 1 milliseconds, random part of 16 bits, UTC time, 48 bit random "signature", UUIDv8 conform.
-
 
1558
 * C# implementation: https://gist.github.com/danielmarschall/7fafd270a3bc107d38e8449ce7420c25
-
 
1559
 * PHP implementation: https://github.com/danielmarschall/uuid_mac_utils/blob/master/includes/uuid_utils.inc.php
-
 
1560
 *
-
 
1561
 * @param int      $hickelUuidVersion (optional)
-
 
1562
 * @param DateTime $dt                (optional)
-
 
1563
 * @return string The UUID
-
 
1564
 */
-
 
1565
function gen_uuid_v8_sqlserver_sortable(int $hickelUuidVersion = 2, DateTime $dt = null): string {
-
 
1566
        // The sorting in SQL Server is like this:
-
 
1567
 
-
 
1568
        if ($dt == null) $dt = new DateTime();
-
 
1569
 
-
 
1570
        // First Sort block 5, nibbles from left to right (i.e. 000000000001 < 000000000010 < ... < 010000000000 < 100000000000)
-
 
1571
        if ($hickelUuidVersion == 1) {
-
 
1572
                $block5 = "000000000000";
-
 
1573
        } else if ($hickelUuidVersion == 2) {
-
 
1574
                $block5 = "5ce32bd83b96";
-
 
1575
        } else {
-
 
1576
                throw new Exception("Invalid version");
-
 
1577
        }
-
 
1578
 
-
 
1579
        // Then: Sort block 4, nibbles from left to right
-
 
1580
        if ($hickelUuidVersion == 1) {
-
 
1581
                $year = $dt->format('Y');
-
 
1582
                $block4 = substr($year, 2, 2).substr($year, 0, 2); // Example: 0x2420 = 2024
-
 
1583
        } else {
-
 
1584
                $variant = 0x8; // First nibble needs to be 0b10_ (0x8-0xB) for "RFC 4122bis". We use it to store 2 more random bits.
-
 
1585
                $unused2bits = 0; // Cannot be used for random, because it would affect the sorting
-
 
1586
                $year = $dt->format('Y');
-
 
1587
                $block4 = sprintf('%01x%03x', $variant + ($unused2bits & 0x3), $year);
-
 
1588
        }
-
 
1589
 
-
 
1590
        // Then: Sort block 3, bytes from right to left (i.e. 0100 < 1000 < 0001 < 0010)
-
 
1591
        if ($hickelUuidVersion == 1) {
-
 
1592
                $block3 = $dt->format('dm');
-
 
1593
        } else {
-
 
1594
                $uuidVersion = 8; // First nibble needs to be "8" for "UUIDv8 = Custom UUID"
-
 
1595
                $dayOfYear = intval($dt->format('z')) + 1; /* 1..366 */
-
 
1596
                $block3 = sprintf('%01x%03x', $uuidVersion, $dayOfYear);
-
 
1597
        }
-
 
1598
 
-
 
1599
        // Then: Sort block 2, bytes from right to left
-
 
1600
        if ($hickelUuidVersion == 1) {
-
 
1601
                $block2 = $dt->format('ih');
-
 
1602
        } else {
-
 
1603
                $minuteOfDay = (intval($dt->format('i')) + intval($dt->format('h')) * 60) + 1; // 1..1440
-
 
1604
                $block2 = sprintf('%04x', $minuteOfDay);
-
 
1605
        }
-
 
1606
 
-
 
1607
        // Then: Sort block 1, bytes from right to left
-
 
1608
        if ($hickelUuidVersion == 1) {
-
 
1609
                $millisecond8bits = ceil(($dt->format('v') / 999) * 255); // deviation -4ms..0ms
-
 
1610
                $rnd16bits = _random_int(0x0000, 0xFFFF-1);
-
 
1611
                $block1 = sprintf('%04x%02x', $rnd16bits, $millisecond8bits).$dt->format('s');
-
 
1612
        } else {
-
 
1613
                $millisecond8bits = round(($dt->format('v') / 999) * 255); // deviation -2ms..2ms
-
 
1614
                $rnd16bits = _random_int(0x0000, 0xFFFF);
-
 
1615
                $block1 = sprintf('%04x%02x%02x', $rnd16bits, $millisecond8bits, $dt->format('s'));
-
 
1616
        }
-
 
1617
 
-
 
1618
        $sleep_ms = (int)ceil(999 / 255); // Make sure that "millisecond" is not repeated on this system
-
 
1619
        if (time_nanosleep(0,$sleep_ms*1000*1000) !== true) usleep($sleep_ms*1000);
-
 
1620
 
-
 
1621
        return strtolower("$block1-$block2-$block3-$block4-$block5");
-
 
1622
}
-
 
1623
 
1457
# --------------------------------------
1624
# --------------------------------------
1458
 
1625
 
1459
// http://php.net/manual/de/function.hex2bin.php#113057
1626
// http://php.net/manual/de/function.hex2bin.php#113057
1460
if (!function_exists('hex2bin')) {
1627
if (!function_exists('hex2bin')) {
1461
    function hex2bin($str) {
1628
    function hex2bin($str) {
Line 1494... Line 1661...
1494
        $sponge = SHA3::init(SHA3::SHAKE256);
1661
        $sponge = SHA3::init(SHA3::SHAKE256);
1495
        $sponge->absorb($msg);
1662
        $sponge->absorb($msg);
1496
        $bin = $sponge->squeeze($outputLength);
1663
        $bin = $sponge->squeeze($outputLength);
1497
        return $binary ? $bin : bin2hex($bin);
1664
        return $binary ? $bin : bin2hex($bin);
1498
}
1665
}
-
 
1666
 
-
 
1667
/**
-
 
1668
 * Converts the day of year of a year into a DateTime object
-
 
1669
 * @param int $year The year
-
 
1670
 * @param int $dayOfYear The day of year (value 1 till 365 or 1 till 366 for leap years)
-
 
1671
 * @return \DateTime The resulting date
-
 
1672
 */
-
 
1673
function getDateFromDay(int $year, int $dayOfYear): \DateTime {
-
 
1674
        // Note: "Y z" and "z Y" make a difference for leap years (last tested with PHP 8.0.3)
-
 
1675
        $date = \DateTime::createFromFormat('Y z', strval($year) . ' ' . strval($dayOfYear-1));
-
 
1676
        return $date;
-
 
1677
}
-
 
1678