first commit

This commit is contained in:
2025-06-17 11:53:18 +02:00
commit 9f0f7ba12b
8804 changed files with 1369176 additions and 0 deletions

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Spomky-Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace CBOR;
use function chr;
use Stringable;
abstract class AbstractCBORObject implements CBORObject, Stringable
{
public function __construct(
private int $majorType,
protected int $additionalInformation
) {
}
public function __toString(): string
{
return chr($this->majorType << 5 | $this->additionalInformation);
}
public function getMajorType(): int
{
return $this->majorType;
}
public function getAdditionalInformation(): int
{
return $this->additionalInformation;
}
}

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace CBOR;
/**
* @see \CBOR\Test\ByteStringObjectTest
*/
final class ByteStringObject extends AbstractCBORObject implements Normalizable
{
private const MAJOR_TYPE = self::MAJOR_TYPE_BYTE_STRING;
private string $value;
private ?string $length = null;
public function __construct(string $data)
{
[$additionalInformation, $length] = LengthCalculator::getLengthOfString($data);
parent::__construct(self::MAJOR_TYPE, $additionalInformation);
$this->length = $length;
$this->value = $data;
}
public function __toString(): string
{
$result = parent::__toString();
if ($this->length !== null) {
$result .= $this->length;
}
return $result . $this->value;
}
public static function create(string $data): self
{
return new self($data);
}
public function getValue(): string
{
return $this->value;
}
public function getLength(): int
{
return mb_strlen($this->value, '8bit');
}
public function normalize(): string
{
return $this->value;
}
}

View File

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace CBOR;
interface CBORObject
{
public const MAJOR_TYPE_UNSIGNED_INTEGER = 0b000;
public const MAJOR_TYPE_NEGATIVE_INTEGER = 0b001;
public const MAJOR_TYPE_BYTE_STRING = 0b010;
public const MAJOR_TYPE_TEXT_STRING = 0b011;
public const MAJOR_TYPE_LIST = 0b100;
public const MAJOR_TYPE_MAP = 0b101;
public const MAJOR_TYPE_TAG = 0b110;
public const MAJOR_TYPE_OTHER_TYPE = 0b111;
public const LENGTH_1_BYTE = 0b00011000;
public const LENGTH_2_BYTES = 0b00011001;
public const LENGTH_4_BYTES = 0b00011010;
public const LENGTH_8_BYTES = 0b00011011;
public const LENGTH_INDEFINITE = 0b00011111;
public const FUTURE_USE_1 = 0b00011100;
public const FUTURE_USE_2 = 0b00011101;
public const FUTURE_USE_3 = 0b00011110;
public const OBJECT_FALSE = 20;
public const OBJECT_TRUE = 21;
public const OBJECT_NULL = 22;
public const OBJECT_UNDEFINED = 23;
public const OBJECT_SIMPLE_VALUE = 24;
public const OBJECT_HALF_PRECISION_FLOAT = 25;
public const OBJECT_SINGLE_PRECISION_FLOAT = 26;
public const OBJECT_DOUBLE_PRECISION_FLOAT = 27;
public const OBJECT_BREAK = 0b00011111;
public const TAG_STANDARD_DATETIME = 0;
public const TAG_EPOCH_DATETIME = 1;
public const TAG_UNSIGNED_BIG_NUM = 2;
public const TAG_NEGATIVE_BIG_NUM = 3;
public const TAG_DECIMAL_FRACTION = 4;
public const TAG_BIG_FLOAT = 5;
public const TAG_ENCODED_BASE64_URL = 21;
public const TAG_ENCODED_BASE64 = 22;
public const TAG_ENCODED_BASE16 = 23;
public const TAG_ENCODED_CBOR = 24;
public const TAG_URI = 32;
public const TAG_BASE64_URL = 33;
public const TAG_BASE64 = 34;
public const TAG_MIME = 36;
public const TAG_CBOR = 55799;
public function __toString(): string;
public function getMajorType(): int;
public function getAdditionalInformation(): int;
}

View File

@ -0,0 +1,239 @@
<?php
declare(strict_types=1);
namespace CBOR;
use CBOR\OtherObject\BreakObject;
use CBOR\OtherObject\DoublePrecisionFloatObject;
use CBOR\OtherObject\FalseObject;
use CBOR\OtherObject\HalfPrecisionFloatObject;
use CBOR\OtherObject\NullObject;
use CBOR\OtherObject\OtherObjectManager;
use CBOR\OtherObject\OtherObjectManagerInterface;
use CBOR\OtherObject\SimpleObject;
use CBOR\OtherObject\SinglePrecisionFloatObject;
use CBOR\OtherObject\TrueObject;
use CBOR\OtherObject\UndefinedObject;
use CBOR\Tag\Base16EncodingTag;
use CBOR\Tag\Base64EncodingTag;
use CBOR\Tag\Base64Tag;
use CBOR\Tag\Base64UrlEncodingTag;
use CBOR\Tag\Base64UrlTag;
use CBOR\Tag\BigFloatTag;
use CBOR\Tag\CBOREncodingTag;
use CBOR\Tag\CBORTag;
use CBOR\Tag\DatetimeTag;
use CBOR\Tag\DecimalFractionTag;
use CBOR\Tag\MimeTag;
use CBOR\Tag\NegativeBigIntegerTag;
use CBOR\Tag\TagManager;
use CBOR\Tag\TagManagerInterface;
use CBOR\Tag\TimestampTag;
use CBOR\Tag\UnsignedBigIntegerTag;
use CBOR\Tag\UriTag;
use InvalidArgumentException;
use function ord;
use RuntimeException;
use const STR_PAD_LEFT;
final class Decoder implements DecoderInterface
{
private TagManagerInterface $tagObjectManager;
private OtherObjectManagerInterface $otherTypeManager;
public function __construct(
?TagManagerInterface $tagObjectManager = null,
?OtherObjectManagerInterface $otherTypeManager = null
) {
$this->tagObjectManager = $tagObjectManager ?? $this->generateTagManager();
$this->otherTypeManager = $otherTypeManager ?? $this->generateOtherObjectManager();
}
public static function create(
?TagManagerInterface $tagObjectManager = null,
?OtherObjectManagerInterface $otherTypeManager = null
): self {
return new self($tagObjectManager, $otherTypeManager);
}
public function decode(Stream $stream): CBORObject
{
return $this->process($stream, false);
}
private function process(Stream $stream, bool $breakable): CBORObject
{
$ib = ord($stream->read(1));
$mt = $ib >> 5;
$ai = $ib & 0b00011111;
$val = null;
switch ($ai) {
case CBORObject::LENGTH_1_BYTE: //24
case CBORObject::LENGTH_2_BYTES: //25
case CBORObject::LENGTH_4_BYTES: //26
case CBORObject::LENGTH_8_BYTES: //27
$val = $stream->read(2 ** ($ai & 0b00000111));
break;
case CBORObject::FUTURE_USE_1: //28
case CBORObject::FUTURE_USE_2: //29
case CBORObject::FUTURE_USE_3: //30
throw new InvalidArgumentException(sprintf(
'Cannot parse the data. Found invalid Additional Information "%s" (%d).',
str_pad(decbin($ai), 8, '0', STR_PAD_LEFT),
$ai
));
case CBORObject::LENGTH_INDEFINITE: //31
return $this->processInfinite($stream, $mt, $breakable);
}
return $this->processFinite($stream, $mt, $ai, $val);
}
private function processFinite(Stream $stream, int $mt, int $ai, ?string $val): CBORObject
{
switch ($mt) {
case CBORObject::MAJOR_TYPE_UNSIGNED_INTEGER: //0
return UnsignedIntegerObject::createObjectForValue($ai, $val);
case CBORObject::MAJOR_TYPE_NEGATIVE_INTEGER: //1
return NegativeIntegerObject::createObjectForValue($ai, $val);
case CBORObject::MAJOR_TYPE_BYTE_STRING: //2
$length = $val === null ? $ai : Utils::binToInt($val);
return ByteStringObject::create($stream->read($length));
case CBORObject::MAJOR_TYPE_TEXT_STRING: //3
$length = $val === null ? $ai : Utils::binToInt($val);
return TextStringObject::create($stream->read($length));
case CBORObject::MAJOR_TYPE_LIST: //4
$object = ListObject::create();
$nbItems = $val === null ? $ai : Utils::binToInt($val);
for ($i = 0; $i < $nbItems; ++$i) {
$object->add($this->process($stream, false));
}
return $object;
case CBORObject::MAJOR_TYPE_MAP: //5
$object = MapObject::create();
$nbItems = $val === null ? $ai : Utils::binToInt($val);
for ($i = 0; $i < $nbItems; ++$i) {
$object->add($this->process($stream, false), $this->process($stream, false));
}
return $object;
case CBORObject::MAJOR_TYPE_TAG: //6
return $this->tagObjectManager->createObjectForValue($ai, $val, $this->process($stream, false));
case CBORObject::MAJOR_TYPE_OTHER_TYPE: //7
return $this->otherTypeManager->createObjectForValue($ai, $val);
default:
throw new RuntimeException(sprintf(
'Unsupported major type "%s" (%d).',
str_pad(decbin($mt), 5, '0', STR_PAD_LEFT),
$mt
)); // Should never append
}
}
private function processInfinite(Stream $stream, int $mt, bool $breakable): CBORObject
{
switch ($mt) {
case CBORObject::MAJOR_TYPE_BYTE_STRING: //2
$object = IndefiniteLengthByteStringObject::create();
while (! ($it = $this->process($stream, true)) instanceof BreakObject) {
if (! $it instanceof ByteStringObject) {
throw new RuntimeException(
'Unable to parse the data. Infinite Byte String object can only get Byte String objects.'
);
}
$object->add($it);
}
return $object;
case CBORObject::MAJOR_TYPE_TEXT_STRING: //3
$object = IndefiniteLengthTextStringObject::create();
while (! ($it = $this->process($stream, true)) instanceof BreakObject) {
if (! $it instanceof TextStringObject) {
throw new RuntimeException(
'Unable to parse the data. Infinite Text String object can only get Text String objects.'
);
}
$object->add($it);
}
return $object;
case CBORObject::MAJOR_TYPE_LIST: //4
$object = IndefiniteLengthListObject::create();
$it = $this->process($stream, true);
while (! $it instanceof BreakObject) {
$object->add($it);
$it = $this->process($stream, true);
}
return $object;
case CBORObject::MAJOR_TYPE_MAP: //5
$object = IndefiniteLengthMapObject::create();
while (! ($it = $this->process($stream, true)) instanceof BreakObject) {
$object->add($it, $this->process($stream, false));
}
return $object;
case CBORObject::MAJOR_TYPE_OTHER_TYPE: //7
if (! $breakable) {
throw new InvalidArgumentException('Cannot parse the data. No enclosing indefinite.');
}
return BreakObject::create();
case CBORObject::MAJOR_TYPE_UNSIGNED_INTEGER: //0
case CBORObject::MAJOR_TYPE_NEGATIVE_INTEGER: //1
case CBORObject::MAJOR_TYPE_TAG: //6
default:
throw new InvalidArgumentException(sprintf(
'Cannot parse the data. Found infinite length for Major Type "%s" (%d).',
str_pad(decbin($mt), 5, '0', STR_PAD_LEFT),
$mt
));
}
}
private function generateTagManager(): TagManagerInterface
{
return TagManager::create()
->add(DatetimeTag::class)
->add(TimestampTag::class)
->add(UnsignedBigIntegerTag::class)
->add(NegativeBigIntegerTag::class)
->add(DecimalFractionTag::class)
->add(BigFloatTag::class)
->add(Base64UrlEncodingTag::class)
->add(Base64EncodingTag::class)
->add(Base16EncodingTag::class)
->add(CBOREncodingTag::class)
->add(UriTag::class)
->add(Base64UrlTag::class)
->add(Base64Tag::class)
->add(MimeTag::class)
->add(CBORTag::class)
;
}
private function generateOtherObjectManager(): OtherObjectManagerInterface
{
return OtherObjectManager::create()
->add(BreakObject::class)
->add(SimpleObject::class)
->add(FalseObject::class)
->add(TrueObject::class)
->add(NullObject::class)
->add(UndefinedObject::class)
->add(HalfPrecisionFloatObject::class)
->add(SinglePrecisionFloatObject::class)
->add(DoublePrecisionFloatObject::class)
;
}
}

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace CBOR;
interface DecoderInterface
{
public function decode(Stream $stream): CBORObject;
}

