Skip to content

Commit 307049b

Browse files
authored
Merge pull request #9 from petrknap/byter
Implemented `Byter`
2 parents d196bd9 + e45d925 commit 307049b

File tree

9 files changed

+203
-15
lines changed

9 files changed

+203
-15
lines changed

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Library for work with binary data and objects
22

33
Simple library for work with binary data and objects in PHP.
4-
See the examples below for more information, or check out [`Encoder`](./src/Encoder.php), [`Decoder`](./src/Decoder.php) and [`Serializer`](./src/Serializer.php).
4+
See the examples below for more information, or check out [`Encoder`](./src/Encoder.php), [`Decoder`](./src/Decoder.php), [`Serializer`](./src/Serializer.php) and [`Byter`](./src/Byter.php).
55

66
```php
77
use PetrKnap\Binary\Binary;
@@ -26,6 +26,22 @@ $unserialized = Binary::unserialize($serialized);
2626
printf('Data was serialized into `%s` %s.', base64_encode($serialized), $unserialized === $data ? 'successfully' : 'unsuccessfully');
2727
```
2828

29+
```php
30+
use PetrKnap\Binary\Binary;
31+
32+
$data = base64_decode('hmlpFnFwbchsoQARSibVpfbWVfuwAHLbGxjFl9eC8fiGaWkWcXBtyGyhABFKJtWl9tZV+7AActsbGMWX14Lx+A==');
33+
$sha1 = sha1($data, binary: true);
34+
$md5 = md5($data, binary: true);
35+
$unbitten = Binary::unbite($sha1, $md5, $data);
36+
[$sha1Bite, $md5Bite, $dataBite] = Binary::bite($unbitten, 20, 16);
37+
38+
printf(
39+
'Hashes and data was unbitten into `%s` %s.',
40+
base64_encode($unbitten),
41+
$sha1Bite === $sha1 && $md5Bite === $md5 && $dataBite === $data ? 'successfully' : 'unsuccessfully',
42+
);
43+
```
44+
2945
---
3046

3147
Run `composer require petrknap/binary` to install it.

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
},
6666
"suggest": {
6767
"ext-igbinary": "Required to serialize data via igbinary",
68-
"ext-mbstring": "Required to check checksum",
68+
"ext-mbstring": "Required to bite bytes",
6969
"ext-zlib": "Required to compress data"
7070
}
7171
}

src/Binary.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,22 @@ public static function unserialize(string $data): mixed
2525
{
2626
return (new Serializer())->unserialize(serialized: $data);
2727
}
28+
29+
/**
30+
* @see Byter::bite()
31+
*
32+
* @return array<string>
33+
*/
34+
public static function bite(string $data, int $size1, int ...$sizeN): array
35+
{
36+
return (new Byter())->bite($data, $size1, ...$sizeN);
37+
}
38+
39+
/**
40+
* @see Byter::unbite()
41+
*/
42+
public static function unbite(string $bite1, string ...$biteN): string
43+
{
44+
return (new Byter())->unbite($bite1, ...$biteN);
45+
}
2846
}

src/Byter.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PetrKnap\Binary;
6+
7+
use PetrKnap\Shorts\HasRequirements;
8+
use RuntimeException;
9+
10+
class Byter
11+
{
12+
use HasRequirements;
13+
14+
private const ENCODING = '8bit';
15+
16+
public function __construct()
17+
{
18+
self::checkRequirements(
19+
functions: [
20+
'mb_strcut',
21+
'mb_strlen',
22+
],
23+
);
24+
}
25+
26+
/**
27+
* @param int $size1 size of bite in bytes; if negative, bites from the end
28+
*
29+
* @return array<string> bites of specified sizes; and remains, if any
30+
*
31+
* @throws Exception\CouldNotBiteData
32+
*/
33+
public function bite(string $data, int $size1, int ...$sizeN): array
34+
{
35+
$remains = $data;
36+
$bites = [];
37+
foreach ([$size1, ...$sizeN] as $size) {
38+
if (abs($size) > $this->size($remains)) {
39+
throw new Exception\CouldNotBiteData(__METHOD__, $data, new RuntimeException(
40+
'Remains are smaller than bite',
41+
));
42+
}
43+
$bite = mb_strcut($remains, 0, $size, encoding: self::ENCODING);
44+
$remains = mb_strcut($remains, $size, encoding: self::ENCODING);
45+
if ($size < 0) {
46+
$bites[] = $remains;
47+
$remains = $bite;
48+
} else {
49+
$bites[] = $bite;
50+
}
51+
}
52+
if ($remains !== '') {
53+
$bites[] = $remains;
54+
}
55+
return $bites;
56+
}
57+
58+
/**
59+
* Backward version of {@see self::bite()}
60+
*
61+
* @link https://en.wikipedia.org/wiki/Backwards_(Red_Dwarf)
62+
*/
63+
public function unbite(string $bite1, string ...$biteN): string
64+
{
65+
return implode([$bite1, ...$biteN]);
66+
}
67+
68+
/**
69+
* @return int size in bytes
70+
*/
71+
public function size(string $data): int
72+
{
73+
return mb_strlen($data, encoding: self::ENCODING);
74+
}
75+
}

src/Coder/Checksum.php

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace PetrKnap\Binary\Coder;
66

7-
use PetrKnap\Shorts\HasRequirements;
7+
use PetrKnap\Binary\Byter;
88

