Skip to content

Commit 7e6c078

Browse files
committed
Add Int64 type, methods ReadInt64() and ReadUInt64() and PHP unit tests.
64 bit integer reading uses bcmath functions as whilst PHP has added support for 64 bit integers, a lot of standard functions such as unpack use 32 bit signed integers internally.
1 parent 23e08d5 commit 7e6c078

5 files changed

Lines changed: 290 additions & 0 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ Methods
5757

5858
**readInt32()** returns a 32-bit signed integer
5959

60+
**readUInt64()** returns a 64-bit unsigned integer
61+
62+
**readInt64()** returns a 64-bit signed integer
63+
6064
**readSingle()** returns a 4-bytes floating-point
6165

6266
**readUBits($length)** returns a variable length of bits (unsigned)

src/BinaryReader.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PhpBinaryReader\Type\Int8;
99
use PhpBinaryReader\Type\Int16;
1010
use PhpBinaryReader\Type\Int32;
11+
use PhpBinaryReader\Type\Int64;
1112
use PhpBinaryReader\Type\Single;
1213
use PhpBinaryReader\Type\Str;
1314

@@ -78,6 +79,11 @@ class BinaryReader
7879
*/
7980
private $int32Reader;
8081

82+
/**
83+
* @var \PhpBinaryReader\Type\Int64
84+
*/
85+
private $int64Reader;
86+
8187
/**
8288
* @var \PhpBinaryReader\Type\Single
8389
*/
@@ -109,6 +115,7 @@ public function __construct($input, $endian = Endian::ENDIAN_LITTLE)
109115
$this->int8Reader = new Int8();
110116
$this->int16Reader = new Int16();
111117
$this->int32Reader = new Int32();
118+
$this->int64Reader = new Int64();
112119
$this->singleReader = new Single();
113120
}
114121

@@ -213,6 +220,22 @@ public function readUInt32()
213220
return $this->int32Reader->read($this);
214221
}
215222

223+
/**
224+
* @return int
225+
*/
226+
public function readInt64()
227+
{
228+
return $this->int64Reader->readSigned($this);
229+
}
230+
231+
/**
232+
* @return int
233+
*/
234+
public function readUInt64()
235+
{
236+
return $this->int64Reader->read($this);
237+
}
238+
216239
/**
217240
* @return float
218241
*/
@@ -435,6 +458,14 @@ public function getInt32Reader()
435458
return $this->int32Reader;
436459
}
437460

461+
/**
462+
* @return \PhpBinaryReader\Type\Int64
463+
*/
464+
public function getInt64Reader()
465+
{
466+
return $this->int64Reader;
467+
}
468+
438469
/**
439470
* @return \PhpBinaryReader\Type\Single
440471
*/

