diff --git a/README.md b/README.md index cb9a9bd..95c9a70 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,10 @@ Methods **readInt32()** returns a 32-bit signed integer +**readUInt64()** returns a 64-bit unsigned integer + +**readInt64()** returns a 64-bit signed integer + **readSingle()** returns a 4-bytes floating-point **readUBits($length)** returns a variable length of bits (unsigned) diff --git a/src/BinaryReader.php b/src/BinaryReader.php index e30aa10..8e2d4a0 100644 --- a/src/BinaryReader.php +++ b/src/BinaryReader.php @@ -8,6 +8,7 @@ use PhpBinaryReader\Type\Int8; use PhpBinaryReader\Type\Int16; use PhpBinaryReader\Type\Int32; +use PhpBinaryReader\Type\Int64; use PhpBinaryReader\Type\Single; use PhpBinaryReader\Type\Str; @@ -78,6 +79,11 @@ class BinaryReader */ private $int32Reader; + /** + * @var \PhpBinaryReader\Type\Int64 + */ + private $int64Reader; + /** * @var \PhpBinaryReader\Type\Single */ @@ -109,6 +115,7 @@ public function __construct($input, $endian = Endian::ENDIAN_LITTLE) $this->int8Reader = new Int8(); $this->int16Reader = new Int16(); $this->int32Reader = new Int32(); + $this->int64Reader = new Int64(); $this->singleReader = new Single(); } @@ -213,6 +220,22 @@ public function readUInt32() return $this->int32Reader->read($this); } + /** + * @return int + */ + public function readInt64() + { + return $this->int64Reader->readSigned($this); + } + + /** + * @return int + */ + public function readUInt64() + { + return $this->int64Reader->read($this); + } + /** * @return float */ @@ -435,6 +458,14 @@ public function getInt32Reader() return $this->int32Reader; } + /** + * @return \PhpBinaryReader\Type\Int64 + */ + public function getInt64Reader() + { + return $this->int64Reader; + } + /** * @return \PhpBinaryReader\Type\Single */ diff --git a/src/Type/Int64.php b/src/Type/Int64.php new file mode 100644 index 0000000..b1e4158 --- /dev/null +++ b/src/Type/Int64.php @@ -0,0 +1,120 @@ +canReadBytes(8)) { + throw new \OutOfBoundsException('Cannot read 64-bit int, it exceeds the boundary of the file'); + } + + $endian = $br->getEndian() == Endian::ENDIAN_BIG ? $this->endianBig : $this->endianLittle; + $firstSegment = $br->readFromHandle(4); + $secondSegment = $br->readFromHandle(4); + + $firstHalf = unpack($endian, $firstSegment)[1]; + $secondHalf = unpack($endian, $secondSegment)[1]; + + if ($br->getEndian() == Endian::ENDIAN_BIG) { + $value = bcadd($secondHalf, bcmul($firstHalf, "4294967296")); + } else { + $value = bcadd($firstHalf, bcmul($secondHalf, "4294967296")); + } + + if ($br->getCurrentBit() != 0) { + $value = $this->bitReader($br, $value); + } + + return $value; + } + + /** + * Returns a Signed 64-Bit Integer + * + * @param \PhpBinaryReader\BinaryReader $br + * @return int + */ + public function readSigned(&$br) + { + $value = $this->read($br); + if (bccomp($value, bcpow(2, 63)) >= 0) { + $value = bcsub($value, bcpow(2, 64)); + } + + return $value; + } + + /** + * @param \PhpBinaryReader\BinaryReader $br + * @param int $data + * @return int + */ + private function bitReader(&$br, $data) + { + $bitmask = new BitMask(); + $loMask = $bitmask->getMask($br->getCurrentBit(), BitMask::MASK_LO); + $hiMask = $bitmask->getMask($br->getCurrentBit(), BitMask::MASK_HI); + $hiBits = ($br->getNextByte() & $hiMask) << 56; + $miBits = ($data & 0xFFFFFFFFFFFFFF00) >> (8 - $br->getCurrentBit()); + $loBits = ($data & $loMask); + $br->setNextByte($data & 0xFF); + + return $hiBits | $miBits | $loBits; + } + + /** + * @param string $endianBig + */ + public function setEndianBig($endianBig) + { + $this->endianBig = $endianBig; + } + + /** + * @return string + */ + public function getEndianBig() + { + return $this->endianBig; + } + + /** + * @param string $endianLittle + */ + public function setEndianLittle($endianLittle) + { + $this->endianLittle = $endianLittle; + } + + /** + * @return string + */ + public function getEndianLittle() + { + return $this->endianLittle; + } +} diff --git a/test/BinaryReaderTest.php b/test/BinaryReaderTest.php index 32a4d61..cd02d96 100644 --- a/test/BinaryReaderTest.php +++ b/test/BinaryReaderTest.php @@ -114,6 +114,21 @@ public function testInt32($brBig, $brLittle) $this->assertEquals(3, $brLittle->readUInt32()); } + /** + * @dataProvider binaryReaders + */ + public function testInt64($brBig, $brLittle) + { + $this->assertEquals(12885059444, $brBig->readInt64()); + $this->assertEquals(8387672839590772739, $brLittle->readInt64()); + + $brLittle->setPosition(0); + $brBig->setPosition(0); + + $this->assertEquals(12885059444, $brBig->readUInt64()); + $this->assertEquals(8387672839590772739, $brLittle->readUInt64()); + } + /** * @param \PhpBinaryReader\BinaryReader $brBig * @param \PhpBinaryReader\BinaryReader $brLittle @@ -321,6 +336,7 @@ public function testReaders() $this->assertInstanceOf('\PhpBinaryReader\Type\Byte', $brBig->getByteReader()); $this->assertInstanceOf('\PhpBinaryReader\Type\Int16', $brBig->getInt16Reader()); $this->assertInstanceOf('\PhpBinaryReader\Type\Int32', $brBig->getInt32Reader()); + $this->assertInstanceOf('\PhpBinaryReader\Type\Int64', $brBig->getInt64Reader()); $this->assertInstanceOf('\PhpBinaryReader\Type\Int8', $brBig->getInt8Reader()); $this->assertInstanceOf('\PhpBinaryReader\Type\Str', $brBig->getStringReader()); $this->assertInstanceOf('\PhpBinaryReader\Type\Single', $brBig->getSingleReader()); diff --git a/test/Type/Int64Test.php b/test/Type/Int64Test.php new file mode 100644 index 0000000..f87db60 --- /dev/null +++ b/test/Type/Int64Test.php @@ -0,0 +1,119 @@ +int64 = new Int64(); + } + + /** + * @dataProvider binaryReaders + */ + public function testUnsignedReaderWithBigEndian($brBig, $brLittle) + { + $this->assertEquals(12885059444, $this->int64->read($brBig)); + $this->assertEquals(7310314309530157055, $this->int64->read($brBig)); + } + + /** + * @dataProvider binaryReaders + */ + public function testSignedReaderWithBigEndian($brBig, $brLittle) + { + $brBig->setPosition(12); + $this->assertEquals(-3229614080, $this->int64->readSigned($brBig)); + } + + /** + * @dataProvider binaryReaders + */ + public function testReaderWithLittleEndian($brBig, $brLittle) + { + $this->assertEquals(8387672839590772739, $this->int64->read($brLittle)); + $this->assertEquals(18446744069975864165, $this->int64->read($brLittle)); + } + + /** + * @dataProvider binaryReaders + */ + public function testSignedReaderWithLittleEndian($brBig, $brLittle) + { + $brLittle->setPosition(12); + $this->assertEquals(4575657225703391231, $this->int64->readSigned($brLittle)); + } + + /** + * @dataProvider binaryReaders + */ + public function testBitReaderWithBigEndian($brBig, $brLittle) + { + $brBig->setPosition(6); + $brBig->readBits(4); + $this->assertEquals(504403158265495567, $this->int64->read($brBig)); + } + + /** + * @dataProvider binaryReaders + */ + public function testBitReaderWithLittleEndian($brBig, $brLittle) + { + $brLittle->setPosition(6); + $brLittle->readBits(4); + $this->assertEquals(504403158265495567, $this->int64->read($brLittle)); + } + + /** + * @expectedException \OutOfBoundsException + * @dataProvider binaryReaders + */ + public function testOutOfBoundsExceptionIsThrownWithBigEndian($brBig, $brLittle) + { + $brBig->readBits(360); + $this->int64->read($brBig); + } + + /** + * @expectedException \OutOfBoundsException + * @dataProvider binaryReaders + */ + public function testOutOfBoundsExceptionIsThrownWithLittleEndian($brBig, $brLittle) + { + $brLittle->readBits(360); + $this->int64->read($brLittle); + } + + /** + * @dataProvider binaryReaders + */ + public function testAlternateMachineByteOrderSigned($brBig, $brLittle) + { + $brLittle->setMachineByteOrder(Endian::ENDIAN_BIG); + $brLittle->setEndian(Endian::ENDIAN_LITTLE); + $this->assertEquals(8387672839590772739, $this->int64->readSigned($brLittle)); + } + + public function testEndian() + { + $this->int64->setEndianBig('X'); + $this->assertEquals('X', $this->int64->getEndianBig()); + + $this->int64->setEndianLittle('Y'); + $this->assertEquals('Y', $this->int64->getEndianLittle()); + } + +}