Skip to content

Commit 91451cc

Browse files
committed
Finish implementation in BinaryReader, Implement in BinaryWriter
1 parent 15a3a03 commit 91451cc

File tree

6 files changed

+494
-9
lines changed

6 files changed

+494
-9
lines changed

src/BinaryReader.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,15 @@ public function readBytesWithLength(bool $use16BitLength = false): BinaryString
9292

9393
public function readInt(IntType $type): int
9494
{
95-
$bytesCount = $type->bytes();
96-
if ($bytesCount > PHP_INT_SIZE) {
95+
if (!$type->isSupported()) {
96+
// @codeCoverageIgnoreStart
9797
throw new RuntimeException(
98-
sprintf('Cannot read %d-byte integers on %d-byte platform', $bytesCount, PHP_INT_SIZE)
98+
sprintf('Cannot read %d-byte integers on %d-byte platform', $type->bytes(), PHP_INT_SIZE)
9999
);
100+
// @codeCoverageIgnoreEnd
100101
}
101102

103+
$bytesCount = $type->bytes();
102104
$bytes = $this->readBytes($bytesCount)->value;
103105

104106
if ($type->isLittleEndian()) {
@@ -123,6 +125,12 @@ public function readInt(IntType $type): int
123125
return $value;
124126
}
125127

128+
#[\Deprecated('Use readInt(IntType::UINT16) instead')]
129+
public function readUint16BE(): int
130+
{
131+
return $this->readInt(IntType::UINT16);
132+
}
133+
126134
public function readString(int $length): BinaryString
127135
{
128136
$bytes = $this->readBytes($length);

src/BinaryWriter.php

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,48 @@ public function writeBytesWithLength(BinaryString $bytes, bool $use16BitLength =
5858
return $this;
5959
}
6060

61-
public function writeUint16BE(int $value): self
61+
public function writeInt(IntType $type, int $value): self
6262
{
63-
if ($value < 0 || $value > 65535) {
64-
throw new \InvalidArgumentException('Uint16 value must be between 0 and 65535');
63+
if (!$type->isSupported()) {
64+
// @codeCoverageIgnoreStart
65+
throw new \RuntimeException(
66+
sprintf('Cannot write %d-byte integers on %d-byte platform', $type->bytes(), PHP_INT_SIZE)
67+
);
68+
// @codeCoverageIgnoreEnd
69+
}
70+
71+
$bytesCount = $type->bytes();
72+
73+
if (!$type->isValid($value)) {
74+
throw new \InvalidArgumentException(
75+
sprintf('Value %d is out of range for %s', $value, $type->name)
76+
);
77+
}
78+
79+
// Handle negative values for signed types
80+
if ($type->isSigned() && $value < 0) {
81+
$value = (1 << ($bytesCount * 8)) + $value;
82+
}
83+
84+
$bytes = '';
85+
for ($i = $bytesCount - 1; $i >= 0; $i--) {
86+
$bytes .= chr(($value >> ($i * 8)) & 0xFF);
87+
}
88+
89+
if ($type->isLittleEndian()) {
90+
$bytes = strrev($bytes);
6591
}
6692

67-
$this->buffer .= chr(($value >> 8) & 0xFF);
68-
$this->buffer .= chr($value & 0xFF);
93+
$this->buffer .= $bytes;
6994
return $this;
7095
}
7196

97+
#[\Deprecated('Use writeInt(IntType::UINT16, $value) instead')]
98+
public function writeUint16BE(int $value): self
99+
{
100+
return $this->writeInt(IntType::UINT16, $value);
101+
}
102+
72103
public function writeString(BinaryString $string): self
73104
{
74105
if (!mb_check_encoding($string->value, 'UTF-8')) {

src/IntType.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,44 @@ public function isLittleEndian(): bool
6565
default => false,
6666
};
6767
}
68+
69+
public function isSupported(): bool
70+
{
71+
return $this->bytes() <= PHP_INT_SIZE;
72+
}
73+
74+
public function minValue(): int
75+
{
76+
if ($this->isSigned()) {
77+
$bits = $this->bytes() * 8;
78+
if ($bits >= PHP_INT_SIZE * 8) {
79+
return PHP_INT_MIN;
80+
}
81+
return -(1 << ($bits - 1));
82+
}
83+
84+
return 0;
85+
}
86+
87+
public function maxValue(): int
88+
{
89+
$bits = $this->bytes() * 8;
90+
91+
if ($this->isSigned()) {
92+
if ($bits >= PHP_INT_SIZE * 8) {
93+
return PHP_INT_MAX;
94+
}
95+
return (1 << ($bits - 1)) - 1;
96+
} else {
97+
if ($bits >= PHP_INT_SIZE * 8) {
98+
return PHP_INT_MAX;
99+
}
100+
return (1 << $bits) - 1;
101+
}
102+
}
103+
104+
public function isValid(int $value): bool
105+
{
106+
return $value >= $this->minValue() && $value <= $this->maxValue();
107+
}
68108
}

tests/BinaryReaderTest.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,29 @@ public function testReadInt(int $expected, string $payload, IntType $type): void
131131
$this->assertSame(strlen($payload), $this->reader->position);
132132
}
133133

134+
public function testReadIntUnsupportedSize(): void
135+
{
136+
// This test only runs on 32-bit systems where 64-bit integers are not supported
137+
if (PHP_INT_SIZE >= 8) {
138+
$this->markTestSkipped('64-bit integers are supported on this platform');
139+
}
140+
141+
$this->reader = new BinaryReader(BinaryString::fromString("\x01\x23\x45\x67\x89\xAB\xCD\xEF"));
142+
143+
$this->expectException(\RuntimeException::class);
144+
$this->expectExceptionMessage('Cannot read 8-byte integers on 4-byte platform');
145+
146+
$this->reader->readInt(IntType::UINT64);
147+
}
148+
149+
public function testReadUint16BEDeprecated(): void
150+
{
151+
$this->reader = new BinaryReader(BinaryString::fromString("\x04\xD2"));
152+
153+
$this->assertSame(1234, $this->reader->readUint16BE());
154+
$this->assertSame(2, $this->reader->position);
155+
}
156+
134157
public function testPeekByte()
135158
{
136159
$this->assertEquals(0x01, $this->reader->peekByte());
@@ -337,6 +360,28 @@ public function testGetPosition()
337360
$this->assertEquals(2, $this->reader->position);
338361
}
339362

363+
public function testSetPosition()
364+
{
365+
// Test direct position property assignment
366+
$this->reader->position = 2;
367+
$this->assertEquals(2, $this->reader->position);
368+
369+
// Test validation through direct assignment
370+
try {
371+
$this->reader->position = -1;
372+
$this->fail("Expected exception not thrown");
373+
} catch (\RuntimeException $exception) {
374+
$this->assertEquals('Invalid seek position: -1', $exception->getMessage());
375+
}
376+
377+
try {
378+
$this->reader->position = 5;
379+
$this->fail("Expected exception not thrown");
380+
} catch (\RuntimeException $exception) {
381+
$this->assertEquals('Invalid seek position: 5', $exception->getMessage());
382+
}
383+
}
384+
340385
public function testGetRemainingBytes()
341386
{
342387
$this->reader->seek(0);

tests/BinaryWriterTest.php

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
use KDuma\BinaryTools\BinaryString;
66
use KDuma\BinaryTools\BinaryWriter;
7+
use KDuma\BinaryTools\IntType;
78
use PHPUnit\Framework\Attributes\CoversClass;
9+
use PHPUnit\Framework\Attributes\DataProvider;
810
use PHPUnit\Framework\TestCase;
911

1012
#[CoversClass(BinaryWriter::class)]
@@ -63,7 +65,7 @@ public function testWriteUint16BE()
6365
$this->writer->writeUint16BE(65535 + 1);
6466
$this->fail("Expected exception not thrown");
6567
} catch (\InvalidArgumentException $exception) {
66-
$this->assertEquals('Uint16 value must be between 0 and 65535', $exception->getMessage());
68+
$this->assertEquals('Value 65536 is out of range for UINT16', $exception->getMessage());
6769
$this->assertEquals(0, $this->writer->getLength());
6870
}
6971
}
@@ -179,4 +181,111 @@ public function testWriteString()
179181
$this->assertEquals(0, $this->writer->getLength());
180182
}
181183
}
184+
185+
public static function writeIntProvider(): iterable
186+
{
187+
yield 'uint8 zero' => [0, "\x00", IntType::UINT8];
188+
yield 'uint8 max' => [255, "\xFF", IntType::UINT8];
189+
yield 'int8 positive' => [127, "\x7F", IntType::INT8];
190+
yield 'int8 negative' => [-1, "\xFF", IntType::INT8];
191+
yield 'int8 min' => [-128, "\x80", IntType::INT8];
192+
yield 'uint16 positive' => [1234, "\x04\xD2", IntType::UINT16];
193+
yield 'uint16 little endian positive' => [1234, "\xD2\x04", IntType::UINT16_LE];
194+
yield 'int16 positive' => [1234, "\x04\xD2", IntType::INT16];
195+
yield 'int16 little endian positive' => [1234, "\xD2\x04", IntType::INT16_LE];
196+
yield 'int16 negative' => [-1234, "\xFB\x2E", IntType::INT16];
197+
yield 'int16 little endian negative' => [-1234, "\x2E\xFB", IntType::INT16_LE];
198+
yield 'uint32 positive' => [0xDEADBEEF, "\xDE\xAD\xBE\xEF", IntType::UINT32];
199+
yield 'uint32 little endian positive' => [0xDEADBEEF, "\xEF\xBE\xAD\xDE", IntType::UINT32_LE];
200+
yield 'int32 positive' => [1234, "\x00\x00\x04\xD2", IntType::INT32];
201+
yield 'int32 little endian positive' => [1234, "\xD2\x04\x00\x00", IntType::INT32_LE];
202+
yield 'int32 negative' => [-1234, "\xFF\xFF\xFB\x2E", IntType::INT32];
203+
yield 'int32 little endian negative' => [-1234, "\x2E\xFB\xFF\xFF", IntType::INT32_LE];
204+
yield 'uint64 positive' => [0x0123456789ABCDEF, "\x01\x23\x45\x67\x89\xAB\xCD\xEF", IntType::UINT64];
205+
yield 'uint64 little endian positive' => [0x0123456789ABCDEF, "\xEF\xCD\xAB\x89\x67\x45\x23\x01", IntType::UINT64_LE];
206+
yield 'int64 positive' => [1234, "\x00\x00\x00\x00\x00\x00\x04\xD2", IntType::INT64];
207+
yield 'int64 little endian positive' => [1234, "\xD2\x04\x00\x00\x00\x00\x00\x00", IntType::INT64_LE];
208+
yield 'int64 negative' => [-1234, "\xFF\xFF\xFF\xFF\xFF\xFF\xFB\x2E", IntType::INT64];
209+
yield 'int64 little endian negative' => [-1234, "\x2E\xFB\xFF\xFF\xFF\xFF\xFF\xFF", IntType::INT64_LE];
210+
}
211+
212+
/**
213+
* @dataProvider writeIntProvider
214+
*/
215+
public function testWriteInt(int $value, string $expected, IntType $type): void
216+
{
217+
if (!$type->isSupported()) {
218+
$this->markTestSkipped(sprintf('IntType %s is not supported on this platform', $type->name));
219+
}
220+
221+
$this->writer->reset();
222+
$this->writer->writeInt($type, $value);
223+
$this->assertEquals($expected, $this->writer->getBuffer()->toString());
224+
}
225+
226+
public function testWriteIntUnsupportedType(): void
227+
{
228+
// This test only runs on 32-bit systems where 64-bit integers are not supported
229+
if (PHP_INT_SIZE >= 8) {
230+
$this->markTestSkipped('64-bit integers are supported on this platform');
231+
}
232+
233+
$this->writer->reset();
234+
235+
$this->expectException(\RuntimeException::class);
236+
$this->expectExceptionMessage('Cannot write 8-byte integers on 4-byte platform');
237+
238+
$this->writer->writeInt(IntType::UINT64, 1234);
239+
}
240+
241+
public function testWriteIntOutOfRange(): void
242+
{
243+
$this->writer->reset();
244+
245+
// Test uint8 overflow
246+
$this->expectException(\InvalidArgumentException::class);
247+
$this->expectExceptionMessage('Value 256 is out of range for UINT8');
248+
249+
$this->writer->writeInt(IntType::UINT8, 256);
250+
}
251+
252+
public function testWriteIntNegativeUnsigned(): void
253+
{
254+
$this->writer->reset();
255+
256+
// Test negative value for unsigned type
257+
$this->expectException(\InvalidArgumentException::class);
258+
$this->expectExceptionMessage('Value -1 is out of range for UINT8');
259+
260+
$this->writer->writeInt(IntType::UINT8, -1);
261+
}
262+
263+
public function testWriteIntSignedOverflow(): void
264+
{
265+
$this->writer->reset();
266+
267+
// Test int8 overflow
268+
$this->expectException(\InvalidArgumentException::class);
269+
$this->expectExceptionMessage('Value 128 is out of range for INT8');
270+
271+
$this->writer->writeInt(IntType::INT8, 128);
272+
}
273+
274+
public function testWriteIntSignedUnderflow(): void
275+
{
276+
$this->writer->reset();
277+
278+
// Test int8 underflow
279+
$this->expectException(\InvalidArgumentException::class);
280+
$this->expectExceptionMessage('Value -129 is out of range for INT8');
281+
282+
$this->writer->writeInt(IntType::INT8, -129);
283+
}
284+
285+
public function testWriteUint16BEDeprecated(): void
286+
{
287+
$this->writer->reset();
288+
$this->writer->writeUint16BE(1234);
289+
$this->assertEquals("\x04\xD2", $this->writer->getBuffer()->toString());
290+
}
182291
}

0 commit comments

Comments
 (0)