primo commit

This commit is contained in:
2024-12-17 17:34:10 +01:00
commit e650f8df99
16435 changed files with 2451012 additions and 0 deletions

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace CBOR;
use Stringable;
use function chr;
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,58 @@
<?php
declare(strict_types=1);
namespace CBOR;
use function strlen;
/**
* @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 strlen($this->value);
}
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 RuntimeException;
use function ord;
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,89 @@
<?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(string ...$chunks): self
{
$object = new self();
foreach ($chunks as $chunk) {
$object->append($chunk);
}
return $object;
}
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,142 @@
<?php
declare(strict_types=1);
namespace CBOR;
use ArrayAccess;
use ArrayIterator;
use InvalidArgumentException;
use Iterator;
use IteratorAggregate;
use function array_key_exists;
/**
* @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(CBORObject ...$items): self
{
$object = new self();
foreach ($items as $item) {
$object->add($item);
}
return $object;
}
/**
* @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 ArrayAccess;
use ArrayIterator;
use InvalidArgumentException;
use Iterator;
use IteratorAggregate;
use function array_key_exists;
/**
* @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,89 @@
<?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(string ...$chunks): self
{
$object = new self();
foreach ($chunks as $chunk) {
$object->append($chunk);
}
return $object;
}
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,66 @@
<?php
declare(strict_types=1);
namespace CBOR;
use Brick\Math\BigInteger;
use InvalidArgumentException;
use function chr;
use function count;
use function strlen;
use const STR_PAD_LEFT;
final class LengthCalculator
{
/**
* @return array{int, null|string}
*/
public static function getLengthOfString(string $data): array
{
$length = strlen($data);
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(strlen($data), 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,162 @@
<?php
declare(strict_types=1);
namespace CBOR;
use ArrayAccess;
use ArrayIterator;
use Countable;
use InvalidArgumentException;
use Iterator;
use IteratorAggregate;
use function array_key_exists;
use function count;
/**
* @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 {
}, $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 ArrayAccess;
use ArrayIterator;
use Countable;
use InvalidArgumentException;
use Iterator;
use IteratorAggregate;
use function array_key_exists;
use function count;
/**
* @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,78 @@
<?php
declare(strict_types=1);
namespace CBOR\OtherObject;
use Brick\Math\BigInteger;
use CBOR\Normalizable;
use CBOR\OtherObject as Base;
use CBOR\Utils;
use InvalidArgumentException;
use function strlen;
use const INF;
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 (strlen($value) !== 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,78 @@
<?php
declare(strict_types=1);
namespace CBOR\OtherObject;
use Brick\Math\BigInteger;
use CBOR\Normalizable;
use CBOR\OtherObject as Base;
use CBOR\Utils;
use InvalidArgumentException;
use function strlen;
use const INF;
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 (strlen($value) !== 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 CBOR\OtherObject;
use InvalidArgumentException;
use function array_key_exists;
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,73 @@
<?php
declare(strict_types=1);
namespace CBOR\OtherObject;
use CBOR\Normalizable;
use CBOR\OtherObject as Base;
use CBOR\Utils;
use InvalidArgumentException;
use function chr;
use function ord;
use function strlen;
final class SimpleObject extends Base implements Normalizable
{
public static function supportedAdditionalInformation(): array
{
return [...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 (strlen($data) !== 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,77 @@
<?php
declare(strict_types=1);
namespace CBOR\OtherObject;
use Brick\Math\BigInteger;
use CBOR\OtherObject as Base;
use CBOR\Utils;
use InvalidArgumentException;
use function strlen;
use const INF;
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 (strlen($value) !== 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,77 @@
<?php
declare(strict_types=1);
namespace CBOR;
use InvalidArgumentException;
use RuntimeException;
use function strlen;
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 (strlen($newData) < $sizeToRead) {
throw new InvalidArgumentException(sprintf(
'Out of range. Expected: %d, read: %d.',
$length,
strlen($data)
));
}
$data .= $newData;
}
if (strlen($data) !== $length) {
throw new InvalidArgumentException(sprintf(
'Out of range. Expected: %d, read: %d.',
$length,
strlen($data)
));
}
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 InvalidArgumentException;
use RuntimeException;
use function count;
use function extension_loaded;
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 DateTimeImmutable;
use DateTimeInterface;
use InvalidArgumentException;
use const DATE_RFC3339;
/**
* @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 InvalidArgumentException;
use RuntimeException;
use function count;
use function extension_loaded;
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 CBOR\CBORObject;
use CBOR\Tag;
use CBOR\Utils;
use InvalidArgumentException;
use function array_key_exists;
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,83 @@
<?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 function strlen;
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 (strlen($parts[1]) > 6) {
$parts[1] = substr($parts[1], 0, 6);
} 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 ?? '');
}
}
}