View File

@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace CBOR;
/**
* @see \CBOR\Test\IndefiniteLengthByteStringObjectTest
*/
final class IndefiniteLengthByteStringObject extends AbstractCBORObject implements Normalizable
{
private const MAJOR_TYPE = self::MAJOR_TYPE_BYTE_STRING;
private const ADDITIONAL_INFORMATION = self::LENGTH_INDEFINITE;
/**
* @var ByteStringObject[]
*/
private array $chunks = [];
public function __construct()
{
parent::__construct(self::MAJOR_TYPE, self::ADDITIONAL_INFORMATION);
}
public function __toString(): string
{
$result = parent::__toString();
foreach ($this->chunks as $chunk) {
$result .= $chunk->__toString();
}
return $result . "\xFF";
}
public static function create(): self
{
return new self();
}
public function add(ByteStringObject $chunk): self
{
$this->chunks[] = $chunk;
return $this;
}
public function append(string $chunk): self
{
$this->add(ByteStringObject::create($chunk));
return $this;
}
public function getValue(): string
{
$result = '';
foreach ($this->chunks as $chunk) {
$result .= $chunk->getValue();
}
return $result;
}
public function getLength(): int
{
$length = 0;
foreach ($this->chunks as $chunk) {
$length += $chunk->getLength();
}
return $length;
}
public function normalize(): string
{
$result = '';
foreach ($this->chunks as $chunk) {
$result .= $chunk->normalize();
}
return $result;
}
}

View File

@ -0,0 +1,137 @@
<?php
declare(strict_types=1);
namespace CBOR;
use function array_key_exists;
use ArrayAccess;
use ArrayIterator;
use InvalidArgumentException;
use Iterator;
use IteratorAggregate;
/**
* @phpstan-implements ArrayAccess<int, CBORObject>
* @phpstan-implements IteratorAggregate<int, CBORObject>
* @final
*/
class IndefiniteLengthListObject extends AbstractCBORObject implements IteratorAggregate, Normalizable, ArrayAccess
{
private const MAJOR_TYPE = self::MAJOR_TYPE_LIST;
private const ADDITIONAL_INFORMATION = self::LENGTH_INDEFINITE;
/**
* @var CBORObject[]
*/
private array $data = [];
public function __construct()
{
parent::__construct(self::MAJOR_TYPE, self::ADDITIONAL_INFORMATION);
}
public function __toString(): string
{
$result = parent::__toString();
foreach ($this->data as $object) {
$result .= (string) $object;
}
return $result . "\xFF";
}
public static function create(): self
{
return new self();
}
/**
* @return mixed[]
*/
public function normalize(): array
{
return array_map(
static fn (CBORObject $object) => $object instanceof Normalizable ? $object->normalize() : $object,
$this->data
);
}
public function add(CBORObject $item): self
{
$this->data[] = $item;
return $this;
}
public function has(int $index): bool
{
return array_key_exists($index, $this->data);
}
public function remove(int $index): self
{
if (! $this->has($index)) {
return $this;
}
unset($this->data[$index]);
$this->data = array_values($this->data);
return $this;
}
public function get(int $index): CBORObject
{
if (! $this->has($index)) {
throw new InvalidArgumentException('Index not found.');
}
return $this->data[$index];
}
public function set(int $index, CBORObject $object): self
{
if (! $this->has($index)) {
throw new InvalidArgumentException('Index not found.');
}
$this->data[$index] = $object;
return $this;
}
/**
* @return Iterator<int, CBORObject>
*/
public function getIterator(): Iterator
{
return new ArrayIterator($this->data);
}
public function offsetExists($offset): bool
{
return $this->has($offset);
}
public function offsetGet($offset): CBORObject
{
return $this->get($offset);
}
public function offsetSet($offset, $value): void
{
if ($offset === null) {
$this->add($value);
return;
}
$this->set($offset, $value);
}
public function offsetUnset($offset): void
{
$this->remove($offset);
}
}

View File

