From a2dc3abcc25e323d6bea863fb906620e56811638 Mon Sep 17 00:00:00 2001 From: Eldar Shahmaliyev Date: Wed, 8 Jan 2025 20:18:36 +0400 Subject: [PATCH 1/5] (wip|[skip ci]): Add initial implementation for TextString encoding and tests - Implement `TextString` class with an `encode` method for CBOR text string encoding based on the length. - Placeholder added for the `decode` method (not yet implemented). - Create `TextStringTest` to validate encoding functionality with various test cases. - Include a data provider for encoding tests, covering short and long strings. Note: Decoding functionality is still pending implementation. --- src/CBOR/MajorTypes/TextString.php | 39 +++++++++++++ tests/Unit/CBOR/MajorTypes/TextStringTest.php | 57 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 src/CBOR/MajorTypes/TextString.php create mode 100644 tests/Unit/CBOR/MajorTypes/TextStringTest.php diff --git a/src/CBOR/MajorTypes/TextString.php b/src/CBOR/MajorTypes/TextString.php new file mode 100644 index 0000000..788b826 --- /dev/null +++ b/src/CBOR/MajorTypes/TextString.php @@ -0,0 +1,39 @@ +assertSame($expected, $actual); + } + + #[DataProvider('provideCases')] + public function testItCanDecodeCorrectly(string $case, string $header): void + { + $target = $header . $case; + + $actual = TextString::decode($target); + $expected = $case; + + $this->assertSame($expected, $actual); + } + + public static function provideCases(): 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. + 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" + ] + ]; + } +} From 3957912b822a620a6140eada29413a11d5f1a302 Mon Sep 17 00:00:00 2001 From: Eldar Shahmaliyev Date: Wed, 8 Jan 2025 20:20:46 +0400 Subject: [PATCH 2/5] (wip|[skip ci]): Integrate TextString encoding into CBOR class - Add support for encoding string data using the `TextString` class within the `CBOR::encode` method. - Update the CBOR class to handle the `string` type by delegating encoding to `TextString::encode`. - Keep the `decode` method as a placeholder for future implementation. --- src/CBOR/CBOR.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/CBOR/CBOR.php b/src/CBOR/CBOR.php index 5c0224b..a2efb26 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,6 +22,9 @@ 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)); @@ -28,6 +32,6 @@ public static function encode(string|int|array $data): string public static function decode(string $data): int { - return UnsignedInteger::decode((string) $data); + // TODO } } From 28ca64da2f89539e8c054bbfc889faaaf95cb89d Mon Sep 17 00:00:00 2001 From: Eldar Shahmaliyev Date: Thu, 9 Jan 2025 16:56:45 +0400 Subject: [PATCH 3/5] feat(CBOR): Implement decode method for TextString and improve test suite --- src/CBOR/MajorTypes/TextString.php | 38 ++++++++++++++- tests/Unit/CBOR/MajorTypes/TextStringTest.php | 46 +++++++++++++++---- 2 files changed, 72 insertions(+), 12 deletions(-) diff --git a/src/CBOR/MajorTypes/TextString.php b/src/CBOR/MajorTypes/TextString.php index 788b826..d155752 100644 --- a/src/CBOR/MajorTypes/TextString.php +++ b/src/CBOR/MajorTypes/TextString.php @@ -34,6 +34,40 @@ public static function encode(string $input): string public static function decode(string $input): string { - // TODO + $initialByte = ord($input[0]); + $majorType = ($initialByte >> 5) & 0x07; + + if ($majorType !== 0x03) { + throw new \ValueError('Invalid CBOR TextString major type.'); + } + + $additionalInfo = $initialByte & 0x1F; + $offset = 1; + + if ($additionalInfo <= 23) { + $length = $additionalInfo; + } elseif ($additionalInfo === 24) { + $length = ord($input[$offset]); + $offset += 1; + } elseif ($additionalInfo === 25) { + $length = unpack('n', substr($input, $offset, 2))[1]; + $offset += 2; + } elseif ($additionalInfo === 26) { + $length = unpack('N', substr($input, $offset, 4))[1]; + $offset += 4; + } elseif ($additionalInfo === 27) { + $length = unpack('J', substr($input, $offset, 8))[1]; + $offset += 8; + } else { + throw new \ValueError('Invalid CBOR TextString length information.'); + } + + $text = substr($input, $offset, $length); + + if (strlen($text) !== $length) { + throw new \ValueError('Invalid CBOR TextString length mismatch.'); + } + + return $text; } -} \ No newline at end of file +} diff --git a/tests/Unit/CBOR/MajorTypes/TextStringTest.php b/tests/Unit/CBOR/MajorTypes/TextStringTest.php index 583983c..950b643 100644 --- a/tests/Unit/CBOR/MajorTypes/TextStringTest.php +++ b/tests/Unit/CBOR/MajorTypes/TextStringTest.php @@ -17,27 +17,27 @@ class TextStringTest extends TestCase { - #[DataProvider('provideCases')] - public function testItCanEncodeCorrectly(string $case, string $header): void + #[DataProvider('provideValidCases')] + public function testEncodeProducesCorrectCBORRepresentation(string $input, string $expectedHeader): void { - $actual = bin2hex(TextString::encode($case)); - $expected = bin2hex($header . $case); + $actual = bin2hex(TextString::encode($input)); + $expected = bin2hex($expectedHeader . $input); $this->assertSame($expected, $actual); } - #[DataProvider('provideCases')] - public function testItCanDecodeCorrectly(string $case, string $header): void + #[DataProvider('provideValidCases')] + public function testDecodeExtractsOriginalStringFromCBORRepresentation(string $input, string $header): void { - $target = $header . $case; + $encoded = $header . $input; - $actual = TextString::decode($target); - $expected = $case; + $actual = TextString::decode($encoded); + $expected = $input; $this->assertSame($expected, $actual); } - public static function provideCases(): array + public static function provideValidCases(): array { return [ ["f", "\x61"], @@ -54,4 +54,30 @@ public static function provideCases(): array ] ]; } + + 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"); + } } From 81c230bc6d6efd8ae277e7514f3f576fb51861e0 Mon Sep 17 00:00:00 2001 From: Eldar Shahmaliyev Date: Thu, 9 Jan 2025 18:00:16 +0400 Subject: [PATCH 4/5] feat(CBOR): Add decode support for TextString and UnsignedInteger with validation - Implemented `decode` method in `CBOR` class to support both `TextString` and `UnsignedInteger` types. - Added `validate` method to `TextString` and `UnsignedInteger` classes for type-specific validation. - Updated `decode` methods in `TextString` and `UnsignedInteger` to use their respective `validate` methods. - Enhanced `CBORTest` to include tests for decoding mixed types (`TextString` and `UnsignedInteger`). - Fixed and refactored exception messages for better consistency and readability in `UnsignedInteger`. --- src/CBOR/CBOR.php | 12 ++++++++++-- src/CBOR/MajorTypes/TextString.php | 12 +++++++----- src/CBOR/MajorTypes/UnsignedInteger.php | 15 +++++++++------ tests/Unit/CBOR/CBORTest.php | 8 ++++---- .../Unit/CBOR/MajorTypes/UnsignedIntegerTest.php | 2 +- 5 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/CBOR/CBOR.php b/src/CBOR/CBOR.php index a2efb26..c83c76a 100644 --- a/src/CBOR/CBOR.php +++ b/src/CBOR/CBOR.php @@ -30,8 +30,16 @@ public static function encode(string|int|array $data): string throw new \ValueError("Unsupported type: " . gettype($data)); } - public static function decode(string $data): int + public static function decode(string $data): int|string { - // TODO + 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 index d155752..69fbdd8 100644 --- a/src/CBOR/MajorTypes/TextString.php +++ b/src/CBOR/MajorTypes/TextString.php @@ -34,14 +34,11 @@ public static function encode(string $input): string public static function decode(string $input): string { - $initialByte = ord($input[0]); - $majorType = ($initialByte >> 5) & 0x07; - - if ($majorType !== 0x03) { + if (! self::validate($input)) { throw new \ValueError('Invalid CBOR TextString major type.'); } - $additionalInfo = $initialByte & 0x1F; + $additionalInfo = ord($input[0]) & 0x1F; $offset = 1; if ($additionalInfo <= 23) { @@ -70,4 +67,9 @@ public static function decode(string $input): string return $text; } + + public static function validate(string $input): bool + { + return ((ord($input[0]) >> 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..0158a63 100644 --- a/tests/Unit/CBOR/CBORTest.php +++ b/tests/Unit/CBOR/CBORTest.php @@ -18,14 +18,14 @@ 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); @@ -42,8 +42,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/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); } From eea8a0305facdc50470933777fb9087bc2e29994 Mon Sep 17 00:00:00 2001 From: Eldar Shahmaliyev Date: Thu, 9 Jan 2025 18:27:52 +0400 Subject: [PATCH 5/5] test(CBOR): Improve test coverage for CBOR and TextString --- src/CBOR/CBOR.php | 2 +- tests/Unit/CBOR/CBORTest.php | 16 ++++++++++++++++ tests/Unit/CBOR/MajorTypes/TextStringTest.php | 4 ++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/CBOR/CBOR.php b/src/CBOR/CBOR.php index c83c76a..d1867ba 100644 --- a/src/CBOR/CBOR.php +++ b/src/CBOR/CBOR.php @@ -40,6 +40,6 @@ public static function decode(string $data): int|string return UnsignedInteger::decode($data); } - throw new \ValueError("Unsupported type"); + throw new \ValueError("Unsupported type."); } } diff --git a/tests/Unit/CBOR/CBORTest.php b/tests/Unit/CBOR/CBORTest.php index 0158a63..d1adbf9 100644 --- a/tests/Unit/CBOR/CBORTest.php +++ b/tests/Unit/CBOR/CBORTest.php @@ -32,6 +32,22 @@ public function testDecode(int|string $expected, string $data): void $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[] */ diff --git a/tests/Unit/CBOR/MajorTypes/TextStringTest.php b/tests/Unit/CBOR/MajorTypes/TextStringTest.php index 950b643..fb34fd9 100644 --- a/tests/Unit/CBOR/MajorTypes/TextStringTest.php +++ b/tests/Unit/CBOR/MajorTypes/TextStringTest.php @@ -46,6 +46,10 @@ public static function provideValidCases(): array ["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.