1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-29 10:12:41 +01:00

Implement base85 in a 32-bit safe way, without bcmath

Summary: See T1635 and the giant inline comment.

Test Plan: Ran unit tests on 32-bit and 64-bit machines.

Reviewers: btrahan, vrana

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T1635

Differential Revision: https://secure.phabricator.com/D3250
This commit is contained in:
epriestley 2012-08-12 19:21:33 -07:00
parent 08b29ad23f
commit 061a9f8cbd

View file

@ -682,6 +682,30 @@ final class ArcanistBundle {
// This is implemented awkwardly in order to closely mirror git's // This is implemented awkwardly in order to closely mirror git's
// implementation in base85.c // implementation in base85.c
// It is also implemeted awkwardly to work correctly on 32-bit machines.
// Broadly, this algorithm converts the binary input to printable output
// by transforming each 4 binary bytes of input to 5 printable bytes of
// output, one piece at a time.
//
// To do this, we convert the 4 bytes into a 32-bit integer, then use
// modulus and division by 85 to pick out printable bytes (85^5 is slightly
// larger than 2^32). In C, this algorithm is fairly easy to implement
// because the accumulator can be made unsigned.
//
// In PHP, there are no unsigned integers, so values larger than 2^31 break
// on 32-bit systems under modulus:
//
// $ php -r 'print (1 << 31) % 13;' # On a 32-bit machine.
// -11
//
// However, PHP's float type is an IEEE 754 64-bit double precision float,
// so we can safely store integers up to around 2^53 without loss of
// precision. To work around the lack of an unsigned type, we just use a
// double and perform the modulus with fmod().
//
// (Since PHP overflows integer operations into floats, we don't need much
// additional casting.)
static $map = array( static $map = array(
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
@ -700,19 +724,19 @@ final class ArcanistBundle {
$pos = 0; $pos = 0;
$bytes = strlen($data); $bytes = strlen($data);
while ($bytes) { while ($bytes) {
$accum = '0'; $accum = 0;
for ($count = 24; $count >= 0; $count -= 8) { for ($count = 24; $count >= 0; $count -= 8) {
$val = ord($data[$pos++]); $val = ord($data[$pos++]);
$val = bcmul($val, (string)(1 << $count)); $val = $val * (1 << $count);
$accum = bcadd($accum, $val); $accum = $accum + $val;
if (--$bytes == 0) { if (--$bytes == 0) {
break; break;
} }
} }
$slice = ''; $slice = '';
for ($count = 4; $count >= 0; $count--) { for ($count = 4; $count >= 0; $count--) {
$val = bcmod($accum, 85); $val = (int)fmod($accum, 85.0);
$accum = bcdiv($accum, 85); $accum = floor($accum / 85.0);
$slice .= $map[$val]; $slice .= $map[$val];
} }
$buf .= strrev($slice); $buf .= strrev($slice);