From 0e05c6de809bd390b27586fd8a18bd7e8c4e638a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 14 Mar 2020 11:48:14 +0100 Subject: [PATCH] [Uid] improve base convertion logic --- src/Symfony/Component/Uid/BinaryUtil.php | 97 ++++++++++++++++++++++ src/Symfony/Component/Uid/InternalUtil.php | 85 ------------------- src/Symfony/Component/Uid/Ulid.php | 4 +- src/Symfony/Component/Uid/Uuid.php | 4 +- 4 files changed, 101 insertions(+), 89 deletions(-) create mode 100644 src/Symfony/Component/Uid/BinaryUtil.php delete mode 100644 src/Symfony/Component/Uid/InternalUtil.php diff --git a/src/Symfony/Component/Uid/BinaryUtil.php b/src/Symfony/Component/Uid/BinaryUtil.php new file mode 100644 index 000000000000..5e3925df8a6c --- /dev/null +++ b/src/Symfony/Component/Uid/BinaryUtil.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid; + +/** + * @internal + * + * @author Nicolas Grekas + */ +class BinaryUtil +{ + public const BASE10 = [ + '' => '0123456789', + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + ]; + + public static function toBase(string $bytes, array $map): string + { + $base = \strlen($alphabet = $map['']); + $bytes = array_values(unpack(\PHP_INT_SIZE >= 8 ? 'n*' : 'C*', $bytes)); + $digits = ''; + + while ($count = \count($bytes)) { + $quotient = []; + $remainder = 0; + + for ($i = 0; $i !== $count; ++$i) { + $carry = $bytes[$i] + ($remainder << (\PHP_INT_SIZE >= 8 ? 16 : 8)); + $digit = intdiv($carry, $base); + $remainder = $carry % $base; + + if ($digit || $quotient) { + $quotient[] = $digit; + } + } + + $digits = $alphabet[$remainder].$digits; + $bytes = $quotient; + } + + return $digits; + } + + public static function fromBase(string $digits, array $map): string + { + $base = \strlen($map['']); + $count = \strlen($digits); + $bytes = []; + + while ($count) { + $quotient = []; + $remainder = 0; + + for ($i = 0; $i !== $count; ++$i) { + $carry = ($bytes ? $digits[$i] : $map[$digits[$i]]) + $remainder * $base; + + if (\PHP_INT_SIZE >= 8) { + $digit = $carry >> 16; + $remainder = $carry & 0xFFFF; + } else { + $digit = $carry >> 8; + $remainder = $carry & 0xFF; + } + + if ($digit || $quotient) { + $quotient[] = $digit; + } + } + + $bytes[] = $remainder; + $count = \count($digits = $quotient); + } + + return pack(\PHP_INT_SIZE >= 8 ? 'n*' : 'C*', ...array_reverse($bytes)); + } + + public static function add(string $a, string $b): string + { + $carry = 0; + for ($i = 7; 0 <= $i; --$i) { + $carry += \ord($a[$i]) + \ord($b[$i]); + $a[$i] = \chr($carry & 0xFF); + $carry >>= 8; + } + + return $a; + } +} diff --git a/src/Symfony/Component/Uid/InternalUtil.php b/src/Symfony/Component/Uid/InternalUtil.php deleted file mode 100644 index a63e15b7781e..000000000000 --- a/src/Symfony/Component/Uid/InternalUtil.php +++ /dev/null @@ -1,85 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Uid; - -/** - * @internal - * - * @author Nicolas Grekas - */ -class InternalUtil -{ - public static function toBinary(string $digits): string - { - $bytes = ''; - $len = \strlen($digits); - - while ($len > $i = strspn($digits, '0')) { - for ($j = 2, $r = 0; $i < $len; $i += $j, $j = 0) { - do { - $r *= 10; - $d = (int) substr($digits, $i, ++$j); - } while ($i + $j < $len && $r + $d < 256); - - $j = \strlen((string) $d); - $q = str_pad(($d += $r) >> 8, $j, '0', STR_PAD_LEFT); - $digits = substr_replace($digits, $q, $i, $j); - $r = $d % 256; - } - - $bytes .= \chr($r); - } - - return strrev($bytes); - } - - public static function toDecimal(string $bytes): string - { - $digits = ''; - $len = \strlen($bytes); - - while ($len > $i = strspn($bytes, "\0")) { - for ($r = 0; $i < $len; $i += $j) { - $j = $d = 0; - do { - $r <<= 8; - $d = ($d << 8) + \ord($bytes[$i + $j]); - } while ($i + ++$j < $len && $r + $d < 10); - - if (256 < $d) { - $q = intdiv($d += $r, 10); - $bytes[$i] = \chr($q >> 8); - $bytes[1 + $i] = \chr($q & 0xFF); - } else { - $bytes[$i] = \chr(intdiv($d += $r, 10)); - } - $r = $d % 10; - } - - $digits .= (string) $r; - } - - return strrev($digits); - } - - public static function binaryAdd(string $a, string $b): string - { - $sum = 0; - for ($i = 7; 0 <= $i; --$i) { - $sum += \ord($a[$i]) + \ord($b[$i]); - $a[$i] = \chr($sum & 0xFF); - $sum >>= 8; - } - - return $a; - } -} diff --git a/src/Symfony/Component/Uid/Ulid.php b/src/Symfony/Component/Uid/Ulid.php index 69576f6a92ae..a09918f0ea5a 100644 --- a/src/Symfony/Component/Uid/Ulid.php +++ b/src/Symfony/Component/Uid/Ulid.php @@ -121,7 +121,7 @@ public function getTime(): float base_convert(substr($time, 6, 4), 32, 16) ); - return InternalUtil::toDecimal(hex2bin($time)) / 1000; + return BinaryUtil::toBase(hex2bin($time), BinaryUtil::BASE10) / 1000; } public function __toString(): string @@ -163,7 +163,7 @@ private static function generate(): string if (\PHP_INT_SIZE >= 8) { $time = base_convert($time, 10, 32); } else { - $time = bin2hex(InternalUtil::toBinary($time)); + $time = bin2hex(BinaryUtil::fromBase($time, BinaryUtil::BASE10)); $time = sprintf('%s%04s%04s', base_convert(substr($time, 0, 2), 16, 32), base_convert(substr($time, 2, 5), 16, 32), diff --git a/src/Symfony/Component/Uid/Uuid.php b/src/Symfony/Component/Uid/Uuid.php index 67c19caf74d0..d6ae0f1d7d37 100644 --- a/src/Symfony/Component/Uid/Uuid.php +++ b/src/Symfony/Component/Uid/Uuid.php @@ -121,10 +121,10 @@ public function getTime(): float } $time = str_pad(hex2bin($time), 8, "\0", STR_PAD_LEFT); - $time = InternalUtil::binaryAdd($time, self::TIME_OFFSET_COM); + $time = BinaryUtil::add($time, self::TIME_OFFSET_COM); $time[0] = $time[0] & "\x7F"; - return InternalUtil::toDecimal($time) / 10000000; + return BinaryUtil::toBase($time, BinaryUtil::BASE10) / 10000000; } public function getMac(): string