src/Type/Int64.php

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
3+
namespace PhpBinaryReader\Type;
4+
5+
use PhpBinaryReader\BinaryReader;
6+
use PhpBinaryReader\BitMask;
7+
use PhpBinaryReader\Endian;
8+
9+
class Int64 implements TypeInterface
10+
{
11+
/**
12+
* @var string
13+
*/
14+
private $endianBig = 'N';
15+
16+
/**
17+
* @var string
18+
*/
19+
private $endianLittle = 'V';
20+
21+
/**
22+
* Returns an Unsigned 64-bit Integer
23+
*
24+
* @param \PhpBinaryReader\BinaryReader $br
25+
* @param null $length
26+
* @return int
27+
* @throws \OutOfBoundsException
28+
*/
29+
public function read(BinaryReader &$br, $length = null)
30+
{
31+
if (!$br->canReadBytes(8)) {
32+
throw new \OutOfBoundsException('Cannot read 64-bit int, it exceeds the boundary of the file');
33+
}
34+
35+
$endian = $br->getEndian() == Endian::ENDIAN_BIG ? $this->endianBig : $this->endianLittle;
36+
$firstSegment = $br->readFromHandle(4);
37+
$secondSegment = $br->readFromHandle(4);
38+
39+
$firstHalf = unpack($endian, $firstSegment)[1];
40+
$secondHalf = unpack($endian, $secondSegment)[1];
41+
42+
if ($br->getEndian() == Endian::ENDIAN_BIG) {
43+
$value = bcadd($secondHalf, bcmul($firstHalf, "4294967296"));
44+
} else {
45+
$value = bcadd($firstHalf, bcmul($secondHalf, "4294967296"));
46+
}
47+
48+
if ($br->getCurrentBit() != 0) {
49+
$value = $this->bitReader($br, $value);
50+
}
51+
52+
return $value;
53+
}
54+
55+
/**
56+
* Returns a Signed 64-Bit Integer
57+
*
58+
* @param \PhpBinaryReader\BinaryReader $br
59+
* @return int
60+
*/
61+
public function readSigned(&$br)
62+
{
63+
$value = $this->read($br);
64+
if (bccomp($value, bcpow(2, 63)) >= 0) {
65+
$value = bcsub($value, bcpow(2, 64));
66+
}
67+
68+
return $value;
69+
}
70+
71+
/**
72+
* @param \PhpBinaryReader\BinaryReader $br
73+
* @param int $data
74+
* @return int
75+
*/
76+
private function bitReader(&$br, $data)
77+
{
78+
$bitmask = new BitMask();
79+
$loMask = $bitmask->getMask($br->getCurrentBit(), BitMask::MASK_LO);
80+
$hiMask = $bitmask->getMask($br->getCurrentBit(), BitMask::MASK_HI);
81+
$hiBits = ($br->getNextByte() & $hiMask) << 56;
82+
$miBits = ($data & 0xFFFFFFFFFFFFFF00) >> (8 - $br->getCurrentBit());
83+
$loBits = ($data & $loMask);
84+
$br->setNextByte($data & 0xFF);
85+
86+
return $hiBits | $miBits | $loBits;
87+
}
88+
89+
/**
90+
* @param string $endianBig
91+
*/
92+
public function setEndianBig($endianBig)
93+
{
94+
$this->endianBig = $endianBig;
95+
}
96+
97+
/**
98+
* @return string
99+
*/
100+
public function getEndianBig()
101+
{
102+
return $this->endianBig;
103+
}
104+
105+
/**
106+
* @param string $endianLittle
107+
*/
108+
public function setEndianLittle($endianLittle)
109+
{
110+
$this->endianLittle = $endianLittle;
111+
}
112+
113+
/**
114+
* @return string
115+
*/
116+
public function getEndianLittle()
117+
{
118+
return $this->endianLittle;
119+
}
120+
}

test/BinaryReaderTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,21 @@ public function testInt32($brBig, $brLittle)
114114
$this->assertEquals(3, $brLittle->readUInt32());
115115
}
116116

