Skip to content

Commit cf46816

Browse files
committed
feat: Add ByteString implementation for CBOR Major Type 2
1 parent b1d6967 commit cf46816

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed

src/CBOR/MajorTypes/ByteString.php

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
namespace ATProto\Core\CBOR\MajorTypes;
4+
5+
use ValueError;
6+
7+
class ByteString
8+
{
9+
public const int MAJOR_TYPE = 0x02;
10+
11+
public static function validate(string $input): bool
12+
{
13+
return ((ord($input[0]) >> 5) & 0x07) === self::MAJOR_TYPE;
14+
}
15+
16+
public static function encode(string $input): string
17+
{
18+
$len = strlen($input);
19+
20+
if ($len <= 0x17) {
21+
$header = chr((self::MAJOR_TYPE << 5) | $len);
22+
} else if ($len <= 0xFF) {
23+
$header = chr((self::MAJOR_TYPE << 5) | 24) . chr($len);
24+
} else if ($len <= 0xFFFF) {
25+
$header = chr((self::MAJOR_TYPE << 5) | 25) . pack('n', $len);
26+
} else if ($len <= 0xFFFFFFFF) {
27+
$header = chr((self::MAJOR_TYPE << 5) | 26) . pack('N', $len);
28+
} else {
29+
$header = chr((self::MAJOR_TYPE << 5) | 27) . pack('J', $len);
30+
}
31+
32+
return $header . $input;
33+
}
34+
35+
public static function decode(string $input): string
36+
{
37+
if (! self::validate($input)) {
38+
throw new ValueError("Invalid CBOR ByteString major type.");
39+
}
40+
41+
$addInfo = ord($input[0]) & 0x1F;
42+
$offset = 1;
43+
44+
if ($addInfo <= 23) {
45+
$length = $addInfo;
46+
}
47+
48+
if ($addInfo === 24) {
49+
$length = ord($input[$offset]);
50+
$offset += 1;
51+
}
52+
53+
if ($addInfo === 25) {
54+
$length = unpack('n', substr($input, $offset, 2))[1];
55+
$offset += 2;
56+
}
57+
58+
if ($addInfo === 26) {
59+
$length = unpack('N', substr($input, $offset, 4))[1];
60+
$offset += 4;
61+
}
62+
63+
if ($addInfo === 27) {
64+
$length = unpack('J', substr($input, $offset, 8))[1];
65+
$offset += 8;
66+
}
67+
68+
if (! isset($length)) {
69+
throw new \ValueError('Invalid CBOR ByteString length information.');
70+
}
71+
72+
$text = substr($input, $offset, $length);
73+
74+
if (strlen($text) !== $length) {
75+
throw new \ValueError("Invalid CBOR ByteString length mismatch.");
76+
}
77+
78+
return $text;
79+
}
80+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
namespace Tests\Unit\CBOR\MajorTypes;
4+
5+
use ATProto\Core\CBOR\MajorTypes\ByteString;
6+
use PHPUnit\Framework\Attributes\DataProvider;
7+
use PHPUnit\Framework\TestCase;
8+
9+
class ByteStringTest extends TestCase
10+
{
11+
#[DataProvider('provideValidCases')]
12+
public function testValidate(string $input, string $header): void
13+
{
14+
$this->assertTrue(ByteString::validate($header . $input));
15+
}
16+
17+
public static function provideValidCases(): array
18+
{
19+
return [
20+
['f', "\x41"],
21+
['fo', "\x42"],
22+
['foo', "\x43"],
23+
['foob', "\x44"],
24+
['fooba', "\x45"],
25+
['foobar', "\x46"],
26+
[
27+
str_repeat("This is a longer string.", 4),
28+
"\x58\x60"
29+
],
30+
[
31+
str_repeat("This is a longer string.", 12),
32+
"\x59\x01\x20"
33+
],
34+
[
35+
str_repeat("This is a longer string.", 2731),
36+
"\x5A\x00\x01\x00\x08"
37+
],
38+
];
39+
}
40+
41+
#[DataProvider('provideValidCases')]
42+
public function testEncode(string $input, string $header): void
43+
{
44+
$expected = $header . $input;
45+
$actual = ByteString::encode($input);
46+
47+
$this->assertSame(bin2hex($expected), bin2hex($actual));
48+
}
49+
50+
#[DataProvider('provideValidCases')]
51+
public function testDecode(string $input, string $header): void
52+
{
53+
$actual = ByteString::decode($header . $input);
54+
55+
$this->assertSame($input, $actual);
56+
}
57+
58+
public function testDecodeThrowsExceptionForInvalidMajorType(): void
59+
{
60+
$this->expectException(\ValueError::class);
61+
$this->expectExceptionMessage("Invalid CBOR ByteString major type.");
62+
63+
ByteString::decode("\x0C");
64+
}
65+
66+
public function testDecodeThrowsExceptionForLengthMismatch(): void
67+
{
68+
$this->expectException(\ValueError::class);
69+
$this->expectExceptionMessage("Invalid CBOR ByteString length mismatch.");
70+
71+
ByteString::decode("\x45\x66\x6F\x6F\x62");
72+
}
73+
74+
public function testDecodeThrowsExceptionForInvalidAdditionalInformation(): void
75+
{
76+
$this->expectException(\ValueError::class);
77+
$this->expectExceptionMessage("Invalid CBOR ByteString length information.");
78+
79+
ByteString::decode("\x5F");
80+
}
81+
}

0 commit comments

Comments
 (0)