| 148 |
lars |
1 |
<?php
|
|
|
2 |
|
|
|
3 |
/*
|
|
|
4 |
* This file is part of the Symfony package.
|
|
|
5 |
*
|
|
|
6 |
* (c) Fabien Potencier <fabien@symfony.com>
|
|
|
7 |
*
|
|
|
8 |
* For the full copyright and license information, please view the LICENSE
|
|
|
9 |
* file that was distributed with this source code.
|
|
|
10 |
*/
|
|
|
11 |
|
|
|
12 |
namespace Symfony\Component\Uid;
|
|
|
13 |
|
|
|
14 |
/**
|
|
|
15 |
* @internal
|
|
|
16 |
*
|
|
|
17 |
* @author Nicolas Grekas <p@tchwork.com>
|
|
|
18 |
*/
|
|
|
19 |
class BinaryUtil
|
|
|
20 |
{
|
|
|
21 |
public const BASE10 = [
|
|
|
22 |
'' => '0123456789',
|
|
|
23 |
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
|
|
24 |
];
|
|
|
25 |
|
|
|
26 |
public const BASE58 = [
|
|
|
27 |
'' => '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz',
|
|
|
28 |
1 => 0, 1, 2, 3, 4, 5, 6, 7, 8, 'A' => 9,
|
|
|
29 |
'B' => 10, 'C' => 11, 'D' => 12, 'E' => 13, 'F' => 14, 'G' => 15,
|
|
|
30 |
'H' => 16, 'J' => 17, 'K' => 18, 'L' => 19, 'M' => 20, 'N' => 21,
|
|
|
31 |
'P' => 22, 'Q' => 23, 'R' => 24, 'S' => 25, 'T' => 26, 'U' => 27,
|
|
|
32 |
'V' => 28, 'W' => 29, 'X' => 30, 'Y' => 31, 'Z' => 32, 'a' => 33,
|
|
|
33 |
'b' => 34, 'c' => 35, 'd' => 36, 'e' => 37, 'f' => 38, 'g' => 39,
|
|
|
34 |
'h' => 40, 'i' => 41, 'j' => 42, 'k' => 43, 'm' => 44, 'n' => 45,
|
|
|
35 |
'o' => 46, 'p' => 47, 'q' => 48, 'r' => 49, 's' => 50, 't' => 51,
|
|
|
36 |
'u' => 52, 'v' => 53, 'w' => 54, 'x' => 55, 'y' => 56, 'z' => 57,
|
|
|
37 |
];
|
|
|
38 |
|
|
|
39 |
// https://tools.ietf.org/html/rfc4122#section-4.1.4
|
|
|
40 |
// 0x01b21dd213814000 is the number of 100-ns intervals between the
|
|
|
41 |
// UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
|
|
|
42 |
private const TIME_OFFSET_INT = 0x01B21DD213814000;
|
|
|
43 |
private const TIME_OFFSET_BIN = "\x01\xb2\x1d\xd2\x13\x81\x40\x00";
|
|
|
44 |
private const TIME_OFFSET_COM1 = "\xfe\x4d\xe2\x2d\xec\x7e\xbf\xff";
|
|
|
45 |
private const TIME_OFFSET_COM2 = "\xfe\x4d\xe2\x2d\xec\x7e\xc0\x00";
|
|
|
46 |
|
|
|
47 |
public static function toBase(string $bytes, array $map): string
|
|
|
48 |
{
|
|
|
49 |
$base = \strlen($alphabet = $map['']);
|
|
|
50 |
$bytes = array_values(unpack(\PHP_INT_SIZE >= 8 ? 'n*' : 'C*', $bytes));
|
|
|
51 |
$digits = '';
|
|
|
52 |
|
|
|
53 |
while ($count = \count($bytes)) {
|
|
|
54 |
$quotient = [];
|
|
|
55 |
$remainder = 0;
|
|
|
56 |
|
|
|
57 |
for ($i = 0; $i !== $count; ++$i) {
|
|
|
58 |
$carry = $bytes[$i] + ($remainder << (\PHP_INT_SIZE >= 8 ? 16 : 8));
|
|
|
59 |
$digit = intdiv($carry, $base);
|
|
|
60 |
$remainder = $carry % $base;
|
|
|
61 |
|
|
|
62 |
if ($digit || $quotient) {
|
|
|
63 |
$quotient[] = $digit;
|
|
|
64 |
}
|
|
|
65 |
}
|
|
|
66 |
|
|
|
67 |
$digits = $alphabet[$remainder].$digits;
|
|
|
68 |
$bytes = $quotient;
|
|
|
69 |
}
|
|
|
70 |
|
|
|
71 |
return $digits;
|
|
|
72 |
}
|
|
|
73 |
|
|
|
74 |
public static function fromBase(string $digits, array $map): string
|
|
|
75 |
{
|
|
|
76 |
$base = \strlen($map['']);
|
|
|
77 |
$count = \strlen($digits);
|
|
|
78 |
$bytes = [];
|
|
|
79 |
|
|
|
80 |
while ($count) {
|
|
|
81 |
$quotient = [];
|
|
|
82 |
$remainder = 0;
|
|
|
83 |
|
|
|
84 |
for ($i = 0; $i !== $count; ++$i) {
|
|
|
85 |
$carry = ($bytes ? $digits[$i] : $map[$digits[$i]]) + $remainder * $base;
|
|
|
86 |
|
|
|
87 |
if (\PHP_INT_SIZE >= 8) {
|
|
|
88 |
$digit = $carry >> 16;
|
|
|
89 |
$remainder = $carry & 0xFFFF;
|
|
|
90 |
} else {
|
|
|
91 |
$digit = $carry >> 8;
|
|
|
92 |
$remainder = $carry & 0xFF;
|
|
|
93 |
}
|
|
|
94 |
|
|
|
95 |
if ($digit || $quotient) {
|
|
|
96 |
$quotient[] = $digit;
|
|
|
97 |
}
|
|
|
98 |
}
|
|
|
99 |
|
|
|
100 |
$bytes[] = $remainder;
|
|
|
101 |
$count = \count($digits = $quotient);
|
|
|
102 |
}
|
|
|
103 |
|
|
|
104 |
return pack(\PHP_INT_SIZE >= 8 ? 'n*' : 'C*', ...array_reverse($bytes));
|
|
|
105 |
}
|
|
|
106 |
|
|
|
107 |
public static function add(string $a, string $b): string
|
|
|
108 |
{
|
|
|
109 |
$carry = 0;
|
|
|
110 |
for ($i = 7; 0 <= $i; --$i) {
|
|
|
111 |
$carry += \ord($a[$i]) + \ord($b[$i]);
|
|
|
112 |
$a[$i] = \chr($carry & 0xFF);
|
|
|
113 |
$carry >>= 8;
|
|
|
114 |
}
|
|
|
115 |
|
|
|
116 |
return $a;
|
|
|
117 |
}
|
|
|
118 |
|
|
|
119 |
/**
|
|
|
120 |
* @param string $time Count of 100-nanosecond intervals since the UUID epoch 1582-10-15 00:00:00 in hexadecimal
|
|
|
121 |
*/
|
|
|
122 |
public static function hexToDateTime(string $time): \DateTimeImmutable
|
|
|
123 |
{
|
|
|
124 |
if (\PHP_INT_SIZE >= 8) {
|
|
|
125 |
$time = (string) (hexdec($time) - self::TIME_OFFSET_INT);
|
|
|
126 |
} else {
|
|
|
127 |
$time = str_pad(hex2bin($time), 8, "\0", \STR_PAD_LEFT);
|
|
|
128 |
|
|
|
129 |
if (self::TIME_OFFSET_BIN <= $time) {
|
|
|
130 |
$time = self::add($time, self::TIME_OFFSET_COM2);
|
|
|
131 |
$time[0] = $time[0] & "\x7F";
|
|
|
132 |
$time = self::toBase($time, self::BASE10);
|
|
|
133 |
} else {
|
|
|
134 |
$time = self::add($time, self::TIME_OFFSET_COM1);
|
|
|
135 |
$time = '-'.self::toBase($time ^ "\xff\xff\xff\xff\xff\xff\xff\xff", self::BASE10);
|
|
|
136 |
}
|
|
|
137 |
}
|
|
|
138 |
|
|
|
139 |
if (9 > \strlen($time)) {
|
|
|
140 |
$time = '-' === $time[0] ? '-'.str_pad(substr($time, 1), 8, '0', \STR_PAD_LEFT) : str_pad($time, 8, '0', \STR_PAD_LEFT);
|
|
|
141 |
}
|
|
|
142 |
|
|
|
143 |
return \DateTimeImmutable::createFromFormat('U.u?', substr_replace($time, '.', -7, 0));
|
|
|
144 |
}
|
|
|
145 |
|
|
|
146 |
/**
|
|
|
147 |
* @return string Count of 100-nanosecond intervals since the UUID epoch 1582-10-15 00:00:00 in hexadecimal
|
|
|
148 |
*/
|
|
|
149 |
public static function dateTimeToHex(\DateTimeInterface $time): string
|
|
|
150 |
{
|
|
|
151 |
if (\PHP_INT_SIZE >= 8) {
|
|
|
152 |
if (-self::TIME_OFFSET_INT > $time = (int) $time->format('Uu0')) {
|
|
|
153 |
throw new \InvalidArgumentException('The given UUID date cannot be earlier than 1582-10-15.');
|
|
|
154 |
}
|
|
|
155 |
|
|
|
156 |
return str_pad(dechex(self::TIME_OFFSET_INT + $time), 16, '0', \STR_PAD_LEFT);
|
|
|
157 |
}
|
|
|
158 |
|
|
|
159 |
$time = $time->format('Uu0');
|
|
|
160 |
$negative = '-' === $time[0];
|
|
|
161 |
if ($negative && self::TIME_OFFSET_INT < $time = substr($time, 1)) {
|
|
|
162 |
throw new \InvalidArgumentException('The given UUID date cannot be earlier than 1582-10-15.');
|
|
|
163 |
}
|
|
|
164 |
$time = self::fromBase($time, self::BASE10);
|
|
|
165 |
$time = str_pad($time, 8, "\0", \STR_PAD_LEFT);
|
|
|
166 |
|
|
|
167 |
if ($negative) {
|
|
|
168 |
$time = self::add($time, self::TIME_OFFSET_COM1) ^ "\xff\xff\xff\xff\xff\xff\xff\xff";
|
|
|
169 |
} else {
|
|
|
170 |
$time = self::add($time, self::TIME_OFFSET_BIN);
|
|
|
171 |
}
|
|
|
172 |
|
|
|
173 |
return bin2hex($time);
|
|
|
174 |
}
|
|
|
175 |
}
|