diff --git a/src/CBOR/CBOR.php b/src/CBOR/CBOR.php index 5c0224b..d1867ba 100644 --- a/src/CBOR/CBOR.php +++ b/src/CBOR/CBOR.php @@ -11,6 +11,7 @@ namespace ATProto\Core\CBOR; +use ATProto\Core\CBOR\MajorTypes\TextString; use ATProto\Core\CBOR\MajorTypes\UnsignedInteger; class CBOR @@ -21,13 +22,24 @@ public static function encode(string|int|array $data): string case 'integer': return UnsignedInteger::encode($data); break; + case 'string': + return TextString::encode($data); + break; } throw new \ValueError("Unsupported type: " . gettype($data)); } - public static function decode(string $data): int + public static function decode(string $data): int|string { - return UnsignedInteger::decode((string) $data); + if (TextString::validate($data)) { + return TextString::decode($data); + } + + if (UnsignedInteger::validate($data)) { + return UnsignedInteger::decode($data); + } + + throw new \ValueError("Unsupported type."); } } diff --git a/src/CBOR/MajorTypes/TextString.php b/src/CBOR/MajorTypes/TextString.php new file mode 100644 index 0000000..69fbdd8 --- /dev/null +++ b/src/CBOR/MajorTypes/TextString.php @@ -0,0 +1,75 @@ +> 5) & 0x07) === 0x03; + } +} diff --git a/src/CBOR/MajorTypes/UnsignedInteger.php b/src/CBOR/MajorTypes/UnsignedInteger.php index aef69b7..e8a712a 100644 --- a/src/CBOR/MajorTypes/UnsignedInteger.php +++ b/src/CBOR/MajorTypes/UnsignedInteger.php @@ -17,14 +17,12 @@ class UnsignedInteger { public static function decode(string $data): int { - $firstByte = ord($data[0]); - $majorType = ($firstByte >> 5) & 0x07; - $additionalInfo = $firstByte & 0x1F; - - if ($majorType !== 0) { - throw new ValueError("Invalid major type for unsigned integer: $majorType"); + if (! self::validate($data)) { + throw new ValueError("Invalid major type for unsigned integer."); } + $additionalInfo = ord($data[0]) & 0x1F; + if ($additionalInfo <= 23) { $value = $additionalInfo; } elseif ($additionalInfo === 24) { @@ -73,4 +71,9 @@ public static function encode(int $value): string return $prefixedPack('J', "\x1B"); } + + public static function validate(string $input): bool + { + return ((ord($input[0]) >> 5) & 0x07) === 0x00; + } } diff --git a/tests/Unit/CBOR/CBORTest.php b/tests/Unit/CBOR/CBORTest.php index 2e21ea6..d1adbf9 100644 --- a/tests/Unit/CBOR/CBORTest.php +++ b/tests/Unit/CBOR/CBORTest.php @@ -18,20 +18,36 @@ class CBORTest extends TestCase { #[DataProvider('validCases')] - public function testEncode(int $data, string $expected): void + public function testEncode(int|string $data, string $expected): void { $encoded = CBOR::encode($data); $this->assertSame($expected, $encoded); } #[DataProvider('validCases')] - public function testDecode(int $expected, string $data): void + public function testDecode(int|string $expected, string $data): void { $actual = CBOR::decode($data); $this->assertSame($expected, $actual); } + public function testCBORDecodeThrowsExceptionWhenPassedUnsupportedType(): void + { + $this->expectException(\ValueError::class); + $this->expectExceptionMessage('Unsupported type.'); + + CBOR::decode("\x80"); // CBOR array + } + + public function testCBOREncodeThrowsExceptionWhenPassedUnsupportedType(): void + { + $this->expectException(\ValueError::class); + $this->expectExceptionMessage('Unsupported type: array'); + + CBOR::encode(array()); + } + /** * @return array[] */ @@ -42,8 +58,8 @@ public static function validCases(): array [1, hex2bin('01')], // 1 encoded as CBOR unsigned integer [10, hex2bin('0a')], // 10 encoded as CBOR unsigned integer -// // String test cases -// [['hello'], hex2bin('6568656c6c6f')], // "hello" encoded as CBOR text string + // String test cases + ['hello', hex2bin('6568656C6C6F')], // "hello" encoded as CBOR text string // // // Boolean test cases // [[true], hex2bin('f5')], // true encoded as CBOR special type diff --git a/tests/Unit/CBOR/MajorTypes/TextStringTest.php b/tests/Unit/CBOR/MajorTypes/TextStringTest.php new file mode 100644 index 0000000..fb34fd9 --- /dev/null +++ b/tests/Unit/CBOR/MajorTypes/TextStringTest.php @@ -0,0 +1,87 @@ +assertSame($expected, $actual); + } + + #[DataProvider('provideValidCases')] + public function testDecodeExtractsOriginalStringFromCBORRepresentation(string $input, string $header): void + { + $encoded = $header . $input; + + $actual = TextString::decode($encoded); + $expected = $input; + + $this->assertSame($expected, $actual); + } + + public static function provideValidCases(): array + { + return [ + ["f", "\x61"], + ["fo", "\x62"], + ["foo", "\x63"], + ["foob", "\x64"], + ["fooba", "\x65"], + ["foobar", "\x66"], + [ + "This is a longer string. This is a longer string. This is a longer string. This is a longer string.", + "\x78\x63" + ], + [ + "This is a longer string. This is a longer string. This is a longer string. This is a longer string. + This is a longer string. This is a longer string. This is a longer string. This is a longer string. + This is a longer string. This is a longer string. This is a longer string. This is a longer string.", + "\x79\x01\x4D" + ] + ]; + } + + public function testDecodeThrowsExceptionForInvalidMajorType(): void + { + $this->expectException(\ValueError::class); + $this->expectExceptionMessage("Invalid CBOR TextString major type."); + + TextString::decode("\x0C"); + } + + public function testDecodeThrowsExceptionForLengthMismatch(): void + { + $this->expectException(\ValueError::class); + $this->expectExceptionMessage("Invalid CBOR TextString length mismatch."); + + // Encoded length is 5, but actual length is 4 + TextString::decode("\x65\x66\x6F\x6F\x62"); + } + + public function testDecodeThrowsExceptionForInvalidAdditionalInformation(): void + { + $this->expectException(\ValueError::class); + $this->expectExceptionMessage("Invalid CBOR TextString length information."); + + // Additional info 28 is invalid for text strings + TextString::decode("\x7C\x01"); + } +} diff --git a/tests/Unit/CBOR/MajorTypes/UnsignedIntegerTest.php b/tests/Unit/CBOR/MajorTypes/UnsignedIntegerTest.php index cc41ff6..e725853 100644 --- a/tests/Unit/CBOR/MajorTypes/UnsignedIntegerTest.php +++ b/tests/Unit/CBOR/MajorTypes/UnsignedIntegerTest.php @@ -47,7 +47,7 @@ public function testEncodeThrowsAnExceptionForValueGreaterThanIntMax(): void public function testDecodeThrowsAnExceptionWhenPassedInvalidValue(string $case): void { $this->expectException(\ValueError::class); - $this->expectExceptionMessage("Invalid major type for unsigned integer: "); + $this->expectExceptionMessage("Invalid major type for unsigned integer."); UnsignedInteger::decode($case); }