| <?php |
| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * @package Avro |
| */ |
| |
| /** |
| * Methods for handling 64-bit operations using the GMP extension. |
| * |
| * This is a naive and hackish implementation that is intended |
| * to work well enough to support Avro. It has not been tested |
| * beyond what's needed to decode and encode long values. |
| * |
| * @package Avro |
| */ |
| class AvroGMP { |
| |
| /** |
| * @var resource memoized GMP resource for zero |
| */ |
| private static $gmp_0; |
| |
| /** |
| * @returns resource GMP resource for zero |
| */ |
| private static function gmp_0() |
| { |
| if (!isset(self::$gmp_0)) |
| self::$gmp_0 = gmp_init('0'); |
| return self::$gmp_0; |
| } |
| |
| /** |
| * @var resource memoized GMP resource for one (1) |
| */ |
| private static $gmp_1; |
| |
| /** |
| * @returns resource GMP resource for one (1) |
| */ |
| private static function gmp_1() |
| { |
| if (!isset(self::$gmp_1)) |
| self::$gmp_1 = gmp_init('1'); |
| return self::$gmp_1; |
| } |
| |
| /** |
| * @var resource memoized GMP resource for two (2) |
| */ |
| private static $gmp_2; |
| |
| /** |
| * @returns resource GMP resource for two (2) |
| */ |
| private static function gmp_2() |
| { |
| if (!isset(self::$gmp_2)) |
| self::$gmp_2 = gmp_init('2'); |
| return self::$gmp_2; |
| } |
| |
| /** |
| * @var resource memoized GMP resource for 0x7f |
| */ |
| private static $gmp_0x7f; |
| |
| /** |
| * @returns resource GMP resource for 0x7f |
| */ |
| private static function gmp_0x7f() |
| { |
| if (!isset(self::$gmp_0x7f)) |
| self::$gmp_0x7f = gmp_init('0x7f'); |
| return self::$gmp_0x7f; |
| } |
| |
| /** |
| * @var resource memoized GMP resource for 64-bit ~0x7f |
| */ |
| private static $gmp_n0x7f; |
| |
| /** |
| * @returns resource GMP resource for 64-bit ~0x7f |
| */ |
| private static function gmp_n0x7f() |
| { |
| if (!isset(self::$gmp_n0x7f)) |
| self::$gmp_n0x7f = gmp_init('0xffffffffffffff80'); |
| return self::$gmp_n0x7f; |
| } |
| |
| /** |
| * @var resource memoized GMP resource for 64-bits of 1 |
| */ |
| private static $gmp_0xfs; |
| |
| /** |
| * @returns resource GMP resource for 64-bits of 1 |
| */ |
| private static function gmp_0xfs() |
| { |
| if (!isset(self::$gmp_0xfs)) |
| self::$gmp_0xfs = gmp_init('0xffffffffffffffff'); |
| return self::$gmp_0xfs; |
| } |
| |
| /** |
| * @param GMP resource |
| * @returns GMP resource 64-bit two's complement of input. |
| */ |
| static function gmp_twos_complement($g) |
| { |
| return gmp_neg(gmp_sub(gmp_pow(self::gmp_2(), 64), $g)); |
| } |
| |
| /** |
| * @interal Only works up to shift 63 (doesn't wrap bits around). |
| * @param resource|int|string $g |
| * @param int $shift number of bits to shift left |
| * @returns resource $g shifted left |
| */ |
| static function shift_left($g, $shift) |
| { |
| if (0 == $shift) |
| return $g; |
| |
| if (0 > gmp_sign($g)) |
| $g = self::gmp_twos_complement($g); |
| |
| $m = gmp_mul($g, gmp_pow(self::gmp_2(), $shift)); |
| $m = gmp_and($m, self::gmp_0xfs()); |
| if (gmp_testbit($m, 63)) |
| $m = gmp_neg(gmp_add(gmp_and(gmp_com($m), self::gmp_0xfs()), |
| self::gmp_1())); |
| return $m; |
| } |
| |
| /** |
| * Arithmetic right shift |
| * @param resource|int|string $g |
| * @param int $shift number of bits to shift right |
| * @returns resource $g shifted right $shift bits |
| */ |
| static function shift_right($g, $shift) |
| { |
| if (0 == $shift) |
| return $g; |
| |
| if (0 <= gmp_sign($g)) |
| $m = gmp_div($g, gmp_pow(self::gmp_2(), $shift)); |
| else // negative |
| { |
| $g = gmp_and($g, self::gmp_0xfs()); |
| $m = gmp_div($g, gmp_pow(self::gmp_2(), $shift)); |
| $m = gmp_and($m, self::gmp_0xfs()); |
| for ($i = 63; $i >= (63 - $shift); $i--) |
| gmp_setbit($m, $i); |
| |
| $m = gmp_neg(gmp_add(gmp_and(gmp_com($m), self::gmp_0xfs()), |
| self::gmp_1())); |
| } |
| |
| return $m; |
| } |
| |
| /** |
| * @param int|str $n integer (or string representation of integer) to encode |
| * @return string $bytes of the long $n encoded per the Avro spec |
| */ |
| static function encode_long($n) |
| { |
| $g = gmp_init($n); |
| $g = gmp_xor(self::shift_left($g, 1), |
| self::shift_right($g, 63)); |
| $bytes = ''; |
| while (0 != gmp_cmp(self::gmp_0(), gmp_and($g, self::gmp_n0x7f()))) |
| { |
| $bytes .= chr(gmp_intval(gmp_and($g, self::gmp_0x7f())) | 0x80); |
| $g = self::shift_right($g, 7); |
| } |
| $bytes .= chr(gmp_intval($g)); |
| return $bytes; |
| } |
| |
| /** |
| * @param int[] $bytes array of ascii codes of bytes to decode |
| * @return string represenation of decoded long. |
| */ |
| static function decode_long_from_array($bytes) |
| { |
| $b = array_shift($bytes); |
| $g = gmp_init($b & 0x7f); |
| $shift = 7; |
| while (0 != ($b & 0x80)) |
| { |
| $b = array_shift($bytes); |
| $g = gmp_or($g, self::shift_left(($b & 0x7f), $shift)); |
| $shift += 7; |
| } |
| $val = gmp_xor(self::shift_right($g, 1), gmp_neg(gmp_and($g, 1))); |
| return gmp_strval($val); |
| } |
| |
| } |