diff --git a/src/CBOR/CBOR.php b/src/CBOR/CBOR.php deleted file mode 100644 index d1867ba..0000000 --- a/src/CBOR/CBOR.php +++ /dev/null @@ -1,45 +0,0 @@ -> 5) & 0x07) === self::MAJOR_TYPE; + } + + public static function encode(string $input): string + { + $len = strlen($input); + + if ($len <= 0x17) { + $header = chr((self::MAJOR_TYPE << 5) | $len); + } else if ($len <= 0xFF) { + $header = chr((self::MAJOR_TYPE << 5) | 24) . chr($len); + } else if ($len <= 0xFFFF) { + $header = chr((self::MAJOR_TYPE << 5) | 25) . pack('n', $len); + } else if ($len <= 0xFFFFFFFF) { + $header = chr((self::MAJOR_TYPE << 5) | 26) . pack('N', $len); + } else { + $header = chr((self::MAJOR_TYPE << 5) | 27) . pack('J', $len); + } + + return $header . $input; + } + + public static function decode(string $input): string + { + if (! self::validate($input)) { + throw new ValueError("Invalid CBOR ByteString major type."); + } + + $addInfo = ord($input[0]) & 0x1F; + $offset = 1; + + if ($addInfo <= 23) { + $length = $addInfo; + } + + if ($addInfo === 24) { + $length = ord($input[$offset]); + $offset += 1; + } + + if ($addInfo === 25) { + $length = unpack('n', substr($input, $offset, 2))[1]; + $offset += 2; + } + + if ($addInfo === 26) { + $length = unpack('N', substr($input, $offset, 4))[1]; + $offset += 4; + } + + if ($addInfo === 27) { + $length = unpack('J', substr($input, $offset, 8))[1]; + $offset += 8; + } + + if (! isset($length)) { + throw new \ValueError('Invalid CBOR ByteString length information.'); + } + + $text = substr($input, $offset, $length); + + if (strlen($text) !== $length) { + throw new \ValueError("Invalid CBOR ByteString length mismatch."); + } + + return $text; + } +} diff --git a/tests/Unit/CBOR/CBORTest.php b/tests/Unit/CBOR/CBORTest.php deleted file mode 100644 index d1adbf9..0000000 --- a/tests/Unit/CBOR/CBORTest.php +++ /dev/null @@ -1,78 +0,0 @@ -assertSame($expected, $encoded); - } - - #[DataProvider('validCases')] - 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[] - */ - public static function validCases(): array - { - return [ - // Unsigned integer test cases - [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 -// -// // Boolean test cases -// [[true], hex2bin('f5')], // true encoded as CBOR special type -// [[false], hex2bin('f4')], // false encoded as CBOR special type -// -// // Null test case -// [[null], hex2bin('f6')], // null encoded as CBOR special type -// -// // Array test cases -// [[[1, 2, 3]], hex2bin('83010203')], // [1, 2, 3] encoded as CBOR array -// -// // Map test cases -// [[['key' => 'value']], hex2bin('a1636b65796576616c7565')], // {"key": "value"} encoded as CBOR map - ]; - } -} diff --git a/tests/Unit/CBOR/MajorTypes/ByteStringTest.php b/tests/Unit/CBOR/MajorTypes/ByteStringTest.php new file mode 100644 index 0000000..cbd5b0d --- /dev/null +++ b/tests/Unit/CBOR/MajorTypes/ByteStringTest.php @@ -0,0 +1,81 @@ +assertTrue(ByteString::validate($header . $input)); + } + + public static function provideValidCases(): array + { + return [ + ['f', "\x41"], + ['fo', "\x42"], + ['foo', "\x43"], + ['foob', "\x44"], + ['fooba', "\x45"], + ['foobar', "\x46"], + [ + str_repeat("This is a longer string.", 4), + "\x58\x60" + ], + [ + str_repeat("This is a longer string.", 12), + "\x59\x01\x20" + ], + [ + str_repeat("This is a longer string.", 2731), + "\x5A\x00\x01\x00\x08" + ], + ]; + } + + #[DataProvider('provideValidCases')] + public function testEncode(string $input, string $header): void + { + $expected = $header . $input; + $actual = ByteString::encode($input); + + $this->assertSame(bin2hex($expected), bin2hex($actual)); + } + + #[DataProvider('provideValidCases')] + public function testDecode(string $input, string $header): void + { + $actual = ByteString::decode($header . $input); + + $this->assertSame($input, $actual); + } + + public function testDecodeThrowsExceptionForInvalidMajorType(): void + { + $this->expectException(\ValueError::class); + $this->expectExceptionMessage("Invalid CBOR ByteString major type."); + + ByteString::decode("\x0C"); + } + + public function testDecodeThrowsExceptionForLengthMismatch(): void + { + $this->expectException(\ValueError::class); + $this->expectExceptionMessage("Invalid CBOR ByteString length mismatch."); + + ByteString::decode("\x45\x66\x6F\x6F\x62"); + } + + public function testDecodeThrowsExceptionForInvalidAdditionalInformation(): void + { + $this->expectException(\ValueError::class); + $this->expectExceptionMessage("Invalid CBOR ByteString length information."); + + ByteString::decode("\x5F"); + } +}