@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
namespace CBOR;
use function array_key_exists;
use ArrayAccess;
use ArrayIterator;
use InvalidArgumentException;
use Iterator;
use IteratorAggregate;
/**
* @phpstan-implements ArrayAccess<int, CBORObject>
* @phpstan-implements IteratorAggregate<int, MapItem>
* @final
*/
class IndefiniteLengthMapObject extends AbstractCBORObject implements IteratorAggregate, Normalizable, ArrayAccess
{
private const MAJOR_TYPE = self::MAJOR_TYPE_MAP;
private const ADDITIONAL_INFORMATION = self::LENGTH_INDEFINITE;
/**
* @var MapItem[]
*/
private array $data = [];
public function __construct()
{
parent::__construct(self::MAJOR_TYPE, self::ADDITIONAL_INFORMATION);
}
public function __toString(): string
{
$result = parent::__toString();
foreach ($this->data as $object) {
$result .= (string) $object->getKey();
$result .= (string) $object->getValue();
}
return $result . "\xFF";
}
public static function create(): self
{
return new self();
}
public function add(CBORObject $key, CBORObject $value): self
{
if (! $key instanceof Normalizable) {
throw new InvalidArgumentException('Invalid key. Shall be normalizable');
}
$this->data[$key->normalize()] = MapItem::create($key, $value);
return $this;
}
public function has(int|string $key): bool
{
return array_key_exists($key, $this->data);
}
public function remove(int|string $index): self
{
if (! $this->has($index)) {
return $this;
}
unset($this->data[$index]);
$this->data = array_values($this->data);
return $this;
}
public function get(int|string $index): CBORObject
{
if (! $this->has($index)) {
throw new InvalidArgumentException('Index not found.');
}
return $this->data[$index]->getValue();
}
public function set(MapItem $object): self
{
$key = $object->getKey();
if (! $key instanceof Normalizable) {
throw new InvalidArgumentException('Invalid key. Shall be normalizable');
}
$this->data[$key->normalize()] = $object;
return $this;
}
/**
* @return Iterator<int, MapItem>
*/
public function getIterator(): Iterator
{
return new ArrayIterator($this->data);
}
/**
* @return mixed[]
*/
public function normalize(): array
{
return array_reduce($this->data, static function (array $carry, MapItem $item): array {
$key = $item->getKey();
if (! $key instanceof Normalizable) {
throw new InvalidArgumentException('Invalid key. Shall be normalizable');
}
$valueObject = $item->getValue();
$carry[$key->normalize()] = $valueObject instanceof Normalizable ? $valueObject->normalize() : $valueObject;
return $carry;
}, []);
}
public function offsetExists($offset): bool
{
return $this->has($offset);
}
public function offsetGet($offset): CBORObject
{
return $this->get($offset);
}
public function offsetSet($offset, $value): void
{
if (! $offset instanceof CBORObject) {
throw new InvalidArgumentException('Invalid key');
}
if (! $value instanceof CBORObject) {
throw new InvalidArgumentException('Invalid value');
}
$this->set(MapItem::create($offset, $value));
}
public function offsetUnset($offset): void
{
$this->remove($offset);
}
}

View File

@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace CBOR;
/**
* @see \CBOR\Test\IndefiniteLengthTextStringObjectTest
*/
final class IndefiniteLengthTextStringObject extends AbstractCBORObject implements Normalizable
{
private const MAJOR_TYPE = self::MAJOR_TYPE_TEXT_STRING;
private const ADDITIONAL_INFORMATION = self::LENGTH_INDEFINITE;
/**
* @var TextStringObject[]
*/
private array $data = [];
public function __construct()
{
parent::__construct(self::MAJOR_TYPE, self::ADDITIONAL_INFORMATION);
}
public function __toString(): string
{
$result = parent::__toString();
foreach ($this->data as $object) {
$result .= (string) $object;
}
return $result . "\xFF";
}
public static function create(): self
{
return new self();
}
public function add(TextStringObject $chunk): self
{
$this->data[] = $chunk;
return $this;
}
public function append(string $chunk): self
{
$this->add(TextStringObject::create($chunk));
return $this;
}
public function getValue(): string
{
$result = '';
foreach ($this->data as $object) {
$result .= $object->getValue();
}
return $result;
}
public function getLength(): int
{
$length = 0;
foreach ($this->data as $object) {
$length += $object->getLength();
}
return $length;
}
public function normalize(): string
{
$result = '';
foreach ($this->data as $object) {
$result .= $object->normalize();
}
return $result;
}
}

View File

@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace CBOR;
use Brick\Math\BigInteger;
use function chr;
use function count;
use InvalidArgumentException;
use const STR_PAD_LEFT;
final class LengthCalculator
{
/**
* @return array{int, null|string}
*/
public static function getLengthOfString(string $data): array
{
$length = mb_strlen($data, '8bit');
return self::computeLength($length);
}
/**
* @param array<int|string, mixed> $data
*
* @return array{int, null|string}
*/
public static function getLengthOfArray(array $data): array
{
$length = count($data);
return self::computeLength($length);
}
/**
* @return array{int, null|string}
*/
private static function computeLength(int $length): array
{
return match (true) {
$length <= 23 => [$length, null],
$length <= 0xFF => [24, chr($length)],
$length <= 0xFFFF => [25, self::hex2bin(dechex($length))],
$length <= 0xFFFFFFFF => [26, self::hex2bin(dechex($length))],
BigInteger::of($length)->isLessThan(BigInteger::fromBase('FFFFFFFFFFFFFFFF', 16)) => [
27,
self::hex2bin(dechex($length)),
],
default => [31, null],
};
}
private static function hex2bin(string $data): string
{
$data = str_pad($data, (int) (2 ** ceil(log(mb_strlen($data, '8bit'), 2))), '0', STR_PAD_LEFT);
$result = hex2bin($data);
if ($result === false) {
throw new InvalidArgumentException('Unable to convert the data');
}
return $result;
}
}

View File

