From 82ac91f172fefed9bc2c969d90045523778a4f47 Mon Sep 17 00:00:00 2001 From: John Rayes Date: Sat, 9 May 2026 14:29:04 -0700 Subject: [PATCH] fix base32 encode/decode --- Sources/Uuid.php | 61 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/Sources/Uuid.php b/Sources/Uuid.php index a336f76884..7395dfc116 100644 --- a/Sources/Uuid.php +++ b/Sources/Uuid.php @@ -1058,18 +1058,42 @@ protected function adjustTimestamp(): int * that PHP's base_convert() produces. This is different from basic base32, * which is specified in RFC 4648, section 6. * + * This implementation is optimized specifically for UUIDs: + * * @param string $hex A hexadecimal string. * @return string A base32hex string. */ protected static function encodeBase32Hex(string $hex): string { - $b32 = ''; + $bin = hex2bin($hex); + $alphabet = self::BASE32_HEX; + $buffer = 0; + $bits = 0; + $pos = 0; + + // UUID Base32 is always 26 chars + $out = str_repeat('0', 26); + + // UUIDs are always exactly 128 bits (16 bytes). + for ($i = 0; $i < 16; $i++) { + // Each input byte contributes 8 bits to a buffer. + $buffer = ($buffer << 8) | \ord($bin[$i]); + $bits += 8; + + // Output characters consume 5 bits at a time. Remaining bits are carried forward between iterations. + while ($bits >= 5) { + $bits -= 5; + + $out[$pos++] = $alphabet[($buffer >> $bits) & 31]; + } + } - foreach (str_split(strrev($hex), 10) as $chunk) { - $b32 = str_pad(base_convert(strrev($chunk), 16, 32), 8, '0', STR_PAD_LEFT) . $b32; + // UUID Base32 encoding always leaves remaining bits. + if ($bits > 0) { + $out[$pos] = $alphabet[($buffer << (5 - $bits)) & 31]; } - return ltrim($b32, '0'); + return $out; } /** @@ -1079,17 +1103,38 @@ protected static function encodeBase32Hex(string $hex): string * that PHP's base_convert() produces. This is different from basic base32, * which is specified in RFC 4648, section 6. * + * This implementation is optimized specifically for UUIDs: + * * @param string $b32 A base32hex string. * @return string A hexadecimal string. */ protected static function decodeBase32Hex(string $b32): string { - $hex = ''; + $buffer = 0; + $bits = 0; + $pos = 0; + + // Decoded UUIDs are always exactly 16 bytes. + $out = str_repeat("\0", 16); - foreach (str_split(strrev($b32), 8) as $chunk) { - $hex = str_pad(base_convert(strrev($chunk), 32, 16), 10, '0', STR_PAD_LEFT) . $hex; + // UUID Base32 strings are always exactly 26 chars. + for ($i = 0; $i < 26; $i++) { + $c = \ord($b32[$i]); + + // Convert ASCII to base32 (0-9 => 0-9, a-v => 10-31). + $buffer = ($buffer << 5) | ($c <= 57 ? $c - 48 : $c - 87); + + // Each Base32 character contributes 5 bits to a buffer. + $bits += 5; + + // Consume output bytes 8 bits at a time. + if ($bits >= 8) { + $bits -= 8; + + $out[$pos++] = \chr(($buffer >> $bits) & 0xFF); + } } - return ltrim($hex, '0'); + return bin2hex($out); } }