blob: cc1cd2a47503b9b08ee781cf4534c15a713b88ba [file]
<?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.
*/
namespace Apache\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.
*/
class AvroGMP
{
/**
* @var \GMP memoized GMP resource for zero
*/
private static \GMP $gmp_0;
/**
* @var \GMP memoized GMP resource for one (1)
*/
private static \GMP $gmp_1;
/**
* @var \GMP memoized GMP resource for two (2)
*/
private static \GMP $gmp_2;
/**
* @var \GMP memoized GMP resource for 0x7f
*/
private static \GMP $gmp_0x7f;
/**
* @var \GMP memoized GMP resource for 64-bit ~0x7f
*/
private static \GMP $gmp_n0x7f;
/**
* @var \GMP memoized GMP resource for 64-bits of 1
*/
private static \GMP $gmp_0xfs;
/**
* @param int|string $n integer (or string representation of integer) to encode
* @return string $bytes of the long $n encoded per the Avro spec
*/
public static function encodeLong(int|string $n): string
{
$g = gmp_init($n);
$g = gmp_xor(
self::shiftLeft($g, 1),
self::shiftRight($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::shiftRight($g, 7);
}
$bytes .= chr(gmp_intval($g));
return $bytes;
}
/**
* @interal Only works up to shift 63 (doesn't wrap bits around).
* @param int $shift number of bits to shift left
* @return \GMP $g shifted left
*/
public static function shiftLeft(int|string|\GMP $g, int $shift)
{
if (0 == $shift) {
return $g;
}
if (0 > gmp_sign($g)) {
$g = self::gmpTwosComplement($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;
}
/**
* @return \GMP resource 64-bit two's complement of input.
*/
public static function gmpTwosComplement(int|string|\GMP $g)
{
return gmp_neg(gmp_sub(gmp_pow(self::gmp_2(), 64), $g));
}
/**
* Arithmetic right shift
* @param int $shift number of bits to shift right
* @return \GMP $g shifted right $shift bits
*/
public static function shiftRight(int|string|\GMP $g, int $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[] $bytes array of ascii codes of bytes to decode
* @return string represenation of decoded long.
*/
public static function decodeLongFromArray(array $bytes): string
{
$b = array_shift($bytes);
$g = gmp_init($b & 0x7F);
$shift = 7;
while (0 != ($b & 0x80)) {
$b = array_shift($bytes);
$g = gmp_or($g, self::shiftLeft($b & 0x7F, $shift));
$shift += 7;
}
$val = gmp_xor(self::shiftRight($g, 1), gmp_neg(gmp_and($g, 1)));
return gmp_strval($val);
}
// phpcs:disable PSR1.Methods.CamelCapsMethodName
/**
* @return \GMP 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;
}
/**
* @return \GMP GMP resource for 64-bits of 1
*/
private static function gmp_0xfs(): \GMP
{
if (!isset(self::$gmp_0xfs)) {
self::$gmp_0xfs = gmp_init('0xffffffffffffffff');
}
return self::$gmp_0xfs;
}
/**
* @return \GMP GMP resource for one (1)
*/
private static function gmp_1(): \GMP
{
if (!isset(self::$gmp_1)) {
self::$gmp_1 = gmp_init('1');
}
return self::$gmp_1;
}
/**
* @return \GMP GMP resource for zero
*/
private static function gmp_0(): \GMP
{
if (!isset(self::$gmp_0)) {
self::$gmp_0 = gmp_init('0');
}
return self::$gmp_0;
}
/**
* @return \GMP GMP resource for 64-bit ~0x7f
*/
private static function gmp_n0x7f(): \GMP
{
if (!isset(self::$gmp_n0x7f)) {
self::$gmp_n0x7f = gmp_init('0xffffffffffffff80');
}
return self::$gmp_n0x7f;
}
/**
* @return \GMP GMP resource for 0x7f
*/
private static function gmp_0x7f(): \GMP
{
if (!isset(self::$gmp_0x7f)) {
self::$gmp_0x7f = gmp_init('0x7f');
}
return self::$gmp_0x7f;
}
}