@ -0,0 +1,165 @@
<?php
declare(strict_types=1);
namespace CBOR;
use function array_key_exists;
use ArrayAccess;
use ArrayIterator;
use function count;
use Countable;
use InvalidArgumentException;
use Iterator;
use IteratorAggregate;
/**
* @phpstan-implements ArrayAccess<int, CBORObject>
* @phpstan-implements IteratorAggregate<int, CBORObject>
* @see \CBOR\Test\ListObjectTest
*/
class ListObject extends AbstractCBORObject implements Countable, IteratorAggregate, Normalizable, ArrayAccess
{
private const MAJOR_TYPE = self::MAJOR_TYPE_LIST;
/**
* @var CBORObject[]
*/
private array $data;
private ?string $length = null;
/**
* @param CBORObject[] $data
*/
public function __construct(array $data = [])
{
[$additionalInformation, $length] = LengthCalculator::getLengthOfArray($data);
array_map(static function ($item): void {
if (! $item instanceof CBORObject) {
throw new InvalidArgumentException('The list must contain only CBORObject objects.');
}
}, $data);
parent::__construct(self::MAJOR_TYPE, $additionalInformation);
$this->data = array_values($data);
$this->length = $length;
}
public function __toString(): string
{
$result = parent::__toString();
if ($this->length !== null) {
$result .= $this->length;
}
foreach ($this->data as $object) {
$result .= (string) $object;
}
return $result;
}
/**
* @param CBORObject[] $data
*/
public static function create(array $data = []): self
{
return new self($data);
}
public function add(CBORObject $object): self
{
$this->data[] = $object;
[$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data);
return $this;
}
public function has(int $index): bool
{
return array_key_exists($index, $this->data);
}
public function remove(int $index): self
{
if (! $this->has($index)) {
return $this;
}
unset($this->data[$index]);
$this->data = array_values($this->data);
[$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data);
return $this;
}
public function get(int $index): CBORObject
{
if (! $this->has($index)) {
throw new InvalidArgumentException('Index not found.');
}
return $this->data[$index];
}
public function set(int $index, CBORObject $object): self
{
if (! $this->has($index)) {
throw new InvalidArgumentException('Index not found.');
}
$this->data[$index] = $object;
[$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data);
return $this;
}
/**
* @return array<int, mixed>
*/
public function normalize(): array
{
return array_map(
static fn (CBORObject $object) => $object instanceof Normalizable ? $object->normalize() : $object,
$this->data
);
}
public function count(): int
{
return count($this->data);
}
/**
* @return Iterator<int, CBORObject>
*/
public function getIterator(): Iterator
{
return new ArrayIterator($this->data);
}
public function offsetExists($offset): bool
{
return $this->has($offset);
}
public function offsetGet($offset): CBORObject
{
return $this->get($offset);
}
public function offsetSet($offset, $value): void
{
if ($offset === null) {
$this->add($value);
return;
}
$this->set($offset, $value);
}
public function offsetUnset($offset): void
{
$this->remove($offset);
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace CBOR;
class MapItem
{
public function __construct(
private CBORObject $key,
private CBORObject $value
) {
}
public static function create(CBORObject $key, CBORObject $value): self
{
return new self($key, $value);
}
public function getKey(): CBORObject
{
return $this->key;
}
public function getValue(): CBORObject
{
return $this->value;
}
}

View File

@ -0,0 +1,180 @@
<?php
declare(strict_types=1);
namespace CBOR;
use function array_key_exists;
use ArrayAccess;
use ArrayIterator;
use function count;
use Countable;
use InvalidArgumentException;
use Iterator;
use IteratorAggregate;
/**
* @phpstan-implements ArrayAccess<int, CBORObject>
* @phpstan-implements IteratorAggregate<int, MapItem>
*/
final class MapObject extends AbstractCBORObject implements Countable, IteratorAggregate, Normalizable, ArrayAccess
{
private const MAJOR_TYPE = self::MAJOR_TYPE_MAP;
/**
* @var MapItem[]
*/
private array $data;
private ?string $length = null;
/**
* @param MapItem[] $data
*/
public function __construct(array $data = [])
{
[$additionalInformation, $length] = LengthCalculator::getLengthOfArray($data);
array_map(static function ($item): void {
if (! $item instanceof MapItem) {
throw new InvalidArgumentException('The list must contain only MapItem objects.');
}
}, $data);
parent::__construct(self::MAJOR_TYPE, $additionalInformation);
$this->data = $data;
$this->length = $length;
}
public function __toString(): string
{
$result = parent::__toString();
if ($this->length !== null) {
$result .= $this->length;
}
foreach ($this->data as $object) {
$result .= $object->getKey()
->__toString()
;
$result .= $object->getValue()
->__toString()
;
}
return $result;
}
/**
* @param MapItem[] $data
*/
public static function create(array $data = []): self
{
return new self($data);
}
public function add(CBORObject $key, CBORObject $value): self
{
if (! $key instanceof Normalizable) {
throw new InvalidArgumentException('Invalid key. Shall be normalizable');
}
$this->data[$key->normalize()] = MapItem::create($key, $value);
[$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data);
return $this;
}
public function has(int|string $key): bool
{
return array_key_exists($key, $this->data);
}
public function remove(int|string $index): self
{
if (! $this->has($index)) {
return $this;
}
unset($this->data[$index]);
$this->data = array_values($this->data);
[$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data);
return $this;
}
public function get(int|string $index): CBORObject
{
if (! $this->has($index)) {
throw new InvalidArgumentException('Index not found.');
}
return $this->data[$index]->getValue();
}
public function set(MapItem $object): self
{
$key = $object->getKey();
if (! $key instanceof Normalizable) {
throw new InvalidArgumentException('Invalid key. Shall be normalizable');
}
$this->data[$key->normalize()] = $object;
[$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data);
return $this;
}
public function count(): int
{
return count($this->data);
}
/**
* @return Iterator<int, MapItem>
*/
public function getIterator(): Iterator
{
return new ArrayIterator($this->data);
}
/**
* @return array<int|string, mixed>
*/
public function normalize(): array
{
return array_reduce($this->data, static function (array $carry, MapItem $item): array {
$key = $item->getKey();
if (! $key instanceof Normalizable) {
throw new InvalidArgumentException('Invalid key. Shall be normalizable');
}
$valueObject = $item->getValue();
$carry[$key->normalize()] = $valueObject instanceof Normalizable ? $valueObject->normalize() : $valueObject;
return $carry;
}, []);
}
public function offsetExists($offset): bool
{
return $this->has($offset);
}
public function offsetGet($offset): CBORObject
{
return $this->get($offset);
}
public function offsetSet($offset, $value): void
{
if (! $offset instanceof CBORObject) {
throw new InvalidArgumentException('Invalid key');
}
if (! $value instanceof CBORObject) {
throw new InvalidArgumentException('Invalid value');
}
$this->set(MapItem::create($offset, $value));
}
public function offsetUnset($offset): void
{
$this->remove($offset);
}
}

View File

@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
namespace CBOR;
use Brick\Math\BigInteger;
use InvalidArgumentException;
use const STR_PAD_LEFT;
final class NegativeIntegerObject extends AbstractCBORObject implements Normalizable
{
private const MAJOR_TYPE = self::MAJOR_TYPE_NEGATIVE_INTEGER;
public function __construct(
int $additionalInformation,
private ?string $data
) {
parent::__construct(self::MAJOR_TYPE, $additionalInformation);
}
public function __toString(): string
{
$result = parent::__toString();
if ($this->data !== null) {
$result .= $this->data;
}
return $result;
}
public static function createObjectForValue(int $additionalInformation, ?string $data): self
{
return new self($additionalInformation, $data);
}
public static function create(int $value): self
{
return self::createFromString((string) $value);
}
public static function createFromString(string $value): self
{
$integer = BigInteger::of($value);
return self::createBigInteger($integer);
}
public function getValue(): string
{
if ($this->data === null) {
return (string) (-1 - $this->additionalInformation);
}
$result = Utils::binToBigInteger($this->data);
$minusOne = BigInteger::of(-1);
return $minusOne->minus($result)
->toBase(10)
;
}
public function normalize(): string
{
return $this->getValue();
}
private static function createBigInteger(BigInteger $integer): self
{
if ($integer->isGreaterThanOrEqualTo(BigInteger::zero())) {
throw new InvalidArgumentException('The value must be a negative integer.');
}
$minusOne = BigInteger::of(-1);
$computed_value = $minusOne->minus($integer);
switch (true) {
case $computed_value->isLessThan(BigInteger::of(24)):
$ai = $computed_value->toInt();
$data = null;
break;
case $computed_value->isLessThan(BigInteger::fromBase('FF', 16)):
$ai = 24;
$data = self::hex2bin(str_pad($computed_value->toBase(16), 2, '0', STR_PAD_LEFT));
break;
case $computed_value->isLessThan(BigInteger::fromBase('FFFF', 16)):
$ai = 25;
$data = self::hex2bin(str_pad($computed_value->toBase(16), 4, '0', STR_PAD_LEFT));
break;
case $computed_value->isLessThan(BigInteger::fromBase('FFFFFFFF', 16)):
$ai = 26;
$data = self::hex2bin(str_pad($computed_value->toBase(16), 8, '0', STR_PAD_LEFT));
break;
default:
throw new InvalidArgumentException(
'Out of range. Please use NegativeBigIntegerTag tag with ByteStringObject object instead.'
);
}
return new self($ai, $data);
}
private static function hex2bin(string $data): string
{
$result = hex2bin($data);
if ($result === false) {
throw new InvalidArgumentException('Unable to convert the data');
}
return $result;
}
}

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace CBOR;
interface Normalizable
{
/**
* @return mixed|null
*/
public function normalize();
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace CBOR;
use CBOR\OtherObject\OtherObjectInterface;
abstract class OtherObject extends AbstractCBORObject implements OtherObjectInterface
{
private const MAJOR_TYPE = self::MAJOR_TYPE_OTHER_TYPE;
public function __construct(
int $additionalInformation,
protected ?string $data
) {
parent::__construct(self::MAJOR_TYPE, $additionalInformation);
}
public function __toString(): string
{
$result = parent::__toString();
if ($this->data !== null) {
$result .= $this->data;
}
return $result;
}
public function getContent(): ?string
{
return $this->data;
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace CBOR\OtherObject;
use CBOR\OtherObject as Base;
final class BreakObject extends Base
{
public function __construct()
{
parent::__construct(self::OBJECT_BREAK, null);
}
public static function create(): self
{
return new self();
}
public static function supportedAdditionalInformation(): array
{
return [self::OBJECT_BREAK];
}
public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
{
return new self();
}
}

View File

@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace CBOR\OtherObject;
use Brick\Math\BigInteger;
use CBOR\Normalizable;
use CBOR\OtherObject as Base;
use CBOR\Utils;
use const INF;
use InvalidArgumentException;
use const NAN;
final class DoublePrecisionFloatObject extends Base implements Normalizable
{
public static function supportedAdditionalInformation(): array
{
return [self::OBJECT_DOUBLE_PRECISION_FLOAT];
}
public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
{
return new self($additionalInformation, $data);
}
public static function create(string $value): self
{
if (mb_strlen($value, '8bit') !== 8) {
throw new InvalidArgumentException('The value is not a valid double precision floating point');
}
return new self(self::OBJECT_DOUBLE_PRECISION_FLOAT, $value);
}
public function normalize(): float|int
{
$exponent = $this->getExponent();
$mantissa = $this->getMantissa();
$sign = $this->getSign();
if ($exponent === 0) {
$val = $mantissa * 2 ** (-(1022 + 52));
} elseif ($exponent !== 0b11111111111) {
$val = ($mantissa + (1 << 52)) * 2 ** ($exponent - (1023 + 52));
} else {
$val = $mantissa === 0 ? INF : NAN;
}
return $sign * $val;
}
public function getExponent(): int
{
$data = $this->data;
Utils::assertString($data, 'Invalid data');
return Utils::binToBigInteger($data)->shiftedRight(52)->and(Utils::hexToBigInteger('7ff'))->toInt();
}
public function getMantissa(): int
{
$data = $this->data;
Utils::assertString($data, 'Invalid data');
return Utils::binToBigInteger($data)->and(Utils::hexToBigInteger('fffffffffffff'))->toInt();
}
public function getSign(): int
{
$data = $this->data;
Utils::assertString($data, 'Invalid data');
$sign = Utils::binToBigInteger($data)->shiftedRight(63);
return $sign->isEqualTo(BigInteger::one()) ? -1 : 1;
}
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace CBOR\OtherObject;
use CBOR\Normalizable;
use CBOR\OtherObject as Base;
final class FalseObject extends Base implements Normalizable
{
public function __construct()
{
parent::__construct(self::OBJECT_FALSE, null);
}
public static function create(): self
{
return new self();
}
public static function supportedAdditionalInformation(): array
{
return [self::OBJECT_FALSE];
}
public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
{
return new self();
}
public function normalize(): bool
{
return false;
}
}

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace CBOR\OtherObject;
use CBOR\OtherObject as Base;
use InvalidArgumentException;
use function ord;
final class GenericObject extends Base
{
public static function supportedAdditionalInformation(): array
{
return [];
}
public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
{
if ($data !== null && ord($data) < 32) {
throw new InvalidArgumentException('Invalid simple value. Content data should not be present.');
}
return new self($additionalInformation, $data);
}
}

View File

@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace CBOR\OtherObject;
use Brick\Math\BigInteger;
use CBOR\Normalizable;
use CBOR\OtherObject as Base;
use CBOR\Utils;
use const INF;
use InvalidArgumentException;
use const NAN;
final class HalfPrecisionFloatObject extends Base implements Normalizable
{
public static function supportedAdditionalInformation(): array
{
return [self::OBJECT_HALF_PRECISION_FLOAT];
}
public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
{
return new self($additionalInformation, $data);
}
public static function create(string $value): self
{
if (mb_strlen($value, '8bit') !== 2) {
throw new InvalidArgumentException('The value is not a valid half precision floating point');
}
return new self(self::OBJECT_HALF_PRECISION_FLOAT, $value);
}
public function normalize(): float|int
{
$exponent = $this->getExponent();
$mantissa = $this->getMantissa();
$sign = $this->getSign();
if ($exponent === 0) {
$val = $mantissa * 2 ** (-24);
} elseif ($exponent !== 0b11111) {
$val = ($mantissa + (1 << 10)) * 2 ** ($exponent - 25);
} else {
$val = $mantissa === 0 ? INF : NAN;
}
return $sign * $val;
}
public function getExponent(): int
{
$data = $this->data;
Utils::assertString($data, 'Invalid data');
return Utils::binToBigInteger($data)->shiftedRight(10)->and(Utils::hexToBigInteger('1f'))->toInt();
}
public function getMantissa(): int
{
$data = $this->data;
Utils::assertString($data, 'Invalid data');
return Utils::binToBigInteger($data)->and(Utils::hexToBigInteger('3ff'))->toInt();
}
public function getSign(): int
{
$data = $this->data;
Utils::assertString($data, 'Invalid data');
$sign = Utils::binToBigInteger($data)->shiftedRight(15);
return $sign->isEqualTo(BigInteger::one()) ? -1 : 1;
}
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace CBOR\OtherObject;
use CBOR\Normalizable;
use CBOR\OtherObject as Base;
final class NullObject extends Base implements Normalizable
{
public function __construct()
{
parent::__construct(self::OBJECT_NULL, null);
}
public static function create(): self
{
return new self();
}
public static function supportedAdditionalInformation(): array
{
return [self::OBJECT_NULL];
}
public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
{
return new self();
}
public function normalize(): ?string
{
return null;
}
}

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace CBOR\OtherObject;
use CBOR\CBORObject;
interface OtherObjectInterface extends CBORObject
{
/**
* @return int[]
*/
public static function supportedAdditionalInformation(): array;
public static function createFromLoadedData(int $additionalInformation, ?string $data): self;
}

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace CBOR\OtherObject;
use function array_key_exists;
use CBOR\OtherObject;
use InvalidArgumentException;
class OtherObjectManager implements OtherObjectManagerInterface
{
/**
* @var string[]
*/
private array $classes = [];
public static function create(): self
{
return new self();
}
public function add(string $class): self
{
foreach ($class::supportedAdditionalInformation() as $ai) {
if ($ai < 0) {
throw new InvalidArgumentException('Invalid additional information.');
}
$this->classes[$ai] = $class;
}
return $this;
}
public function getClassForValue(int $value): string
{
return array_key_exists($value, $this->classes) ? $this->classes[$value] : GenericObject::class;
}
public function createObjectForValue(int $value, ?string $data): OtherObjectInterface
{
/** @var OtherObject $class */
$class = $this->getClassForValue($value);
return $class::createFromLoadedData($value, $data);
}
}

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace CBOR\OtherObject;
interface OtherObjectManagerInterface
{
public function createObjectForValue(int $value, ?string $data): OtherObjectInterface;
}

View File

@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace CBOR\OtherObject;
use CBOR\Normalizable;
use CBOR\OtherObject as Base;
use CBOR\Utils;
use function chr;
use InvalidArgumentException;
use function ord;
final class SimpleObject extends Base implements Normalizable
{
public static function supportedAdditionalInformation(): array
{
return array_merge(range(0, 19), [24]);
}
public static function create(int $value): self|FalseObject|TrueObject|NullObject|UndefinedObject
{
switch (true) {
case $value >= 0 && $value <= 19:
return new self($value, null);
case $value === 20:
return FalseObject::create();
case $value === 21:
return TrueObject::create();
case $value === 22:
return NullObject::create();
case $value === 23:
return UndefinedObject::create();
case $value <= 31:
throw new InvalidArgumentException('Invalid simple value. Shall be between 32 and 255.');
case $value <= 255:
return new self(24, chr($value));
default:
throw new InvalidArgumentException('The value is not a valid simple value.');
}
}
public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
{
if ($additionalInformation === 24) {
if ($data === null) {
throw new InvalidArgumentException('Invalid simple value. Content data is missing.');
}
if (mb_strlen($data, '8bit') !== 1) {
throw new InvalidArgumentException('Invalid simple value. Content data is too long.');
}
if (ord($data) < 32) {
throw new InvalidArgumentException('Invalid simple value. Content data must be between 32 and 255.');
}
} elseif ($additionalInformation < 20) {
if ($data !== null) {
throw new InvalidArgumentException('Invalid simple value. Content data should not be present.');
}
}
return new self($additionalInformation, $data);
}
public function normalize(): int
{
if ($this->data === null) {
return $this->getAdditionalInformation();
}
return Utils::binToInt($this->data);
}
}

View File

@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace CBOR\OtherObject;
use Brick\Math\BigInteger;
use CBOR\OtherObject as Base;
use CBOR\Utils;
use const INF;
use InvalidArgumentException;
use const NAN;
final class SinglePrecisionFloatObject extends Base
{
public static function supportedAdditionalInformation(): array
{
return [self::OBJECT_SINGLE_PRECISION_FLOAT];
}
public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
{
return new self($additionalInformation, $data);
}
public static function create(string $value): self
{
if (mb_strlen($value, '8bit') !== 4) {
throw new InvalidArgumentException('The value is not a valid single precision floating point');
}
return new self(self::OBJECT_SINGLE_PRECISION_FLOAT, $value);
}
public function normalize(): float|int
{
$exponent = $this->getExponent();
$mantissa = $this->getMantissa();
$sign = $this->getSign();
if ($exponent === 0) {
$val = $mantissa * 2 ** (-(126 + 23));
} elseif ($exponent !== 0b11111111) {
$val = ($mantissa + (1 << 23)) * 2 ** ($exponent - (127 + 23));
} else {
$val = $mantissa === 0 ? INF : NAN;
}
return $sign * $val;
}
public function getExponent(): int
{
$data = $this->data;
Utils::assertString($data, 'Invalid data');
return Utils::binToBigInteger($data)->shiftedRight(23)->and(Utils::hexToBigInteger('ff'))->toInt();
}
public function getMantissa(): int
{
$data = $this->data;
Utils::assertString($data, 'Invalid data');
return Utils::binToBigInteger($data)->and(Utils::hexToBigInteger('7fffff'))->toInt();
}
public function getSign(): int
{
$data = $this->data;
Utils::assertString($data, 'Invalid data');
$sign = Utils::binToBigInteger($data)->shiftedRight(31);
return $sign->isEqualTo(BigInteger::one()) ? -1 : 1;
}
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace CBOR\OtherObject;
use CBOR\Normalizable;
use CBOR\OtherObject as Base;
final class TrueObject extends Base implements Normalizable
{
public function __construct()
{
parent::__construct(self::OBJECT_TRUE, null);
}
public static function create(): self
{
return new self();
}
public static function supportedAdditionalInformation(): array
{
return [self::OBJECT_TRUE];
}
public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
{
return new self();
}
public function normalize(): bool
{
return true;
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace CBOR\OtherObject;
use CBOR\OtherObject as Base;
final class UndefinedObject extends Base
{
public function __construct()
{
parent::__construct(self::OBJECT_UNDEFINED, null);
}
public static function create(): self
{
return new self();
}
public static function supportedAdditionalInformation(): array
{
return [self::OBJECT_UNDEFINED];
}
public static function createFromLoadedData(int $additionalInformation, ?string $data): Base
{
return new self();
}
}

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace CBOR;
interface Stream
{
public function read(int $length): string;
}

View File

@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace CBOR;
use InvalidArgumentException;
use RuntimeException;
final class StringStream implements Stream
{
/**
* @var resource
*/
private $resource;
public function __construct(string $data)
{
$resource = fopen('php://memory', 'rb+');
if ($resource === false) {
throw new RuntimeException('Unable to open the memory');
}
$result = fwrite($resource, $data);
if ($result === false) {
throw new RuntimeException('Unable to write the memory');
}
$result = rewind($resource);
if ($result === false) {
throw new RuntimeException('Unable to rewind the memory');
}
$this->resource = $resource;
}
public static function create(string $data): self
{
return new self($data);
}
public function read(int $length): string
{
if ($length === 0) {
return '';
}
$alreadyRead = 0;
$data = '';
while ($alreadyRead < $length) {
$left = $length - $alreadyRead;
$sizeToRead = $left < 1024 && $left > 0 ? $left : 1024;
$newData = fread($this->resource, $sizeToRead);
$alreadyRead += $sizeToRead;
if ($newData === false) {
throw new RuntimeException('Unable to read the memory');
}
if (mb_strlen($newData, '8bit') < $sizeToRead) {
throw new InvalidArgumentException(sprintf(
'Out of range. Expected: %d, read: %d.',
$length,
mb_strlen($data, '8bit')
));
}
$data .= $newData;
}
if (mb_strlen($data, '8bit') !== $length) {
throw new InvalidArgumentException(sprintf(
'Out of range. Expected: %d, read: %d.',
$length,
mb_strlen($data, '8bit')
));
}
return $data;
}
}

View File

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace CBOR;
use CBOR\Tag\TagInterface;
use InvalidArgumentException;
abstract class Tag extends AbstractCBORObject implements TagInterface
{
private const MAJOR_TYPE = self::MAJOR_TYPE_TAG;
public function __construct(
int $additionalInformation,
protected ?string $data,
protected CBORObject $object
) {
parent::__construct(self::MAJOR_TYPE, $additionalInformation);
}
public function __toString(): string
{
$result = parent::__toString();
if ($this->data !== null) {
$result .= $this->data;
}
return $result . $this->object;
}
public function getData(): ?string
{
return $this->data;
}
public function getValue(): CBORObject
{
return $this->object;
}
/**
* @return array{int, null|string}
*/
protected static function determineComponents(int $tag): array
{
switch (true) {
case $tag < 0:
throw new InvalidArgumentException('The value must be a positive integer.');
case $tag < 24:
return [$tag, null];
case $tag < 0xFF:
return [24, self::hex2bin(dechex($tag))];
case $tag < 0xFFFF:
return [25, self::hex2bin(dechex($tag))];
case $tag < 0xFFFFFFFF:
return [26, self::hex2bin(dechex($tag))];
default:
throw new InvalidArgumentException(
'Out of range. Please use PositiveBigIntegerTag tag with ByteStringObject object instead.'
);
}
}
private static function hex2bin(string $data): string
{
$result = hex2bin($data);
if ($result === false) {
throw new InvalidArgumentException('Unable to convert the data');
}
return $result;
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace CBOR\Tag;
use CBOR\CBORObject;
use CBOR\Tag;
final class Base16EncodingTag extends Tag
{
public static function getTagId(): int
{
return self::TAG_ENCODED_BASE16;
}
public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
{
return new self($additionalInformation, $data, $object);
}
public static function create(CBORObject $object): Tag
{
[$ai, $data] = self::determineComponents(self::TAG_ENCODED_BASE16);
return new self($ai, $data, $object);
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace CBOR\Tag;
use CBOR\CBORObject;
use CBOR\Tag;
final class Base64EncodingTag extends Tag
{
public static function getTagId(): int
{
return self::TAG_ENCODED_BASE64;
}
public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
{
return new self($additionalInformation, $data, $object);
}
public static function create(CBORObject $object): Tag
{
[$ai, $data] = self::determineComponents(self::TAG_ENCODED_BASE64);
return new self($ai, $data, $object);
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace CBOR\Tag;
use CBOR\CBORObject;
use CBOR\IndefiniteLengthTextStringObject;
use CBOR\Tag;
use CBOR\TextStringObject;
use InvalidArgumentException;
final class Base64Tag extends Tag
{
public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
{
if (! $object instanceof TextStringObject && ! $object instanceof IndefiniteLengthTextStringObject) {
throw new InvalidArgumentException('This tag only accepts a Text String object.');
}
parent::__construct($additionalInformation, $data, $object);
}
public static function getTagId(): int
{
return self::TAG_BASE64;
}
public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
{
return new self($additionalInformation, $data, $object);
}
public static function create(CBORObject $object): Tag
{
[$ai, $data] = self::determineComponents(self::TAG_BASE64);
return new self($ai, $data, $object);
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace CBOR\Tag;
use CBOR\CBORObject;
use CBOR\Tag;
final class Base64UrlEncodingTag extends Tag
{
public static function getTagId(): int
{
return self::TAG_ENCODED_BASE64_URL;
}
public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
{
return new self($additionalInformation, $data, $object);
}
public static function create(CBORObject $object): Tag
{
[$ai, $data] = self::determineComponents(self::TAG_ENCODED_BASE64_URL);
return new self($ai, $data, $object);
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace CBOR\Tag;
use CBOR\CBORObject;
use CBOR\IndefiniteLengthTextStringObject;
use CBOR\Tag;
use CBOR\TextStringObject;
use InvalidArgumentException;
final class Base64UrlTag extends Tag
{
public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
{
if (! $object instanceof TextStringObject && ! $object instanceof IndefiniteLengthTextStringObject) {
throw new InvalidArgumentException('This tag only accepts a Text String object.');
}
parent::__construct($additionalInformation, $data, $object);
}
public static function getTagId(): int
{
return self::TAG_BASE64_URL;
}
public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
{
return new self($additionalInformation, $data, $object);
}
public static function create(CBORObject $object): Tag
{
[$ai, $data] = self::determineComponents(self::TAG_BASE64_URL);
return new self($ai, $data, $object);
}
}

View File

@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace CBOR\Tag;
use CBOR\CBORObject;
use CBOR\ListObject;
use CBOR\NegativeIntegerObject;
use CBOR\Normalizable;
use CBOR\Tag;
use CBOR\UnsignedIntegerObject;
use function count;
use function extension_loaded;
use InvalidArgumentException;
use RuntimeException;
final class BigFloatTag extends Tag implements Normalizable
{
public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
{
if (! extension_loaded('bcmath')) {
throw new RuntimeException('The extension "bcmath" is required to use this tag');
}
if (! $object instanceof ListObject || count($object) !== 2) {
throw new InvalidArgumentException(
'This tag only accepts a ListObject object that contains an exponent and a mantissa.'
);
}
$e = $object->get(0);
if (! $e instanceof UnsignedIntegerObject && ! $e instanceof NegativeIntegerObject) {
throw new InvalidArgumentException('The exponent must be a Signed Integer or an Unsigned Integer object.');
}
$m = $object->get(1);
if (! $m instanceof UnsignedIntegerObject && ! $m instanceof NegativeIntegerObject && ! $m instanceof NegativeBigIntegerTag && ! $m instanceof UnsignedBigIntegerTag) {
throw new InvalidArgumentException(
'The mantissa must be a Positive or Negative Signed Integer or an Unsigned Integer object.'
);
}
parent::__construct($additionalInformation, $data, $object);
}
public static function getTagId(): int
{
return self::TAG_BIG_FLOAT;
}
public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
{
return new self($additionalInformation, $data, $object);
}
public static function create(CBORObject $object): Tag
{
[$ai, $data] = self::determineComponents(self::TAG_BIG_FLOAT);
return new self($ai, $data, $object);
}
public static function createFromExponentAndMantissa(CBORObject $e, CBORObject $m): Tag
{
$object = ListObject::create()
->add($e)
->add($m)
;
return self::create($object);
}
public function normalize()
{
/** @var ListObject $object */
$object = $this->object;
/** @var UnsignedIntegerObject|NegativeIntegerObject $e */
$e = $object->get(0);
/** @var UnsignedIntegerObject|NegativeIntegerObject|NegativeBigIntegerTag|UnsignedBigIntegerTag $m */
$m = $object->get(1);
return rtrim(bcmul($m->normalize(), bcpow('2', $e->normalize(), 100), 100), '0');
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace CBOR\Tag;
use CBOR\ByteStringObject;
use CBOR\CBORObject;
use CBOR\IndefiniteLengthByteStringObject;
use CBOR\Tag;
use InvalidArgumentException;
final class CBOREncodingTag extends Tag
{
public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
{
if (! $object instanceof ByteStringObject && ! $object instanceof IndefiniteLengthByteStringObject) {
throw new InvalidArgumentException('This tag only accepts a Byte String object.');
}
parent::__construct($additionalInformation, $data, $object);
}
public static function getTagId(): int
{
return self::TAG_ENCODED_CBOR;
}
public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
{
return new self($additionalInformation, $data, $object);
}
public static function create(CBORObject $object): Tag
{
[$ai, $data] = self::determineComponents(self::TAG_ENCODED_CBOR);
return new self($ai, $data, $object);
}
}

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace CBOR\Tag;
use CBOR\CBORObject;
use CBOR\Normalizable;
use CBOR\Tag;
final class CBORTag extends Tag implements Normalizable
{
public static function getTagId(): int
{
return self::TAG_CBOR;
}
public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
{
return new self($additionalInformation, $data, $object);
}
public static function create(CBORObject $object): Tag
{
[$ai, $data] = self::determineComponents(self::TAG_CBOR);
return new self($ai, $data, $object);
}
/**
* @return mixed|CBORObject|null
*/
public function normalize()
{
return $this->object instanceof Normalizable ? $this->object->normalize() : $this->object;
}
}

View File

@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace CBOR\Tag;
use CBOR\CBORObject;
use CBOR\IndefiniteLengthTextStringObject;
use CBOR\Normalizable;
use CBOR\Tag;
use CBOR\TextStringObject;
use const DATE_RFC3339;
use DateTimeImmutable;
use DateTimeInterface;
use InvalidArgumentException;
/**
* @see \CBOR\Test\Tag\DatetimeTagTest
*/
final class DatetimeTag extends Tag implements Normalizable
{
public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
{
if (! $object instanceof TextStringObject && ! $object instanceof IndefiniteLengthTextStringObject) {
throw new InvalidArgumentException('This tag only accepts a Byte String object.');
}
parent::__construct($additionalInformation, $data, $object);
}
public static function getTagId(): int
{
return self::TAG_STANDARD_DATETIME;
}
public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
{
return new self($additionalInformation, $data, $object);
}
public static function create(CBORObject $object): Tag
{
[$ai, $data] = self::determineComponents(self::TAG_STANDARD_DATETIME);
return new self($ai, $data, $object);
}
public function normalize(): DateTimeInterface
{
/** @var TextStringObject|IndefiniteLengthTextStringObject $object */
$object = $this->object;
$result = DateTimeImmutable::createFromFormat(DATE_RFC3339, $object->normalize());
if ($result !== false) {
return $result;
}
$formatted = DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.uP', $object->normalize());
if ($formatted === false) {
throw new InvalidArgumentException('Invalid data. Cannot be converted into a datetime object');
}
return $formatted;
}
}

View File

@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace CBOR\Tag;
use CBOR\CBORObject;
use CBOR\ListObject;
use CBOR\NegativeIntegerObject;
use CBOR\Normalizable;
use CBOR\Tag;
use CBOR\UnsignedIntegerObject;
use function count;
use function extension_loaded;
use InvalidArgumentException;
use RuntimeException;
final class DecimalFractionTag extends Tag implements Normalizable
{
public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
{
if (! extension_loaded('bcmath')) {
throw new RuntimeException('The extension "bcmath" is required to use this tag');
}
if (! $object instanceof ListObject || count($object) !== 2) {
throw new InvalidArgumentException(
'This tag only accepts a ListObject object that contains an exponent and a mantissa.'
);
}
$e = $object->get(0);
if (! $e instanceof UnsignedIntegerObject && ! $e instanceof NegativeIntegerObject) {
throw new InvalidArgumentException('The exponent must be a Signed Integer or an Unsigned Integer object.');
}
$m = $object->get(1);
if (! $m instanceof UnsignedIntegerObject && ! $m instanceof NegativeIntegerObject && ! $m instanceof NegativeBigIntegerTag && ! $m instanceof UnsignedBigIntegerTag) {
throw new InvalidArgumentException(
'The mantissa must be a Positive or Negative Signed Integer or an Unsigned Integer object.'
);
}
parent::__construct($additionalInformation, $data, $object);
}
public static function create(CBORObject $object): self
{
[$ai, $data] = self::determineComponents(self::TAG_DECIMAL_FRACTION);
return new self($ai, $data, $object);
}
public static function getTagId(): int
{
return self::TAG_DECIMAL_FRACTION;
}
public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
{
return new self($additionalInformation, $data, $object);
}
public static function createFromExponentAndMantissa(CBORObject $e, CBORObject $m): Tag
{
$object = ListObject::create()
->add($e)
->add($m)
;
return self::create($object);
}
public function normalize()
{
/** @var ListObject $object */
$object = $this->object;
/** @var UnsignedIntegerObject|NegativeIntegerObject $e */
$e = $object->get(0);
/** @var UnsignedIntegerObject|NegativeIntegerObject|NegativeBigIntegerTag|UnsignedBigIntegerTag $m */
$m = $object->get(1);
return rtrim(bcmul($m->normalize(), bcpow('10', $e->normalize(), 100), 100), '0');
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace CBOR\Tag;
use CBOR\CBORObject;
use CBOR\Tag;
final class GenericTag extends Tag
{
public static function getTagId(): int
{
return -1;
}
public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
{
return new self($additionalInformation, $data, $object);
}
}

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace CBOR\Tag;
use CBOR\CBORObject;
use CBOR\IndefiniteLengthTextStringObject;
use CBOR\Normalizable;
use CBOR\Tag;
use CBOR\TextStringObject;
use InvalidArgumentException;
/**
* @see \CBOR\Test\Tag\MimeTagTest
*/
final class MimeTag extends Tag implements Normalizable
{
public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
{
if (! $object instanceof TextStringObject && ! $object instanceof IndefiniteLengthTextStringObject) {
throw new InvalidArgumentException('This tag only accepts a Byte String object.');
}
parent::__construct($additionalInformation, $data, $object);
}
public static function getTagId(): int
{
return self::TAG_MIME;
}
public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
{
return new self($additionalInformation, $data, $object);
}
public static function create(CBORObject $object): Tag
{
[$ai, $data] = self::determineComponents(self::TAG_MIME);
return new self($ai, $data, $object);
}
public function normalize(): string
{
/** @var TextStringObject|IndefiniteLengthTextStringObject $object */
$object = $this->object;
return $object->normalize();
}
}

View File

@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace CBOR\Tag;
use Brick\Math\BigInteger;
use CBOR\ByteStringObject;
use CBOR\CBORObject;
use CBOR\IndefiniteLengthByteStringObject;
use CBOR\Normalizable;
use CBOR\Tag;
use InvalidArgumentException;
final class NegativeBigIntegerTag extends Tag implements Normalizable
{
public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
{
if (! $object instanceof ByteStringObject && ! $object instanceof IndefiniteLengthByteStringObject) {
throw new InvalidArgumentException('This tag only accepts a Byte String object.');
}
parent::__construct($additionalInformation, $data, $object);
}
public static function getTagId(): int
{
return self::TAG_NEGATIVE_BIG_NUM;
}
public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
{
return new self($additionalInformation, $data, $object);
}
public static function create(CBORObject $object): Tag
{
[$ai, $data] = self::determineComponents(self::TAG_NEGATIVE_BIG_NUM);
return new self($ai, $data, $object);
}
public function normalize(): string
{
/** @var ByteStringObject|IndefiniteLengthByteStringObject $object */
$object = $this->object;
$integer = BigInteger::fromBase(bin2hex($object->getValue()), 16);
$minusOne = BigInteger::of(-1);
return $minusOne->minus($integer)
->toBase(10)
;
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace CBOR\Tag;
use CBOR\CBORObject;
interface TagInterface extends CBORObject
{
public static function getTagId(): int;
public function getValue(): CBORObject;
public static function createFromLoadedData(
int $additionalInformation,
?string $data,
CBORObject $object
): self;
}

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace CBOR\Tag;
use function array_key_exists;
use CBOR\CBORObject;
use CBOR\Tag;
use CBOR\Utils;
use InvalidArgumentException;
final class TagManager implements TagManagerInterface
{
/**
* @var string[]
*/
private array $classes = [];
public static function create(): self
{
return new self();
}
public function add(string $class): self
{
if ($class::getTagId() < 0) {
throw new InvalidArgumentException('Invalid tag ID.');
}
$this->classes[$class::getTagId()] = $class;
return $this;
}
public function getClassForValue(int $value): string
{
return array_key_exists($value, $this->classes) ? $this->classes[$value] : GenericTag::class;
}
public function createObjectForValue(int $additionalInformation, ?string $data, CBORObject $object): TagInterface
{
$value = $additionalInformation;
if ($additionalInformation >= 24) {
Utils::assertString($data, 'Invalid data');
$value = Utils::binToInt($data);
}
/** @var Tag $class */
$class = $this->getClassForValue($value);
return $class::createFromLoadedData($additionalInformation, $data, $object);
}
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace CBOR\Tag;
use CBOR\CBORObject;
interface TagManagerInterface
{
public function createObjectForValue(int $additionalInformation, ?string $data, CBORObject $object): TagInterface;
}

View File

@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace CBOR\Tag;
use CBOR\CBORObject;
use CBOR\NegativeIntegerObject;
use CBOR\Normalizable;
use CBOR\OtherObject\DoublePrecisionFloatObject;
use CBOR\OtherObject\HalfPrecisionFloatObject;
use CBOR\OtherObject\SinglePrecisionFloatObject;
use CBOR\Tag;
use CBOR\UnsignedIntegerObject;
use DateTimeImmutable;
use DateTimeInterface;
use InvalidArgumentException;
use const STR_PAD_RIGHT;
final class TimestampTag extends Tag implements Normalizable
{
public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
{
if (! $object instanceof UnsignedIntegerObject && ! $object instanceof NegativeIntegerObject && ! $object instanceof HalfPrecisionFloatObject && ! $object instanceof SinglePrecisionFloatObject && ! $object instanceof DoublePrecisionFloatObject) {
throw new InvalidArgumentException('This tag only accepts integer-based or float-based objects.');
}
parent::__construct($additionalInformation, $data, $object);
}
public static function getTagId(): int
{
return self::TAG_EPOCH_DATETIME;
}
public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
{
return new self($additionalInformation, $data, $object);
}
public static function create(CBORObject $object): Tag
{
[$ai, $data] = self::determineComponents(self::TAG_EPOCH_DATETIME);
return new self($ai, $data, $object);
}
public function normalize(): DateTimeInterface
{
$object = $this->object;
switch (true) {
case $object instanceof UnsignedIntegerObject:
case $object instanceof NegativeIntegerObject:
$formatted = DateTimeImmutable::createFromFormat('U', $object->normalize());
break;
case $object instanceof HalfPrecisionFloatObject:
case $object instanceof SinglePrecisionFloatObject:
case $object instanceof DoublePrecisionFloatObject:
$value = (string) $object->normalize();
$parts = explode('.', $value);
if (isset($parts[1])) {
if (mb_strlen($parts[1], '8bit') > 6) {
$parts[1] = mb_substr($parts[1], 0, 6, '8bit');
} else {
$parts[1] = str_pad($parts[1], 6, '0', STR_PAD_RIGHT);
}
}
$formatted = DateTimeImmutable::createFromFormat('U.u', implode('.', $parts));
break;
default:
throw new InvalidArgumentException('Unable to normalize the object');
}
if ($formatted === false) {
throw new InvalidArgumentException('Invalid data. Cannot be converted into a datetime object');
}
return $formatted;
}
}

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace CBOR\Tag;
use CBOR\ByteStringObject;
use CBOR\CBORObject;
use CBOR\IndefiniteLengthByteStringObject;
use CBOR\Normalizable;
use CBOR\Tag;
use CBOR\Utils;
use InvalidArgumentException;
final class UnsignedBigIntegerTag extends Tag implements Normalizable
{
public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
{
if (! $object instanceof ByteStringObject && ! $object instanceof IndefiniteLengthByteStringObject) {
throw new InvalidArgumentException('This tag only accepts a Byte String object.');
}
parent::__construct($additionalInformation, $data, $object);
}
public static function getTagId(): int
{
return self::TAG_UNSIGNED_BIG_NUM;
}
public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
{
return new self($additionalInformation, $data, $object);
}
public static function create(CBORObject $object): Tag
{
[$ai, $data] = self::determineComponents(self::TAG_UNSIGNED_BIG_NUM);
return new self($ai, $data, $object);
}
public function normalize(): string
{
/** @var ByteStringObject|IndefiniteLengthByteStringObject $object */
$object = $this->object;
return Utils::hexToString($object->normalize());
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace CBOR\Tag;
use CBOR\CBORObject;
use CBOR\IndefiniteLengthTextStringObject;
use CBOR\Normalizable;
use CBOR\Tag;
use CBOR\TextStringObject;
use InvalidArgumentException;
final class UriTag extends Tag implements Normalizable
{
public function __construct(int $additionalInformation, ?string $data, CBORObject $object)
{
if (! $object instanceof TextStringObject && ! $object instanceof IndefiniteLengthTextStringObject) {
throw new InvalidArgumentException('This tag only accepts a Text String object.');
}
parent::__construct($additionalInformation, $data, $object);
}
public static function getTagId(): int
{
return self::TAG_URI;
}
public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag
{
return new self($additionalInformation, $data, $object);
}
public static function create(CBORObject $object): Tag
{
[$ai, $data] = self::determineComponents(self::TAG_URI);
return new self($ai, $data, $object);
}
public function normalize(): string
{
/** @var TextStringObject|IndefiniteLengthTextStringObject $object */
$object = $this->object;
return $object->normalize();
}
}

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace CBOR;
/**
* @see \CBOR\Test\TextStringObjectTest
*/
final class TextStringObject extends AbstractCBORObject implements Normalizable
{
private const MAJOR_TYPE = self::MAJOR_TYPE_TEXT_STRING;
private ?string $length = null;
private string $data;
public function __construct(string $data)
{
[$additionalInformation, $length] = LengthCalculator::getLengthOfString($data);
parent::__construct(self::MAJOR_TYPE, $additionalInformation);
$this->data = $data;
$this->length = $length;
}
public function __toString(): string
{
$result = parent::__toString();
if ($this->length !== null) {
$result .= $this->length;
}
return $result . $this->data;
}
public static function create(string $data): self
{
return new self($data);
}
public function getValue(): string
{
return $this->data;
}
public function getLength(): int
{
return mb_strlen($this->data, 'utf8');
}
public function normalize(): string
{
return $this->data;
}
}

View File

@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
namespace CBOR;
use Brick\Math\BigInteger;
use InvalidArgumentException;
use const STR_PAD_LEFT;
final class UnsignedIntegerObject extends AbstractCBORObject implements Normalizable
{
private const MAJOR_TYPE = self::MAJOR_TYPE_UNSIGNED_INTEGER;
public function __construct(
int $additionalInformation,
private ?string $data
) {
parent::__construct(self::MAJOR_TYPE, $additionalInformation);
}
public function __toString(): string
{
$result = parent::__toString();
if ($this->data !== null) {
$result .= $this->data;
}
return $result;
}
public static function createObjectForValue(int $additionalInformation, ?string $data): self
{
return new self($additionalInformation, $data);
}
public static function create(int $value): self
{
return self::createFromString((string) $value);
}
public static function createFromHex(string $value): self
{
$integer = BigInteger::fromBase($value, 16);
return self::createBigInteger($integer);
}
public static function createFromString(string $value): self
{
$integer = BigInteger::of($value);
return self::createBigInteger($integer);
}
public function getMajorType(): int
{
return self::MAJOR_TYPE;
}
public function getValue(): string
{
if ($this->data === null) {
return (string) $this->additionalInformation;
}
$integer = BigInteger::fromBase(bin2hex($this->data), 16);
return $integer->toBase(10);
}
public function normalize(): string
{
return $this->getValue();
}
private static function createBigInteger(BigInteger $integer): self
{
if ($integer->isLessThan(BigInteger::zero())) {
throw new InvalidArgumentException('The value must be a positive integer.');
}
switch (true) {
case $integer->isLessThan(BigInteger::of(24)):
$ai = $integer->toInt();
$data = null;
break;
case $integer->isLessThan(BigInteger::fromBase('FF', 16)):
$ai = 24;
$data = self::hex2bin(str_pad($integer->toBase(16), 2, '0', STR_PAD_LEFT));
break;
case $integer->isLessThan(BigInteger::fromBase('FFFF', 16)):
$ai = 25;
$data = self::hex2bin(str_pad($integer->toBase(16), 4, '0', STR_PAD_LEFT));
break;
case $integer->isLessThan(BigInteger::fromBase('FFFFFFFF', 16)):
$ai = 26;
$data = self::hex2bin(str_pad($integer->toBase(16), 8, '0', STR_PAD_LEFT));
break;
default:
throw new InvalidArgumentException(
'Out of range. Please use PositiveBigIntegerTag tag with ByteStringObject object instead.'
);
}
return new self($ai, $data);
}
private static function hex2bin(string $data): string
{
$result = hex2bin($data);
if ($result === false) {
throw new InvalidArgumentException('Unable to convert the data');
}
return $result;
}
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace CBOR;
use Brick\Math\BigInteger;
use InvalidArgumentException;
use function is_string;
/**
* @internal
*/
abstract class Utils
{
public static function binToInt(string $value): int
{
return self::binToBigInteger($value)->toInt();
}
public static function binToBigInteger(string $value): BigInteger
{
return self::hexToBigInteger(bin2hex($value));
}
public static function hexToInt(string $value): int
{
return self::hexToBigInteger($value)->toInt();
}
public static function hexToBigInteger(string $value): BigInteger
{
return BigInteger::fromBase($value, 16);
}
public static function hexToString(string $value): string
{
return BigInteger::fromBase(bin2hex($value), 16)->toBase(10);
}
public static function decode(string $data): string
{
$decoded = base64_decode(strtr($data, '-_', '+/'), true);
if ($decoded === false) {
throw new InvalidArgumentException('Invalid data provided');
}
return $decoded;
}
/**
* @param mixed|null $data
*/
public static function assertString($data, ?string $message = null): void
{
if (! is_string($data)) {
throw new InvalidArgumentException($message ?? '');
}
}
}