117+
/**
118+
* @dataProvider binaryReaders
119+
*/
120+
public function testInt64($brBig, $brLittle)
121+
{
122+
$this->assertEquals(12885059444, $brBig->readInt64());
123+
$this->assertEquals(8387672839590772739, $brLittle->readInt64());
124+
125+
$brLittle->setPosition(0);
126+
$brBig->setPosition(0);
127+
128+
$this->assertEquals(12885059444, $brBig->readUInt64());
129+
$this->assertEquals(8387672839590772739, $brLittle->readUInt64());
130+
}
131+
117132
/**
118133
* @param \PhpBinaryReader\BinaryReader $brBig
119134
* @param \PhpBinaryReader\BinaryReader $brLittle
@@ -321,6 +336,7 @@ public function testReaders()
321336
$this->assertInstanceOf('\PhpBinaryReader\Type\Byte', $brBig->getByteReader());
322337
$this->assertInstanceOf('\PhpBinaryReader\Type\Int16', $brBig->getInt16Reader());
323338
$this->assertInstanceOf('\PhpBinaryReader\Type\Int32', $brBig->getInt32Reader());
339+
$this->assertInstanceOf('\PhpBinaryReader\Type\Int64', $brBig->getInt64Reader());
324340
$this->assertInstanceOf('\PhpBinaryReader\Type\Int8', $brBig->getInt8Reader());
325341
$this->assertInstanceOf('\PhpBinaryReader\Type\Str', $brBig->getStringReader());
326342
$this->assertInstanceOf('\PhpBinaryReader\Type\Single', $brBig->getSingleReader());

test/Type/Int64Test.php

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?php
2+
3+
namespace PhpBinaryReader\Type;
4+
5+
use PhpBinaryReader\AbstractTestCase;
6+
use PhpBinaryReader\BinaryReader;
7+
use PhpBinaryReader\Endian;
8+
9+
/**
10+
* @coversDefaultClass \PhpBinaryReader\Type\Int64
11+
*/
12+
class Int64Test extends AbstractTestCase
13+
{
14+
/**
15+
* @var Int64
16+
*/
17+
public $int64;
18+
19+
public function setUp()
20+
{
21+
$this->int64 = new Int64();
22+
}
23+
24+
/**
25+
* @dataProvider binaryReaders
26+
*/
27+
public function testUnsignedReaderWithBigEndian($brBig, $brLittle)
28+
{
29+
$this->assertEquals(12885059444, $this->int64->read($brBig));
30+
$this->assertEquals(7310314309530157055, $this->int64->read($brBig));
31+
}
32+
33+
/**
34+
* @dataProvider binaryReaders
35+
*/
36+
public function testSignedReaderWithBigEndian($brBig, $brLittle)
37+
{
38+
$brBig->setPosition(12);
39+
$this->assertEquals(-3229614080, $this->int64->readSigned($brBig));
40+
}
41+
42+
/**
43+
* @dataProvider binaryReaders
44+
*/
45+
public function testReaderWithLittleEndian($brBig, $brLittle)
46+
{
47+
$this->assertEquals(8387672839590772739, $this->int64->read($brLittle));
48+
$this->assertEquals(18446744069975864165, $this->int64->read($brLittle));
49+
}
50+
51+
/**
52+
* @dataProvider binaryReaders
53+
*/
54+
public function testSignedReaderWithLittleEndian($brBig, $brLittle)
55+
{
56+
$brLittle->setPosition(12);
57+
$this->assertEquals(4575657225703391231, $this->int64->readSigned($brLittle));
58+
}
59+
60+
/**
61+
* @dataProvider binaryReaders
62+
*/
63+
public function testBitReaderWithBigEndian($brBig, $brLittle)
64+
{
65+
$brBig->setPosition(6);
66+
$brBig->readBits(4);
67+
$this->assertEquals(504403158265495567, $this->int64->read($brBig));
68+
}
69+
70+
/**
71+
* @dataProvider binaryReaders
72+
*/
73+
public function testBitReaderWithLittleEndian($brBig, $brLittle)
74+
{
75+
$brLittle->setPosition(6);
76+
$brLittle->readBits(4);
77+
$this->assertEquals(504403158265495567, $this->int64->read($brLittle));
78+
}
79+
80+
/**
81+
* @expectedException \OutOfBoundsException
82+
* @dataProvider binaryReaders
83+
*/
84+
public function testOutOfBoundsExceptionIsThrownWithBigEndian($brBig, $brLittle)
85+
{
86+
$brBig->readBits(360);
87+
$this->int64->read($brBig);
88+
}
89+
90+
/**
91+
* @expectedException \OutOfBoundsException
92+
* @dataProvider binaryReaders
93+
*/
94+
public function testOutOfBoundsExceptionIsThrownWithLittleEndian($brBig, $brLittle)
95+
{
96+
$brLittle->readBits(360);
97+
$this->int64->read($brLittle);
98+
}
99+
100+
/**
101+
* @dataProvider binaryReaders
102+
*/
103+
public function testAlternateMachineByteOrderSigned($brBig, $brLittle)
104+
{
105+
$brLittle->setMachineByteOrder(Endian::ENDIAN_BIG);
106+
$brLittle->setEndian(Endian::ENDIAN_LITTLE);
107+
$this->assertEquals(8387672839590772739, $this->int64->readSigned($brLittle));
108+
}
109+
110+
public function testEndian()
111+
{
112+
$this->int64->setEndianBig('X');
113+
$this->assertEquals('X', $this->int64->getEndianBig());
114+
115+
$this->int64->setEndianLittle('Y');
116+
$this->assertEquals('Y', $this->int64->getEndianLittle());
117+
}
118+
119+
}

0 commit comments

Comments
 (0)