99
/**
1010
* @see hash()
@@ -13,20 +13,14 @@
1313
*/
1414
final class Checksum extends Coder
1515
{
16-
use HasRequirements;
17-
1816
public const ALGORITHM = 'crc32';
1917

2018
private string $algorithm;
19+
private readonly Byter $byter;
2120

2221
public function __construct()
2322
{
24-
self::checkRequirements(
25-
functions: [
26-
'mb_strlen',
27-
'mb_strcut',
28-
],
29-
);
23+
$this->byter = new Byter();
3024
}
3125

3226
public function encode(string $decoded, ?string $algorithm = null): string
@@ -44,14 +38,13 @@ public function decode(string $encoded, ?string $algorithm = null): string
4438
protected function doEncode(string $decoded): string
4539
{
4640
$checksum = hash($this->algorithm, $decoded, binary: true);
47-
return $decoded . $checksum;
41+
return $this->byter->unbite($decoded, $checksum);
4842
}
4943

5044
protected function doDecode(string $encoded): string
5145
{
52-
$checksumLength = mb_strlen(hash($this->algorithm, '', binary: true), encoding: '8bit');
53-
$dataLength = mb_strlen($encoded, encoding: '8bit') - $checksumLength;
54-
$decoded = mb_strcut($encoded, 0, $dataLength, encoding: '8bit');
46+
$checksumLength = $this->byter->size(hash($this->algorithm, '', binary: true));
47+
[,$decoded] = $this->byter->bite($encoded, -$checksumLength);
5548
if ($this->doEncode($decoded) !== $encoded) {
5649
throw new Exception\CouldNotDecodeData(__METHOD__, $encoded);
5750
}

src/Exception/ByterException.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PetrKnap\Binary\Exception;
6+
7+
interface ByterException extends BinaryException
8+
{
9+
}

src/Exception/CouldNotBiteData.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PetrKnap\Binary\Exception;
6+
7+
use PetrKnap\Shorts\Exception\CouldNotProcessData;
8+
9+
/**
10+
* @extends CouldNotProcessData<string>
11+
*/
12+
final class CouldNotBiteData extends CouldNotProcessData implements ByterException
13+
{
14+
}

tests/ByterTest.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace PetrKnap\Binary;
4+
5+
use PHPUnit\Framework\Attributes\DataProvider;
6+
use PHPUnit\Framework\TestCase;
7+
8+
final class ByterTest extends TestCase
9+
{
10+
#[DataProvider('dataBitesData')]
11+
public function testBitesData(array $expected, array $sizes): void
12+
{
13+
self::assertSame(
14+
$expected,
15+
(new Byter())->bite(self::getData(), ...$sizes),
16+
);
17+
}
18+
19+
public static function dataBitesData(): array
20+
{
21+
return [
22+
'one left bite' => [[hex2bin('0102'), hex2bin('030405')], [2]],
23+
'one right bite' => [[hex2bin('0405'), hex2bin('010203')], [-2]],
24+
'empty bite' => [['', hex2bin('0102030405')], [0]],
25+
'full bite' => [[hex2bin('0102030405')], [5]],
26+
'many bites' => [[hex2bin('0102'), hex2bin('0405'), '', hex2bin('03')], [2, -2, 0, 1]],
27+
];
28+
}
29+
30+
public function testBiteThrowsWhenThereIsNotEnoughData(): void
31+
{
32+
self::expectException(Exception\CouldNotBiteData::class);
33+
34+
$data = self::getData();
35+
$byter = new Byter();
36+
$byter->bite(
37+
$data,
38+
$byter->size($data) + 1,
39+
);
40+
}
41+
42+
public function testUnbitesBites(): void
43+
{
44+
self::assertSame(
45+
self::getData(),
46+
(new Byter())->unbite(hex2bin('0102'), hex2bin('030405')),
47+
);
48+
}
49+
50+
public function testReturnsSizeOfData(): void
51+
{
52+
self::assertSame(
53+
5,
54+
(new Byter())->size(self::getData()),
55+
);
56+
}
57+
58+
private static function getData(): string
59+
{
60+
return hex2bin('0102030405');
61+
}
62+
}

tests/ReadmeTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public static function getExpectedOutputsOfPhpExamples(): iterable
2020
return [
2121
'coder' => 'Data was coded into `a8vMFCssyD2Rs5BB0Evt6tJv10J_b2Aoui0tcXT69aaPP9oIyB-fLeAHAA` successfully.',
2222
'serializer' => 'Data was serialized into `S7QysqoutjKxUiqpLEhVsi62srRSysxNTE/VL8hLB/GBUimJJYkgpoWxlVJngJ87L5cUFwMDA6+nh0sQkGYEYQ42ICkveqQTxCkOcndiWHdO5iVYlYtjiER48o/9Ux7aM7C9Z1qixnnFBCjB4Onq57LOKaFJyboWAA==` successfully.',
23+
'byter' => 'Hashes and data was unbitten into `IoPwxcGHZQM0gfF966vHI3kleehoRKHtC32Xh30RDlg5E026hmlpFnFwbchsoQARSibVpfbWVfuwAHLbGxjFl9eC8fiGaWkWcXBtyGyhABFKJtWl9tZV+7AActsbGMWX14Lx+A==` successfully.',
2324
];
2425
}
2526
}

0 commit comments

Comments
 (0)