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:
parent
08b29ad23f
commit
061a9f8cbd
1 changed files with 29 additions and 5 deletions
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue