~earboxer/bare-mess-php

1310a4efe33a5db3d3ea4618a0fd2cc2838f2fee — Zach DeCook 3 years ago 7170f55
* Primitives: Add Integer type
3 files changed, 93 insertions(+), 2 deletions(-)

M src/Bare.php
A src/Integer.php
M tests/PrimitivesTest.php
M src/Bare.php => src/Bare.php +3 -2
@@ 16,6 16,8 @@ class Bare
                $val = substr($mess, 0, $length);
                $mess = substr($mess, $length);
                return $val;
            case 'integer': // int
                return (new Integer($mess))->get();
        };
    }



@@ 32,8 34,7 @@ class Bare
                // TODO: Use uint for string's length.
                return UInt::fromValue(strlen($value))->mess() . $value;
            case 'integer': // int
                // TODO: encode as variable length integer.
                return chr($value);
                return Integer::fromValue($value)->mess();
            case 'double': // f64
                // TODO: encode this.
                return chr(0) . chr(0) . chr(0) . chr(0);

A src/Integer.php => src/Integer.php +56 -0
@@ 0,0 1,56 @@
<?php

namespace BareMess;

// We can't use "Int" as the name of this class as it's reserved by PHP.
// Note: Bare::mess and Bare::construct will encode a PHP int as a \BareMess\Integer.
class Integer
{
    use Mutatable;
    private int $value = 0;

    public function __construct(& $mess = null)
    {
        if ($mess === null){
            return;
        }
        $this->value = 0;
        // The LSB is the sign.
        $sign = (ord($mess[0]) % 2) ? -1 : 1;
        // We can't do this inside the loop because bitshifting by negative numbers is not allowed.
        $this->value += (ord($mess[0]) & 0b01111110) >> 1;
        for($i = 1; $i < strlen($mess) + 1; $i++) {
            if (! (ord($mess[$i - 1]) & 0b10000000) ) {
                $mess = substr($mess, $i);
                $this->value *= $sign;
                return;
            }
            // TODO: Throw an error if $mess is empty here.
            // Least significant bits first
            $this->value += (ord($mess[$i]) & 0b01111111) << (($i * 7) - 1);
        }
    }

    public function mess()
    {
        $mess = '';
        // The number of bytes should be greater than or equal to the number required,
        // abs(value * 2) < pow(2, 7 * bytes)
        $bytes = floor(log(abs($this->value) + 1, 2) / 7) + 1;
        $bits = (abs($this->value) << 1 & 0b01111110) + ($this->value < 0);
        if ($bytes != 1) {
            $bits = $bits | 0b10000000;
        }
        $mess .= chr($bits);
        for ($i = 1; $i < $bytes; $i++) {
            // BARE encodes integers as little-endian (least significant first),
            $bits = (abs($this->value) >> (($i * 7)-1)) & 0b01111111;
            // with the most significant bit being set for all except the last byte.
            if ($i != $bytes - 1) {
                $bits = $bits | 0b10000000;
            }
            $mess .= chr($bits);
        }
        return $mess;
    }
}

M tests/PrimitivesTest.php => tests/PrimitivesTest.php +34 -0
@@ 6,6 6,7 @@ use BareMess\Bare;
use BareMess\Example\Department;
use BareMess\Example\PublicKey;
use BareMess\{I8,I16,I32,I64};
use BareMess\Integer;
use BareMess\{U8,U16,U32,U64};
use BareMess\UInt;
use PHPUnit\Framework\TestCase;


@@ 59,6 60,39 @@ class PrimitivesTest extends TestCase
        $this->assertEquals("\x03", $dept->mess());
    }

    public function testInteger()
    {
        // Various equivalent ints.
        $mess = "\xC1\x80\x00\xC1\x00\x41";
        $int = Bare::construct($mess, 0);
        $this->assertEquals(-32, $int);
        $int2 = Bare::construct($mess, 0);
        $this->assertEquals(-32, $int2);
        $int3 = Bare::construct($mess, 0);
        $this->assertEquals(-32, $int3);
        $this->assertEquals('', $mess);

        $int = 300;
        $this->assertEquals(UInt::fromValue(600)->mess(), Bare::mess($int));

        // This is usually PHP_INT_MIN - 1 (BARE's ints are zig-zag encoded).
        $int = Integer::fromValue(-9223372036854775807);
        // Test round-tripping
        $mess = $int->mess();
        $int2 = new Integer($mess);
        $this->assertEquals('', $mess);
        $this->assertEquals(-9223372036854775807, $int->get());
        $this->assertEquals(-9223372036854775807, $int2->get());

        // Test largest value
        $int = Integer::fromValue(9223372036854775807);
        $mess = $int->mess();
        $int2 = new Integer($mess);
        $this->assertEquals('', $mess);
        $this->assertEquals(9223372036854775807, $int->get());
        $this->assertEquals(9223372036854775807, $int2->get());
    }

    public function testIs()
    {
        $i8 = new I8();