10b3fd2d3SAndreas Gohr<?php 20b3fd2d3SAndreas Gohr/** 30b3fd2d3SAndreas Gohr * This file is part of the FreeDSx ASN1 package. 40b3fd2d3SAndreas Gohr * 50b3fd2d3SAndreas Gohr * (c) Chad Sikorra <Chad.Sikorra@gmail.com> 60b3fd2d3SAndreas Gohr * 70b3fd2d3SAndreas Gohr * For the full copyright and license information, please view the LICENSE 80b3fd2d3SAndreas Gohr * file that was distributed with this source code. 90b3fd2d3SAndreas Gohr */ 100b3fd2d3SAndreas Gohr 110b3fd2d3SAndreas Gohrnamespace FreeDSx\Asn1\Encoder; 120b3fd2d3SAndreas Gohr 13*dad993c5SAndreas Gohruse DateTime; 14*dad993c5SAndreas Gohruse DateTimeZone; 150b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Exception\EncoderException; 160b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Exception\InvalidArgumentException; 170b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Exception\PartialPduException; 180b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\AbstractStringType; 190b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\AbstractTimeType; 200b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\AbstractType; 21*dad993c5SAndreas Gohruse FreeDSx\Asn1\Type as EncodedType; 220b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\BitStringType; 230b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\BooleanType; 240b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\EnumeratedType; 250b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\GeneralizedTimeType; 260b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\IncompleteType; 270b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\IntegerType; 280b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\NullType; 290b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\OidType; 300b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\RealType; 310b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\RelativeOidType; 320b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\SetOfType; 330b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\SetType; 340b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\UtcTimeType; 35*dad993c5SAndreas Gohruse function bin2hex; 36*dad993c5SAndreas Gohruse function bindec; 37*dad993c5SAndreas Gohruse function chr; 38*dad993c5SAndreas Gohruse function count; 39*dad993c5SAndreas Gohruse function decbin; 40*dad993c5SAndreas Gohruse function dechex; 41*dad993c5SAndreas Gohruse function explode; 42*dad993c5SAndreas Gohruse function extension_loaded; 43*dad993c5SAndreas Gohruse function floor; 44*dad993c5SAndreas Gohruse function gmp_abs; 45*dad993c5SAndreas Gohruse function gmp_add; 46*dad993c5SAndreas Gohruse function gmp_and; 47*dad993c5SAndreas Gohruse function gmp_div; 48*dad993c5SAndreas Gohruse function gmp_export; 49*dad993c5SAndreas Gohruse function gmp_import; 50*dad993c5SAndreas Gohruse function gmp_init; 51*dad993c5SAndreas Gohruse function gmp_intval; 52*dad993c5SAndreas Gohruse function gmp_mul; 53*dad993c5SAndreas Gohruse function gmp_neg; 54*dad993c5SAndreas Gohruse function gmp_or; 55*dad993c5SAndreas Gohruse function gmp_pow; 56*dad993c5SAndreas Gohruse function gmp_strval; 57*dad993c5SAndreas Gohruse function gmp_sub; 58*dad993c5SAndreas Gohruse function hex2bin; 59*dad993c5SAndreas Gohruse function hexdec; 60*dad993c5SAndreas Gohruse function is_float; 61*dad993c5SAndreas Gohruse function is_int; 62*dad993c5SAndreas Gohruse function is_numeric; 63*dad993c5SAndreas Gohruse function is_string; 64*dad993c5SAndreas Gohruse function ord; 65*dad993c5SAndreas Gohruse function preg_match; 66*dad993c5SAndreas Gohruse function rtrim; 67*dad993c5SAndreas Gohruse function sprintf; 68*dad993c5SAndreas Gohruse function str_pad; 69*dad993c5SAndreas Gohruse function strlen; 70*dad993c5SAndreas Gohruse function strpos; 71*dad993c5SAndreas Gohruse function substr; 720b3fd2d3SAndreas Gohr 730b3fd2d3SAndreas Gohr/** 740b3fd2d3SAndreas Gohr * Basic Encoding Rules (BER) encoder. 750b3fd2d3SAndreas Gohr * 760b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com> 770b3fd2d3SAndreas Gohr */ 780b3fd2d3SAndreas Gohrclass BerEncoder implements EncoderInterface 790b3fd2d3SAndreas Gohr{ 800b3fd2d3SAndreas Gohr /** 810b3fd2d3SAndreas Gohr * Used to represent a bool false binary string. 820b3fd2d3SAndreas Gohr */ 830b3fd2d3SAndreas Gohr protected const BOOL_FALSE = "\x00"; 840b3fd2d3SAndreas Gohr 850b3fd2d3SAndreas Gohr /** 860b3fd2d3SAndreas Gohr * Used to represent a bool true binary string. 870b3fd2d3SAndreas Gohr */ 880b3fd2d3SAndreas Gohr protected const BOOL_TRUE = "\xff"; 890b3fd2d3SAndreas Gohr 900b3fd2d3SAndreas Gohr /** 91fd0855ecSAndreas Gohr * Anything greater than this we assume we may need to deal with a bigint in an OIDs second component. 92fd0855ecSAndreas Gohr */ 93fd0855ecSAndreas Gohr protected const MAX_SECOND_COMPONENT = PHP_INT_MAX - 80; 94fd0855ecSAndreas Gohr 95fd0855ecSAndreas Gohr /** 960b3fd2d3SAndreas Gohr * @var array 970b3fd2d3SAndreas Gohr */ 980b3fd2d3SAndreas Gohr protected $tagMap = [ 990b3fd2d3SAndreas Gohr AbstractType::TAG_CLASS_APPLICATION => [], 1000b3fd2d3SAndreas Gohr AbstractType::TAG_CLASS_CONTEXT_SPECIFIC => [], 1010b3fd2d3SAndreas Gohr AbstractType::TAG_CLASS_PRIVATE => [], 1020b3fd2d3SAndreas Gohr ]; 1030b3fd2d3SAndreas Gohr 1040b3fd2d3SAndreas Gohr protected $tmpTagMap = []; 1050b3fd2d3SAndreas Gohr 1060b3fd2d3SAndreas Gohr /** 1070b3fd2d3SAndreas Gohr * @var array 1080b3fd2d3SAndreas Gohr */ 1090b3fd2d3SAndreas Gohr protected $options = [ 1100b3fd2d3SAndreas Gohr 'bitstring_padding' => '0', 1110b3fd2d3SAndreas Gohr ]; 1120b3fd2d3SAndreas Gohr 1130b3fd2d3SAndreas Gohr /** 1140b3fd2d3SAndreas Gohr * @var bool 1150b3fd2d3SAndreas Gohr */ 1160b3fd2d3SAndreas Gohr protected $isGmpAvailable; 1170b3fd2d3SAndreas Gohr 1180b3fd2d3SAndreas Gohr /** 1190b3fd2d3SAndreas Gohr * @var int 1200b3fd2d3SAndreas Gohr */ 1210b3fd2d3SAndreas Gohr protected $pos; 1220b3fd2d3SAndreas Gohr 1230b3fd2d3SAndreas Gohr /** 1240b3fd2d3SAndreas Gohr * @var int|null 1250b3fd2d3SAndreas Gohr */ 1260b3fd2d3SAndreas Gohr protected $lastPos; 1270b3fd2d3SAndreas Gohr 1280b3fd2d3SAndreas Gohr /** 1290b3fd2d3SAndreas Gohr * @var int 1300b3fd2d3SAndreas Gohr */ 1310b3fd2d3SAndreas Gohr protected $maxLen; 1320b3fd2d3SAndreas Gohr 1330b3fd2d3SAndreas Gohr /** 134fd0855ecSAndreas Gohr * @var string|null 1350b3fd2d3SAndreas Gohr */ 1360b3fd2d3SAndreas Gohr protected $binary; 1370b3fd2d3SAndreas Gohr 1380b3fd2d3SAndreas Gohr /** 1390b3fd2d3SAndreas Gohr * @param array $options 1400b3fd2d3SAndreas Gohr */ 1410b3fd2d3SAndreas Gohr public function __construct(array $options = []) 1420b3fd2d3SAndreas Gohr { 143*dad993c5SAndreas Gohr $this->isGmpAvailable = extension_loaded('gmp'); 1440b3fd2d3SAndreas Gohr $this->setOptions($options); 1450b3fd2d3SAndreas Gohr } 1460b3fd2d3SAndreas Gohr 1470b3fd2d3SAndreas Gohr /** 1480b3fd2d3SAndreas Gohr * {@inheritdoc} 1490b3fd2d3SAndreas Gohr */ 1500b3fd2d3SAndreas Gohr public function decode($binary, array $tagMap = []): AbstractType 1510b3fd2d3SAndreas Gohr { 1520b3fd2d3SAndreas Gohr $this->startEncoding($binary, $tagMap); 1530b3fd2d3SAndreas Gohr if ($this->maxLen === 0) { 1540b3fd2d3SAndreas Gohr throw new InvalidArgumentException('The data to decode cannot be empty.'); 1550b3fd2d3SAndreas Gohr } elseif ($this->maxLen === 1) { 1560b3fd2d3SAndreas Gohr throw new PartialPduException('Received only 1 byte of data.'); 1570b3fd2d3SAndreas Gohr } 1580b3fd2d3SAndreas Gohr $type = $this->decodeBytes(true); 1590b3fd2d3SAndreas Gohr $this->stopEncoding(); 1600b3fd2d3SAndreas Gohr 1610b3fd2d3SAndreas Gohr return $type; 1620b3fd2d3SAndreas Gohr } 1630b3fd2d3SAndreas Gohr 1640b3fd2d3SAndreas Gohr /** 1650b3fd2d3SAndreas Gohr * {@inheritdoc} 1660b3fd2d3SAndreas Gohr */ 1670b3fd2d3SAndreas Gohr public function complete(IncompleteType $type, int $tagType, array $tagMap = []): AbstractType 1680b3fd2d3SAndreas Gohr { 169fd0855ecSAndreas Gohr $lastPos = $this->lastPos; 1700b3fd2d3SAndreas Gohr $this->startEncoding($type->getValue(), $tagMap); 1710b3fd2d3SAndreas Gohr $newType = $this->decodeBytes(false, $tagType, $this->maxLen, $type->getIsConstructed(), AbstractType::TAG_CLASS_UNIVERSAL); 1720b3fd2d3SAndreas Gohr $this->stopEncoding(); 1730b3fd2d3SAndreas Gohr $newType->setTagNumber($type->getTagNumber()) 1740b3fd2d3SAndreas Gohr ->setTagClass($type->getTagClass()); 175fd0855ecSAndreas Gohr $this->lastPos = $lastPos; 1760b3fd2d3SAndreas Gohr 1770b3fd2d3SAndreas Gohr return $newType; 1780b3fd2d3SAndreas Gohr } 1790b3fd2d3SAndreas Gohr 1800b3fd2d3SAndreas Gohr /** 1810b3fd2d3SAndreas Gohr * {@inheritdoc} 1820b3fd2d3SAndreas Gohr */ 1830b3fd2d3SAndreas Gohr public function encode(AbstractType $type): string 1840b3fd2d3SAndreas Gohr { 1850b3fd2d3SAndreas Gohr switch ($type) { 1860b3fd2d3SAndreas Gohr case $type instanceof BooleanType: 1870b3fd2d3SAndreas Gohr $bytes = $type->getValue() ? self::BOOL_TRUE : self::BOOL_FALSE; 1880b3fd2d3SAndreas Gohr break; 1890b3fd2d3SAndreas Gohr case $type instanceof IntegerType: 1900b3fd2d3SAndreas Gohr case $type instanceof EnumeratedType: 1910b3fd2d3SAndreas Gohr $bytes = $this->encodeInteger($type); 1920b3fd2d3SAndreas Gohr break; 1930b3fd2d3SAndreas Gohr case $type instanceof RealType: 1940b3fd2d3SAndreas Gohr $bytes = $this->encodeReal($type); 1950b3fd2d3SAndreas Gohr break; 1960b3fd2d3SAndreas Gohr case $type instanceof AbstractStringType: 1970b3fd2d3SAndreas Gohr $bytes = $type->getValue(); 1980b3fd2d3SAndreas Gohr break; 1990b3fd2d3SAndreas Gohr case $type instanceof SetOfType: 2000b3fd2d3SAndreas Gohr $bytes = $this->encodeSetOf($type); 2010b3fd2d3SAndreas Gohr break; 2020b3fd2d3SAndreas Gohr case $type instanceof SetType: 2030b3fd2d3SAndreas Gohr $bytes = $this->encodeSet($type); 2040b3fd2d3SAndreas Gohr break; 2050b3fd2d3SAndreas Gohr case $type->getIsConstructed(): 2060b3fd2d3SAndreas Gohr $bytes = $this->encodeConstructedType(...$type->getChildren()); 2070b3fd2d3SAndreas Gohr break; 2080b3fd2d3SAndreas Gohr case $type instanceof BitStringType: 2090b3fd2d3SAndreas Gohr $bytes = $this->encodeBitString($type); 2100b3fd2d3SAndreas Gohr break; 2110b3fd2d3SAndreas Gohr case $type instanceof OidType: 2120b3fd2d3SAndreas Gohr $bytes = $this->encodeOid($type); 2130b3fd2d3SAndreas Gohr break; 2140b3fd2d3SAndreas Gohr case $type instanceof RelativeOidType: 2150b3fd2d3SAndreas Gohr $bytes = $this->encodeRelativeOid($type); 2160b3fd2d3SAndreas Gohr break; 2170b3fd2d3SAndreas Gohr case $type instanceof GeneralizedTimeType: 2180b3fd2d3SAndreas Gohr $bytes = $this->encodeGeneralizedTime($type); 2190b3fd2d3SAndreas Gohr break; 2200b3fd2d3SAndreas Gohr case $type instanceof UtcTimeType: 2210b3fd2d3SAndreas Gohr $bytes = $this->encodeUtcTime($type); 2220b3fd2d3SAndreas Gohr break; 2230b3fd2d3SAndreas Gohr case $type instanceof NullType: 2240b3fd2d3SAndreas Gohr $bytes = ''; 2250b3fd2d3SAndreas Gohr break; 2260b3fd2d3SAndreas Gohr default: 227fd0855ecSAndreas Gohr throw new EncoderException(sprintf( 228fd0855ecSAndreas Gohr 'The type "%s" is not currently supported.', 229fd0855ecSAndreas Gohr get_class($type) 230fd0855ecSAndreas Gohr )); 2310b3fd2d3SAndreas Gohr } 232*dad993c5SAndreas Gohr $length = strlen($bytes); 233*dad993c5SAndreas Gohr $bytes = ($length < 128) ? chr($length) . $bytes : $this->encodeLongDefiniteLength($length) . $bytes; 2340b3fd2d3SAndreas Gohr 2350b3fd2d3SAndreas Gohr # The first byte of a tag always contains the class (bits 8 and 7) and whether it is constructed (bit 6). 2360b3fd2d3SAndreas Gohr $tag = $type->getTagClass() | ($type->getIsConstructed() ? AbstractType::CONSTRUCTED_TYPE : 0); 2370b3fd2d3SAndreas Gohr 2380b3fd2d3SAndreas Gohr $this->validateNumericInt($type->getTagNumber()); 2390b3fd2d3SAndreas Gohr # For a high tag (>=31) we flip the first 5 bits on (0x1f) to make the first byte, then the subsequent bytes is 2400b3fd2d3SAndreas Gohr # the VLV encoding of the tag number. 2410b3fd2d3SAndreas Gohr if ($type->getTagNumber() >= 31) { 242*dad993c5SAndreas Gohr $bytes = chr($tag | 0x1f) . $this->intToVlqBytes($type->getTagNumber()) . $bytes; 2430b3fd2d3SAndreas Gohr # For a tag less than 31, everything fits comfortably into a single byte. 2440b3fd2d3SAndreas Gohr } else { 245*dad993c5SAndreas Gohr $bytes = chr($tag | $type->getTagNumber()) . $bytes; 2460b3fd2d3SAndreas Gohr } 2470b3fd2d3SAndreas Gohr 2480b3fd2d3SAndreas Gohr return $bytes; 2490b3fd2d3SAndreas Gohr } 2500b3fd2d3SAndreas Gohr 2510b3fd2d3SAndreas Gohr /** 2520b3fd2d3SAndreas Gohr * Map universal types to specific tag class values when decoding. 2530b3fd2d3SAndreas Gohr * 2540b3fd2d3SAndreas Gohr * @param int $class 2550b3fd2d3SAndreas Gohr * @param array $map 2560b3fd2d3SAndreas Gohr * @return $this 2570b3fd2d3SAndreas Gohr */ 2580b3fd2d3SAndreas Gohr public function setTagMap(int $class, array $map) 2590b3fd2d3SAndreas Gohr { 2600b3fd2d3SAndreas Gohr if (isset($this->tagMap[$class])) { 2610b3fd2d3SAndreas Gohr $this->tagMap[$class] = $map; 2620b3fd2d3SAndreas Gohr } 2630b3fd2d3SAndreas Gohr 2640b3fd2d3SAndreas Gohr return $this; 2650b3fd2d3SAndreas Gohr } 2660b3fd2d3SAndreas Gohr 2670b3fd2d3SAndreas Gohr /** 2680b3fd2d3SAndreas Gohr * Get the options for the encoder. 2690b3fd2d3SAndreas Gohr * 2700b3fd2d3SAndreas Gohr * @return array 2710b3fd2d3SAndreas Gohr */ 2720b3fd2d3SAndreas Gohr public function getOptions(): array 2730b3fd2d3SAndreas Gohr { 2740b3fd2d3SAndreas Gohr return $this->options; 2750b3fd2d3SAndreas Gohr } 2760b3fd2d3SAndreas Gohr 2770b3fd2d3SAndreas Gohr /** 2780b3fd2d3SAndreas Gohr * Set the options for the encoder. 2790b3fd2d3SAndreas Gohr * 2800b3fd2d3SAndreas Gohr * @param array $options 2810b3fd2d3SAndreas Gohr * @return $this 2820b3fd2d3SAndreas Gohr */ 2830b3fd2d3SAndreas Gohr public function setOptions(array $options) 2840b3fd2d3SAndreas Gohr { 285*dad993c5SAndreas Gohr if (isset($options['bitstring_padding']) && is_string($options['bitstring_padding'])) { 2860b3fd2d3SAndreas Gohr $this->options['bitstring_padding'] = $options['bitstring_padding']; 2870b3fd2d3SAndreas Gohr } 2880b3fd2d3SAndreas Gohr 2890b3fd2d3SAndreas Gohr return $this; 2900b3fd2d3SAndreas Gohr } 2910b3fd2d3SAndreas Gohr 2920b3fd2d3SAndreas Gohr /** 2930b3fd2d3SAndreas Gohr * @return int|null 2940b3fd2d3SAndreas Gohr */ 2950b3fd2d3SAndreas Gohr public function getLastPosition(): ?int 2960b3fd2d3SAndreas Gohr { 2970b3fd2d3SAndreas Gohr return $this->lastPos; 2980b3fd2d3SAndreas Gohr } 2990b3fd2d3SAndreas Gohr 3000b3fd2d3SAndreas Gohr protected function startEncoding(string $binary, array $tagMap): void 3010b3fd2d3SAndreas Gohr { 3020b3fd2d3SAndreas Gohr $this->tmpTagMap = $tagMap + $this->tagMap; 3030b3fd2d3SAndreas Gohr $this->binary = $binary; 3040b3fd2d3SAndreas Gohr $this->lastPos = null; 3050b3fd2d3SAndreas Gohr $this->pos = 0; 306*dad993c5SAndreas Gohr $this->maxLen = strlen($this->binary); 3070b3fd2d3SAndreas Gohr } 3080b3fd2d3SAndreas Gohr 3090b3fd2d3SAndreas Gohr protected function stopEncoding(): void 3100b3fd2d3SAndreas Gohr { 3110b3fd2d3SAndreas Gohr $this->tmpTagMap = []; 3120b3fd2d3SAndreas Gohr $this->binary = null; 3130b3fd2d3SAndreas Gohr $this->maxLen = 0; 3140b3fd2d3SAndreas Gohr $this->lastPos = $this->pos; 3150b3fd2d3SAndreas Gohr $this->pos = 0; 3160b3fd2d3SAndreas Gohr } 3170b3fd2d3SAndreas Gohr 3180b3fd2d3SAndreas Gohr /** 3190b3fd2d3SAndreas Gohr * @param bool $isRoot 3200b3fd2d3SAndreas Gohr * @param null|int $tagType 3210b3fd2d3SAndreas Gohr * @param null|int $length 3220b3fd2d3SAndreas Gohr * @param null|bool $isConstructed 3230b3fd2d3SAndreas Gohr * @param null|int $class 3240b3fd2d3SAndreas Gohr * @return AbstractType 3250b3fd2d3SAndreas Gohr * @throws EncoderException 3260b3fd2d3SAndreas Gohr * @throws PartialPduException 3270b3fd2d3SAndreas Gohr */ 3280b3fd2d3SAndreas Gohr protected function decodeBytes(bool $isRoot = false, $tagType = null, $length = null, $isConstructed = null, $class = null): AbstractType 3290b3fd2d3SAndreas Gohr { 3300b3fd2d3SAndreas Gohr $tagNumber = $tagType; 3310b3fd2d3SAndreas Gohr if ($tagType === null) { 332*dad993c5SAndreas Gohr $tag = ord($this->binary[$this->pos++]); 3330b3fd2d3SAndreas Gohr $class = $tag & 0xc0; 3340b3fd2d3SAndreas Gohr $isConstructed = (bool)($tag & AbstractType::CONSTRUCTED_TYPE); 3350b3fd2d3SAndreas Gohr $tagNumber = $tag & ~0xe0; 3360b3fd2d3SAndreas Gohr 3370b3fd2d3SAndreas Gohr # Less than or equal to 30 is a low tag number represented in a single byte. 3380b3fd2d3SAndreas Gohr # A high tag number is determined using VLQ (like the OID identifier encoding) of the subsequent bytes. 3390b3fd2d3SAndreas Gohr if ($tagNumber > 30) { 3400b3fd2d3SAndreas Gohr try { 3410b3fd2d3SAndreas Gohr $tagNumber = $this->getVlqBytesToInt(); 3420b3fd2d3SAndreas Gohr # It's possible we only got part of the VLQ for the high tag, as there is no way to know its ending length. 3430b3fd2d3SAndreas Gohr } catch (EncoderException $e) { 3440b3fd2d3SAndreas Gohr if ($isRoot) { 3450b3fd2d3SAndreas Gohr throw new PartialPduException( 3460b3fd2d3SAndreas Gohr 'Not enough data to decode the high tag number. No ending byte encountered for the VLQ bytes.' 3470b3fd2d3SAndreas Gohr ); 3480b3fd2d3SAndreas Gohr } 3490b3fd2d3SAndreas Gohr throw $e; 3500b3fd2d3SAndreas Gohr } 3510b3fd2d3SAndreas Gohr } 3520b3fd2d3SAndreas Gohr 353*dad993c5SAndreas Gohr $length = ord($this->binary[$this->pos++]); 3540b3fd2d3SAndreas Gohr if ($length === 128) { 3550b3fd2d3SAndreas Gohr throw new EncoderException('Indefinite length encoding is not currently supported.'); 3560b3fd2d3SAndreas Gohr } 3570b3fd2d3SAndreas Gohr if ($length > 128) { 3580b3fd2d3SAndreas Gohr $length = $this->decodeLongDefiniteLength($length); 3590b3fd2d3SAndreas Gohr } 3600b3fd2d3SAndreas Gohr $tagType = ($class === AbstractType::TAG_CLASS_UNIVERSAL) ? $tagNumber : ($this->tmpTagMap[$class][$tagNumber] ?? null); 3610b3fd2d3SAndreas Gohr 3620b3fd2d3SAndreas Gohr if (($this->maxLen - $this->pos) < $length) { 3630b3fd2d3SAndreas Gohr $message = sprintf( 3640b3fd2d3SAndreas Gohr 'The expected byte length was %s, but received %s.', 3650b3fd2d3SAndreas Gohr $length, 3660b3fd2d3SAndreas Gohr ($this->maxLen - $this->pos) 3670b3fd2d3SAndreas Gohr ); 3680b3fd2d3SAndreas Gohr if ($isRoot) { 3690b3fd2d3SAndreas Gohr throw new PartialPduException($message); 3700b3fd2d3SAndreas Gohr } else { 3710b3fd2d3SAndreas Gohr throw new EncoderException($message); 3720b3fd2d3SAndreas Gohr } 3730b3fd2d3SAndreas Gohr } 3740b3fd2d3SAndreas Gohr 3750b3fd2d3SAndreas Gohr if ($tagType === null) { 376*dad993c5SAndreas Gohr $type = new IncompleteType(substr($this->binary, $this->pos, $length), $tagNumber, $class, $isConstructed); 3770b3fd2d3SAndreas Gohr $this->pos += $length; 3780b3fd2d3SAndreas Gohr 3790b3fd2d3SAndreas Gohr return $type; 3800b3fd2d3SAndreas Gohr } 3810b3fd2d3SAndreas Gohr } 3820b3fd2d3SAndreas Gohr 3830b3fd2d3SAndreas Gohr # Yes...this huge switch statement should be a separate method. However, it is faster inline when decoding 3840b3fd2d3SAndreas Gohr # lots of data (such as thousands of ASN.1 structures at a time). 3850b3fd2d3SAndreas Gohr switch ($tagType) { 3860b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_BOOLEAN: 3870b3fd2d3SAndreas Gohr if ($length !== 1 || $isConstructed) { 3880b3fd2d3SAndreas Gohr throw new EncoderException('The encoded boolean type is malformed.'); 3890b3fd2d3SAndreas Gohr } 3900b3fd2d3SAndreas Gohr $type = EncodedType\BooleanType::withTag($tagNumber, $class, $this->decodeBoolean()); 3910b3fd2d3SAndreas Gohr break; 3920b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_NULL: 3930b3fd2d3SAndreas Gohr if ($length !== 0 || $isConstructed) { 3940b3fd2d3SAndreas Gohr throw new EncoderException('The encoded null type is malformed.'); 3950b3fd2d3SAndreas Gohr } 3960b3fd2d3SAndreas Gohr $type = EncodedType\NullType::withTag($tagNumber, $class); 3970b3fd2d3SAndreas Gohr break; 3980b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_INTEGER: 3990b3fd2d3SAndreas Gohr if ($isConstructed) { 4000b3fd2d3SAndreas Gohr throw new EncoderException('The encoded integer type is malformed.'); 4010b3fd2d3SAndreas Gohr } 4020b3fd2d3SAndreas Gohr $type = EncodedType\IntegerType::withTag($tagNumber, $class, $this->decodeInteger($length)); 4030b3fd2d3SAndreas Gohr break; 4040b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_ENUMERATED: 4050b3fd2d3SAndreas Gohr if ($isConstructed) { 4060b3fd2d3SAndreas Gohr throw new EncoderException('The encoded enumerated type is malformed.'); 4070b3fd2d3SAndreas Gohr } 4080b3fd2d3SAndreas Gohr $type = EncodedType\EnumeratedType::withTag($tagNumber, $class, $this->decodeInteger($length)); 4090b3fd2d3SAndreas Gohr break; 4100b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_REAL: 4110b3fd2d3SAndreas Gohr if ($isConstructed) { 4120b3fd2d3SAndreas Gohr throw new EncoderException('The encoded real type is malformed.'); 4130b3fd2d3SAndreas Gohr } 4140b3fd2d3SAndreas Gohr $type = RealType::withTag($tagNumber, $class, $this->decodeReal($length)); 4150b3fd2d3SAndreas Gohr break; 4160b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_BIT_STRING: 4170b3fd2d3SAndreas Gohr $type = EncodedType\BitStringType::withTag($tagNumber, $class, $isConstructed, $this->decodeBitString($length)); 4180b3fd2d3SAndreas Gohr break; 4190b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_OID: 4200b3fd2d3SAndreas Gohr if ($isConstructed) { 4210b3fd2d3SAndreas Gohr throw new EncoderException('The encoded OID type is malformed.'); 4220b3fd2d3SAndreas Gohr } 4230b3fd2d3SAndreas Gohr $type = OidType::withTag($tagNumber, $class, $this->decodeOid($length)); 4240b3fd2d3SAndreas Gohr break; 4250b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_RELATIVE_OID: 4260b3fd2d3SAndreas Gohr if ($isConstructed) { 4270b3fd2d3SAndreas Gohr throw new EncoderException('The encoded relative OID type is malformed.'); 4280b3fd2d3SAndreas Gohr } 4290b3fd2d3SAndreas Gohr $type = RelativeOidType::withTag($tagNumber, $class, $this->decodeRelativeOid($length)); 4300b3fd2d3SAndreas Gohr break; 4310b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_GENERALIZED_TIME: 4320b3fd2d3SAndreas Gohr $type = EncodedType\GeneralizedTimeType::withTag($tagNumber, $class, $isConstructed, ...$this->decodeGeneralizedTime($length)); 4330b3fd2d3SAndreas Gohr break; 4340b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_UTC_TIME: 4350b3fd2d3SAndreas Gohr $type = EncodedType\UtcTimeType::withTag($tagNumber, $class, $isConstructed, ...$this->decodeUtcTime($length)); 4360b3fd2d3SAndreas Gohr break; 4370b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_OCTET_STRING: 438*dad993c5SAndreas Gohr $type = EncodedType\OctetStringType::withTag($tagNumber, $class, $isConstructed, substr($this->binary, $this->pos, $length)); 4390b3fd2d3SAndreas Gohr $this->pos += $length; 4400b3fd2d3SAndreas Gohr break; 4410b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_GENERAL_STRING: 442*dad993c5SAndreas Gohr $type = EncodedType\GeneralStringType::withTag($tagNumber, $class, $isConstructed, substr($this->binary, $this->pos, $length)); 4430b3fd2d3SAndreas Gohr $this->pos += $length; 4440b3fd2d3SAndreas Gohr break; 4450b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_VISIBLE_STRING: 446*dad993c5SAndreas Gohr $type = EncodedType\VisibleStringType::withTag($tagNumber, $class, $isConstructed, substr($this->binary, $this->pos, $length)); 4470b3fd2d3SAndreas Gohr $this->pos += $length; 4480b3fd2d3SAndreas Gohr break; 4490b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_BMP_STRING: 450*dad993c5SAndreas Gohr $type = EncodedType\BmpStringType::withTag($tagNumber, $class, $isConstructed, substr($this->binary, $this->pos, $length)); 4510b3fd2d3SAndreas Gohr $this->pos += $length; 4520b3fd2d3SAndreas Gohr break; 4530b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_CHARACTER_STRING: 454*dad993c5SAndreas Gohr $type = EncodedType\CharacterStringType::withTag($tagNumber, $class, $isConstructed, substr($this->binary, $this->pos, $length)); 4550b3fd2d3SAndreas Gohr $this->pos += $length; 4560b3fd2d3SAndreas Gohr break; 4570b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_UNIVERSAL_STRING: 458*dad993c5SAndreas Gohr $type = EncodedType\UniversalStringType::withTag($tagNumber, $class, $isConstructed, substr($this->binary, $this->pos, $length)); 4590b3fd2d3SAndreas Gohr $this->pos += $length; 4600b3fd2d3SAndreas Gohr break; 4610b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_GRAPHIC_STRING: 462*dad993c5SAndreas Gohr $type = EncodedType\GraphicStringType::withTag($tagNumber, $class, $isConstructed, substr($this->binary, $this->pos, $length)); 4630b3fd2d3SAndreas Gohr $this->pos += $length; 4640b3fd2d3SAndreas Gohr break; 4650b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_VIDEOTEX_STRING: 466*dad993c5SAndreas Gohr $type = EncodedType\VideotexStringType::withTag($tagNumber, $class, $isConstructed, substr($this->binary, $this->pos, $length)); 4670b3fd2d3SAndreas Gohr $this->pos += $length; 4680b3fd2d3SAndreas Gohr break; 4690b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_TELETEX_STRING: 470*dad993c5SAndreas Gohr $type = EncodedType\TeletexStringType::withTag($tagNumber, $class, $isConstructed, substr($this->binary, $this->pos, $length)); 4710b3fd2d3SAndreas Gohr $this->pos += $length; 4720b3fd2d3SAndreas Gohr break; 4730b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_PRINTABLE_STRING: 474*dad993c5SAndreas Gohr $type = EncodedType\PrintableStringType::withTag($tagNumber, $class, $isConstructed, substr($this->binary, $this->pos, $length)); 4750b3fd2d3SAndreas Gohr $this->pos += $length; 4760b3fd2d3SAndreas Gohr break; 4770b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_NUMERIC_STRING: 478*dad993c5SAndreas Gohr $type = EncodedType\NumericStringType::withTag($tagNumber, $class, $isConstructed, substr($this->binary, $this->pos, $length)); 4790b3fd2d3SAndreas Gohr $this->pos += $length; 4800b3fd2d3SAndreas Gohr break; 4810b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_IA5_STRING: 482*dad993c5SAndreas Gohr $type = EncodedType\IA5StringType::withTag($tagNumber, $class, $isConstructed, substr($this->binary, $this->pos, $length)); 4830b3fd2d3SAndreas Gohr $this->pos += $length; 4840b3fd2d3SAndreas Gohr break; 4850b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_UTF8_STRING: 486*dad993c5SAndreas Gohr $type = EncodedType\Utf8StringType::withTag($tagNumber, $class, $isConstructed, substr($this->binary, $this->pos, $length)); 4870b3fd2d3SAndreas Gohr $this->pos += $length; 4880b3fd2d3SAndreas Gohr break; 4890b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_SEQUENCE: 4900b3fd2d3SAndreas Gohr if (!$isConstructed) { 4910b3fd2d3SAndreas Gohr throw new EncoderException('The encoded sequence type is malformed.'); 4920b3fd2d3SAndreas Gohr } 4930b3fd2d3SAndreas Gohr $type = EncodedType\SequenceType::withTag($tagNumber, $class, $this->decodeConstructedType($length)); 4940b3fd2d3SAndreas Gohr break; 4950b3fd2d3SAndreas Gohr case AbstractType::TAG_TYPE_SET: 4960b3fd2d3SAndreas Gohr if (!$isConstructed) { 4970b3fd2d3SAndreas Gohr throw new EncoderException('The encoded set type is malformed.'); 4980b3fd2d3SAndreas Gohr } 4990b3fd2d3SAndreas Gohr $type = EncodedType\SetType::withTag($tagNumber, $class, $this->decodeConstructedType($length)); 5000b3fd2d3SAndreas Gohr break; 5010b3fd2d3SAndreas Gohr default: 5020b3fd2d3SAndreas Gohr throw new EncoderException(sprintf('Unable to decode value to a type for tag %s.', $tagType)); 5030b3fd2d3SAndreas Gohr } 5040b3fd2d3SAndreas Gohr 5050b3fd2d3SAndreas Gohr return $type; 5060b3fd2d3SAndreas Gohr } 5070b3fd2d3SAndreas Gohr 5080b3fd2d3SAndreas Gohr /** 5090b3fd2d3SAndreas Gohr * @param int $length 5100b3fd2d3SAndreas Gohr * @return int 5110b3fd2d3SAndreas Gohr * @throws EncoderException 5120b3fd2d3SAndreas Gohr * @throws PartialPduException 5130b3fd2d3SAndreas Gohr */ 5140b3fd2d3SAndreas Gohr protected function decodeLongDefiniteLength(int $length): int 5150b3fd2d3SAndreas Gohr { 5160b3fd2d3SAndreas Gohr # The length of the length bytes is in the first 7 bits. So remove the MSB to get the value. 5170b3fd2d3SAndreas Gohr $lengthOfLength = $length & ~0x80; 5180b3fd2d3SAndreas Gohr 5190b3fd2d3SAndreas Gohr # The value of 127 is marked as reserved in the spec 5200b3fd2d3SAndreas Gohr if ($lengthOfLength === 127) { 5210b3fd2d3SAndreas Gohr throw new EncoderException('The decoded length cannot be equal to 127 bytes.'); 5220b3fd2d3SAndreas Gohr } 523fd0855ecSAndreas Gohr if ($lengthOfLength > ($this->maxLen - $this->pos)) { 5240b3fd2d3SAndreas Gohr throw new PartialPduException('Not enough data to decode the length.'); 5250b3fd2d3SAndreas Gohr } 5260b3fd2d3SAndreas Gohr $endAt = $this->pos + $lengthOfLength; 5270b3fd2d3SAndreas Gohr 5280b3fd2d3SAndreas Gohr # Base 256 encoded 5290b3fd2d3SAndreas Gohr $length = 0; 5300b3fd2d3SAndreas Gohr for ($this->pos; $this->pos < $endAt; $this->pos++) { 531*dad993c5SAndreas Gohr $length = $length * 256 + ord($this->binary[$this->pos]); 5320b3fd2d3SAndreas Gohr } 5330b3fd2d3SAndreas Gohr 5340b3fd2d3SAndreas Gohr return $length; 5350b3fd2d3SAndreas Gohr } 5360b3fd2d3SAndreas Gohr 5370b3fd2d3SAndreas Gohr /** 5380b3fd2d3SAndreas Gohr * Given what should be VLQ bytes represent an int, get the int and the length of bytes. 5390b3fd2d3SAndreas Gohr * 5400b3fd2d3SAndreas Gohr * @return string|int 5410b3fd2d3SAndreas Gohr * @throws EncoderException 5420b3fd2d3SAndreas Gohr */ 5430b3fd2d3SAndreas Gohr protected function getVlqBytesToInt() 5440b3fd2d3SAndreas Gohr { 5450b3fd2d3SAndreas Gohr $value = 0; 546fd0855ecSAndreas Gohr $lshift = 0; 5470b3fd2d3SAndreas Gohr $isBigInt = false; 5480b3fd2d3SAndreas Gohr 5490b3fd2d3SAndreas Gohr for ($this->pos; $this->pos < $this->maxLen; $this->pos++) { 5500b3fd2d3SAndreas Gohr if (!$isBigInt) { 5510b3fd2d3SAndreas Gohr $lshift = $value << 7; 552fd0855ecSAndreas Gohr # An overflow bitshift will result in a negative number or zero. 553fd0855ecSAndreas Gohr # This will check if GMP is available and flip it to a bigint safe method in one shot. 554fd0855ecSAndreas Gohr if ($value > 0 && $lshift <= 0) { 5550b3fd2d3SAndreas Gohr $isBigInt = true; 5560b3fd2d3SAndreas Gohr $this->throwIfBigIntGmpNeeded(true); 557*dad993c5SAndreas Gohr $value = gmp_init($value); 5580b3fd2d3SAndreas Gohr } 5590b3fd2d3SAndreas Gohr } 5600b3fd2d3SAndreas Gohr if ($isBigInt) { 561*dad993c5SAndreas Gohr $lshift = gmp_mul($value, gmp_pow('2', 7)); 5620b3fd2d3SAndreas Gohr } 563*dad993c5SAndreas Gohr $orVal = (ord($this->binary[$this->pos]) & 0x7f); 5640b3fd2d3SAndreas Gohr if ($isBigInt) { 565*dad993c5SAndreas Gohr $value = gmp_or($lshift, gmp_init($orVal)); 5660b3fd2d3SAndreas Gohr } else { 5670b3fd2d3SAndreas Gohr $value = $lshift | $orVal; 5680b3fd2d3SAndreas Gohr } 5690b3fd2d3SAndreas Gohr # We have reached the last byte if the MSB is not set. 570*dad993c5SAndreas Gohr if ((ord($this->binary[$this->pos]) & 0x80) === 0) { 5710b3fd2d3SAndreas Gohr $this->pos++; 572fd0855ecSAndreas Gohr 573*dad993c5SAndreas Gohr return $isBigInt ? gmp_strval($value) : $value; 5740b3fd2d3SAndreas Gohr } 5750b3fd2d3SAndreas Gohr } 5760b3fd2d3SAndreas Gohr 5770b3fd2d3SAndreas Gohr throw new EncoderException('Expected an ending byte to decode a VLQ, but none was found.'); 5780b3fd2d3SAndreas Gohr } 5790b3fd2d3SAndreas Gohr 5800b3fd2d3SAndreas Gohr /** 5810b3fd2d3SAndreas Gohr * Get the bytes that represent variable length quantity. 5820b3fd2d3SAndreas Gohr * 5830b3fd2d3SAndreas Gohr * @param string|int $int 5840b3fd2d3SAndreas Gohr * @return string 5850b3fd2d3SAndreas Gohr * @throws EncoderException 5860b3fd2d3SAndreas Gohr */ 5870b3fd2d3SAndreas Gohr protected function intToVlqBytes($int) 5880b3fd2d3SAndreas Gohr { 589*dad993c5SAndreas Gohr $bigint = is_float($int + 0); 5900b3fd2d3SAndreas Gohr $this->throwIfBigIntGmpNeeded($bigint); 5910b3fd2d3SAndreas Gohr 5920b3fd2d3SAndreas Gohr if ($bigint) { 593*dad993c5SAndreas Gohr $int = gmp_init($int); 594*dad993c5SAndreas Gohr $bytes = chr(gmp_intval(gmp_and(gmp_init(0x7f), $int))); 595*dad993c5SAndreas Gohr $int = gmp_div($int, gmp_pow(2, 7)); 596*dad993c5SAndreas Gohr $intVal = gmp_intval($int); 5970b3fd2d3SAndreas Gohr } else { 598*dad993c5SAndreas Gohr $bytes = chr(0x7f & $int); 5990b3fd2d3SAndreas Gohr $int >>= 7; 6000b3fd2d3SAndreas Gohr $intVal = $int; 6010b3fd2d3SAndreas Gohr } 6020b3fd2d3SAndreas Gohr 6030b3fd2d3SAndreas Gohr while ($intVal > 0) { 6040b3fd2d3SAndreas Gohr if ($bigint) { 605*dad993c5SAndreas Gohr $bytes = chr(gmp_intval(gmp_or(gmp_and(gmp_init(0x7f), $int), gmp_init(0x80)))) . $bytes; 606*dad993c5SAndreas Gohr $int = gmp_div($int, gmp_pow('2', 7)); 607*dad993c5SAndreas Gohr $intVal = gmp_intval($int); 6080b3fd2d3SAndreas Gohr } else { 609*dad993c5SAndreas Gohr $bytes = chr((0x7f & $int) | 0x80) . $bytes; 6100b3fd2d3SAndreas Gohr $int >>= 7; 6110b3fd2d3SAndreas Gohr $intVal = $int; 6120b3fd2d3SAndreas Gohr } 6130b3fd2d3SAndreas Gohr } 6140b3fd2d3SAndreas Gohr 6150b3fd2d3SAndreas Gohr return $bytes; 6160b3fd2d3SAndreas Gohr } 6170b3fd2d3SAndreas Gohr 6180b3fd2d3SAndreas Gohr /** 6190b3fd2d3SAndreas Gohr * @param string|integer $integer 6200b3fd2d3SAndreas Gohr * @throws EncoderException 6210b3fd2d3SAndreas Gohr */ 6220b3fd2d3SAndreas Gohr protected function validateNumericInt($integer): void 6230b3fd2d3SAndreas Gohr { 624*dad993c5SAndreas Gohr if (is_int($integer)) { 6250b3fd2d3SAndreas Gohr return; 6260b3fd2d3SAndreas Gohr } 627*dad993c5SAndreas Gohr if (is_string($integer) && is_numeric($integer) && strpos($integer, '.') === false) { 6280b3fd2d3SAndreas Gohr return; 6290b3fd2d3SAndreas Gohr } 6300b3fd2d3SAndreas Gohr 631fd0855ecSAndreas Gohr throw new EncoderException(sprintf( 632fd0855ecSAndreas Gohr 'The value to encode for "%s" must be numeric.', 633fd0855ecSAndreas Gohr $integer 634fd0855ecSAndreas Gohr )); 6350b3fd2d3SAndreas Gohr } 6360b3fd2d3SAndreas Gohr 6370b3fd2d3SAndreas Gohr /** 6380b3fd2d3SAndreas Gohr * @param int $num 6390b3fd2d3SAndreas Gohr * @return string 6400b3fd2d3SAndreas Gohr * @throws EncoderException 6410b3fd2d3SAndreas Gohr */ 6420b3fd2d3SAndreas Gohr protected function encodeLongDefiniteLength(int $num) 6430b3fd2d3SAndreas Gohr { 6440b3fd2d3SAndreas Gohr $bytes = ''; 6450b3fd2d3SAndreas Gohr while ($num) { 646*dad993c5SAndreas Gohr $bytes = (chr((int) ($num % 256))) . $bytes; 6470b3fd2d3SAndreas Gohr $num = (int) ($num / 256); 6480b3fd2d3SAndreas Gohr } 6490b3fd2d3SAndreas Gohr 650*dad993c5SAndreas Gohr $length = strlen($bytes); 6510b3fd2d3SAndreas Gohr if ($length >= 127) { 6520b3fd2d3SAndreas Gohr throw new EncoderException('The encoded length cannot be greater than or equal to 127 bytes'); 6530b3fd2d3SAndreas Gohr } 6540b3fd2d3SAndreas Gohr 655*dad993c5SAndreas Gohr return chr(0x80 | $length) . $bytes; 6560b3fd2d3SAndreas Gohr } 6570b3fd2d3SAndreas Gohr 6580b3fd2d3SAndreas Gohr /** 6590b3fd2d3SAndreas Gohr * @param BitStringType $type 6600b3fd2d3SAndreas Gohr * @return string 6610b3fd2d3SAndreas Gohr */ 6620b3fd2d3SAndreas Gohr protected function encodeBitString(BitStringType $type) 6630b3fd2d3SAndreas Gohr { 6640b3fd2d3SAndreas Gohr $data = $type->getValue(); 665*dad993c5SAndreas Gohr $length = strlen($data); 6660b3fd2d3SAndreas Gohr $unused = 0; 6670b3fd2d3SAndreas Gohr if ($length % 8) { 6680b3fd2d3SAndreas Gohr $unused = 8 - ($length % 8); 669*dad993c5SAndreas Gohr $data = str_pad($data, $length + $unused, $this->options['bitstring_padding']); 670*dad993c5SAndreas Gohr $length = strlen($data); 6710b3fd2d3SAndreas Gohr } 6720b3fd2d3SAndreas Gohr 673*dad993c5SAndreas Gohr $bytes = chr($unused); 6740b3fd2d3SAndreas Gohr for ($i = 0; $i < $length / 8; $i++) { 675*dad993c5SAndreas Gohr $bytes .= chr(bindec(substr($data, $i * 8, 8))); 6760b3fd2d3SAndreas Gohr } 6770b3fd2d3SAndreas Gohr 6780b3fd2d3SAndreas Gohr return $bytes; 6790b3fd2d3SAndreas Gohr } 6800b3fd2d3SAndreas Gohr 6810b3fd2d3SAndreas Gohr /** 6820b3fd2d3SAndreas Gohr * @param RelativeOidType $type 6830b3fd2d3SAndreas Gohr * @return string 6840b3fd2d3SAndreas Gohr * @throws EncoderException 6850b3fd2d3SAndreas Gohr */ 6860b3fd2d3SAndreas Gohr protected function encodeRelativeOid(RelativeOidType $type) 6870b3fd2d3SAndreas Gohr { 688*dad993c5SAndreas Gohr $oids = explode('.', $type->getValue()); 6890b3fd2d3SAndreas Gohr 6900b3fd2d3SAndreas Gohr $bytes = ''; 6910b3fd2d3SAndreas Gohr foreach ($oids as $oid) { 6920b3fd2d3SAndreas Gohr $bytes .= $this->intToVlqBytes($oid); 6930b3fd2d3SAndreas Gohr } 6940b3fd2d3SAndreas Gohr 6950b3fd2d3SAndreas Gohr return $bytes; 6960b3fd2d3SAndreas Gohr } 6970b3fd2d3SAndreas Gohr 6980b3fd2d3SAndreas Gohr /** 6990b3fd2d3SAndreas Gohr * @param OidType $type 7000b3fd2d3SAndreas Gohr * @return string 7010b3fd2d3SAndreas Gohr * @throws EncoderException 7020b3fd2d3SAndreas Gohr */ 7030b3fd2d3SAndreas Gohr protected function encodeOid(OidType $type) 7040b3fd2d3SAndreas Gohr { 705fd0855ecSAndreas Gohr /** @var int[] $oids */ 706*dad993c5SAndreas Gohr $oids = explode('.', $type->getValue()); 707*dad993c5SAndreas Gohr $length = count($oids); 7080b3fd2d3SAndreas Gohr if ($length < 2) { 709fd0855ecSAndreas Gohr throw new EncoderException(sprintf( 710fd0855ecSAndreas Gohr 'To encode the OID it must have at least 2 components: %s', 711fd0855ecSAndreas Gohr $type->getValue() 712fd0855ecSAndreas Gohr )); 713fd0855ecSAndreas Gohr } 714fd0855ecSAndreas Gohr if ($oids[0] > 2) { 715fd0855ecSAndreas Gohr throw new EncoderException(sprintf( 716fd0855ecSAndreas Gohr 'The value of the first OID component cannot be greater than 2. Received: %s', 717fd0855ecSAndreas Gohr $oids[0] 718fd0855ecSAndreas Gohr )); 7190b3fd2d3SAndreas Gohr } 7200b3fd2d3SAndreas Gohr 721fd0855ecSAndreas Gohr # The first and second components of the OID are represented using the formula: (X * 40) + Y 722fd0855ecSAndreas Gohr if ($oids[1] > self::MAX_SECOND_COMPONENT) { 723fd0855ecSAndreas Gohr $this->throwIfBigIntGmpNeeded(true); 724*dad993c5SAndreas Gohr $firstAndSecond = gmp_strval(gmp_add((string)($oids[0] * 40), $oids[1])); 725fd0855ecSAndreas Gohr } else { 726fd0855ecSAndreas Gohr $firstAndSecond = ($oids[0] * 40) + $oids[1]; 727fd0855ecSAndreas Gohr } 728fd0855ecSAndreas Gohr $bytes = $this->intToVlqBytes($firstAndSecond); 7290b3fd2d3SAndreas Gohr 7300b3fd2d3SAndreas Gohr for ($i = 2; $i < $length; $i++) { 7310b3fd2d3SAndreas Gohr $bytes .= $this->intToVlqBytes($oids[$i]); 7320b3fd2d3SAndreas Gohr } 7330b3fd2d3SAndreas Gohr 7340b3fd2d3SAndreas Gohr return $bytes; 7350b3fd2d3SAndreas Gohr } 7360b3fd2d3SAndreas Gohr 7370b3fd2d3SAndreas Gohr /** 7380b3fd2d3SAndreas Gohr * @param GeneralizedTimeType $type 7390b3fd2d3SAndreas Gohr * @return string 7400b3fd2d3SAndreas Gohr * @throws EncoderException 7410b3fd2d3SAndreas Gohr */ 7420b3fd2d3SAndreas Gohr protected function encodeGeneralizedTime(GeneralizedTimeType $type) 7430b3fd2d3SAndreas Gohr { 7440b3fd2d3SAndreas Gohr return $this->encodeTime($type, 'YmdH'); 7450b3fd2d3SAndreas Gohr } 7460b3fd2d3SAndreas Gohr 7470b3fd2d3SAndreas Gohr /** 7480b3fd2d3SAndreas Gohr * @param UtcTimeType $type 7490b3fd2d3SAndreas Gohr * @return string 7500b3fd2d3SAndreas Gohr * @throws EncoderException 7510b3fd2d3SAndreas Gohr */ 7520b3fd2d3SAndreas Gohr protected function encodeUtcTime(UtcTimeType $type) 7530b3fd2d3SAndreas Gohr { 7540b3fd2d3SAndreas Gohr return $this->encodeTime($type, 'ymdH'); 7550b3fd2d3SAndreas Gohr } 7560b3fd2d3SAndreas Gohr 7570b3fd2d3SAndreas Gohr /** 7580b3fd2d3SAndreas Gohr * @param AbstractTimeType $type 7590b3fd2d3SAndreas Gohr * @param string $format 7600b3fd2d3SAndreas Gohr * @return string 7610b3fd2d3SAndreas Gohr * @throws EncoderException 7620b3fd2d3SAndreas Gohr */ 7630b3fd2d3SAndreas Gohr protected function encodeTime(AbstractTimeType $type, string $format) 7640b3fd2d3SAndreas Gohr { 7650b3fd2d3SAndreas Gohr if ($type->getDateTimeFormat() === GeneralizedTimeType::FORMAT_SECONDS || $type->getDateTimeFormat() === GeneralizedTimeType::FORMAT_FRACTIONS) { 7660b3fd2d3SAndreas Gohr $format .= 'is'; 7670b3fd2d3SAndreas Gohr } elseif ($type->getDateTimeFormat() === GeneralizedTimeType::FORMAT_MINUTES) { 7680b3fd2d3SAndreas Gohr $format .= 'i'; 7690b3fd2d3SAndreas Gohr } 7700b3fd2d3SAndreas Gohr 7710b3fd2d3SAndreas Gohr # Is it possible to construct a datetime object in this way? Seems better to be safe with this check. 7720b3fd2d3SAndreas Gohr if ($type->getValue()->format('H') === '24') { 7730b3fd2d3SAndreas Gohr throw new EncoderException('Midnight must only be specified by 00, not 24.'); 7740b3fd2d3SAndreas Gohr } 7750b3fd2d3SAndreas Gohr 7760b3fd2d3SAndreas Gohr return $this->formatDateTime( 7770b3fd2d3SAndreas Gohr clone $type->getValue(), 7780b3fd2d3SAndreas Gohr $type->getDateTimeFormat(), 7790b3fd2d3SAndreas Gohr $type->getTimeZoneFormat(), 7800b3fd2d3SAndreas Gohr $format 7810b3fd2d3SAndreas Gohr ); 7820b3fd2d3SAndreas Gohr } 7830b3fd2d3SAndreas Gohr 7840b3fd2d3SAndreas Gohr /** 7850b3fd2d3SAndreas Gohr * @param \DateTime $dateTime 7860b3fd2d3SAndreas Gohr * @param string $dateTimeFormat 7870b3fd2d3SAndreas Gohr * @param string $tzFormat 7880b3fd2d3SAndreas Gohr * @param string $format 7890b3fd2d3SAndreas Gohr * @return string 7900b3fd2d3SAndreas Gohr */ 791*dad993c5SAndreas Gohr protected function formatDateTime(DateTime $dateTime, string $dateTimeFormat, string $tzFormat, string $format) 7920b3fd2d3SAndreas Gohr { 7930b3fd2d3SAndreas Gohr if ($tzFormat === GeneralizedTimeType::TZ_LOCAL) { 794*dad993c5SAndreas Gohr $dateTime->setTimezone(new DateTimeZone(date_default_timezone_get())); 7950b3fd2d3SAndreas Gohr } elseif ($tzFormat === GeneralizedTimeType::TZ_UTC) { 796*dad993c5SAndreas Gohr $dateTime->setTimezone(new DateTimeZone('UTC')); 7970b3fd2d3SAndreas Gohr } 7980b3fd2d3SAndreas Gohr $value = $dateTime->format($format); 7990b3fd2d3SAndreas Gohr 8000b3fd2d3SAndreas Gohr # Fractions need special formatting, so we cannot directly include them in the format above. 8010b3fd2d3SAndreas Gohr $ms = ''; 8020b3fd2d3SAndreas Gohr if ($dateTimeFormat === GeneralizedTimeType::FORMAT_FRACTIONS) { 803*dad993c5SAndreas Gohr $ms = (string) rtrim($dateTime->format('u'), '0'); 8040b3fd2d3SAndreas Gohr } 8050b3fd2d3SAndreas Gohr 8060b3fd2d3SAndreas Gohr $tz = ''; 8070b3fd2d3SAndreas Gohr if ($tzFormat === GeneralizedTimeType::TZ_UTC) { 8080b3fd2d3SAndreas Gohr $tz = 'Z'; 8090b3fd2d3SAndreas Gohr } elseif ($tzFormat === GeneralizedTimeType::TZ_DIFF) { 8100b3fd2d3SAndreas Gohr $tz = $dateTime->format('O'); 8110b3fd2d3SAndreas Gohr } 8120b3fd2d3SAndreas Gohr 8130b3fd2d3SAndreas Gohr return $value . ($ms !== '' ? '.' . $ms : '') . $tz; 8140b3fd2d3SAndreas Gohr } 8150b3fd2d3SAndreas Gohr 8160b3fd2d3SAndreas Gohr /** 817fd0855ecSAndreas Gohr * @param IntegerType|EnumeratedType $type 8180b3fd2d3SAndreas Gohr * @return string 8190b3fd2d3SAndreas Gohr * @throws EncoderException 8200b3fd2d3SAndreas Gohr */ 8210b3fd2d3SAndreas Gohr protected function encodeInteger(AbstractType $type): string 8220b3fd2d3SAndreas Gohr { 8230b3fd2d3SAndreas Gohr $int = $type->getValue(); 8240b3fd2d3SAndreas Gohr $this->validateNumericInt($int); 8250b3fd2d3SAndreas Gohr $isBigInt = $type->isBigInt(); 8260b3fd2d3SAndreas Gohr $isNegative = ($int < 0); 8270b3fd2d3SAndreas Gohr $this->throwIfBigIntGmpNeeded($isBigInt); 8280b3fd2d3SAndreas Gohr if ($isNegative) { 829*dad993c5SAndreas Gohr $int = $isBigInt ? gmp_abs($type->getValue()) : ($int * -1); 8300b3fd2d3SAndreas Gohr } 8310b3fd2d3SAndreas Gohr 8320b3fd2d3SAndreas Gohr # Subtract one for Two's Complement... 8330b3fd2d3SAndreas Gohr if ($isNegative) { 834*dad993c5SAndreas Gohr $int = $isBigInt ? gmp_sub($int, '1') : $int - 1; 8350b3fd2d3SAndreas Gohr } 8360b3fd2d3SAndreas Gohr 8370b3fd2d3SAndreas Gohr if ($isBigInt) { 838*dad993c5SAndreas Gohr $bytes = gmp_export($int); 8390b3fd2d3SAndreas Gohr } else { 8400b3fd2d3SAndreas Gohr # dechex can produce uneven hex while hex2bin requires it to be even 841*dad993c5SAndreas Gohr $hex = dechex($int); 842*dad993c5SAndreas Gohr $bytes = hex2bin((strlen($hex) % 2) === 0 ? $hex : '0' . $hex); 8430b3fd2d3SAndreas Gohr } 8440b3fd2d3SAndreas Gohr 8450b3fd2d3SAndreas Gohr # Two's Complement, invert the bits... 8460b3fd2d3SAndreas Gohr if ($isNegative) { 847*dad993c5SAndreas Gohr $len = strlen($bytes); 8480b3fd2d3SAndreas Gohr for ($i = 0; $i < $len; $i++) { 849*dad993c5SAndreas Gohr /** @var string $bytes */ 8500b3fd2d3SAndreas Gohr $bytes[$i] = ~$bytes[$i]; 8510b3fd2d3SAndreas Gohr } 8520b3fd2d3SAndreas Gohr } 8530b3fd2d3SAndreas Gohr 8540b3fd2d3SAndreas Gohr # MSB == Most Significant Bit. The one used for the sign. 855*dad993c5SAndreas Gohr $msbSet = (bool) (ord($bytes[0]) & 0x80); 8560b3fd2d3SAndreas Gohr if (!$isNegative && $msbSet) { 8570b3fd2d3SAndreas Gohr $bytes = self::BOOL_FALSE . $bytes; 8580b3fd2d3SAndreas Gohr } elseif ($isNegative && !$msbSet) { 8590b3fd2d3SAndreas Gohr $bytes = self::BOOL_TRUE . $bytes; 8600b3fd2d3SAndreas Gohr } 8610b3fd2d3SAndreas Gohr 8620b3fd2d3SAndreas Gohr return $bytes; 8630b3fd2d3SAndreas Gohr } 8640b3fd2d3SAndreas Gohr 8650b3fd2d3SAndreas Gohr /** 8660b3fd2d3SAndreas Gohr * @param RealType $type 8670b3fd2d3SAndreas Gohr * @return string 8680b3fd2d3SAndreas Gohr * @throws EncoderException 8690b3fd2d3SAndreas Gohr */ 8700b3fd2d3SAndreas Gohr protected function encodeReal(RealType $type) 8710b3fd2d3SAndreas Gohr { 8720b3fd2d3SAndreas Gohr $real = $type->getValue(); 8730b3fd2d3SAndreas Gohr 8740b3fd2d3SAndreas Gohr # If the value is zero, the contents are omitted 8750b3fd2d3SAndreas Gohr if ($real === ((float) 0)) { 8760b3fd2d3SAndreas Gohr return ''; 8770b3fd2d3SAndreas Gohr } 8780b3fd2d3SAndreas Gohr # If this is infinity, then a single octet of 0x40 is used. 8790b3fd2d3SAndreas Gohr if ($real === INF) { 8800b3fd2d3SAndreas Gohr return "\x40"; 8810b3fd2d3SAndreas Gohr } 8820b3fd2d3SAndreas Gohr # If this is negative infinity, then a single octet of 0x41 is used. 8830b3fd2d3SAndreas Gohr if ($real === -INF) { 8840b3fd2d3SAndreas Gohr return "\x41"; 8850b3fd2d3SAndreas Gohr } 8860b3fd2d3SAndreas Gohr 8870b3fd2d3SAndreas Gohr // @todo Real type encoding/decoding is rather complex. Need to implement this yet. 8880b3fd2d3SAndreas Gohr throw new EncoderException('Real type encoding of this value not yet implemented.'); 8890b3fd2d3SAndreas Gohr } 8900b3fd2d3SAndreas Gohr 8910b3fd2d3SAndreas Gohr /** 8920b3fd2d3SAndreas Gohr * @param int $length 8930b3fd2d3SAndreas Gohr * @return array 8940b3fd2d3SAndreas Gohr * @throws EncoderException 8950b3fd2d3SAndreas Gohr */ 8960b3fd2d3SAndreas Gohr protected function decodeGeneralizedTime($length): array 8970b3fd2d3SAndreas Gohr { 8980b3fd2d3SAndreas Gohr return $this->decodeTime('YmdH', GeneralizedTimeType::TIME_REGEX, GeneralizedTimeType::REGEX_MAP, $length); 8990b3fd2d3SAndreas Gohr } 9000b3fd2d3SAndreas Gohr 9010b3fd2d3SAndreas Gohr /** 9020b3fd2d3SAndreas Gohr * @param int $length 9030b3fd2d3SAndreas Gohr * @return array 9040b3fd2d3SAndreas Gohr * @throws EncoderException 9050b3fd2d3SAndreas Gohr */ 9060b3fd2d3SAndreas Gohr protected function decodeUtcTime($length): array 9070b3fd2d3SAndreas Gohr { 9080b3fd2d3SAndreas Gohr return $this->decodeTime('ymdH', UtcTimeType::TIME_REGEX, UtcTimeType::REGEX_MAP, $length); 9090b3fd2d3SAndreas Gohr } 9100b3fd2d3SAndreas Gohr 9110b3fd2d3SAndreas Gohr /** 9120b3fd2d3SAndreas Gohr * @param string $format 9130b3fd2d3SAndreas Gohr * @param string $regex 9140b3fd2d3SAndreas Gohr * @param array $matchMap 9150b3fd2d3SAndreas Gohr * @param int $length 9160b3fd2d3SAndreas Gohr * @return array 9170b3fd2d3SAndreas Gohr * @throws EncoderException 9180b3fd2d3SAndreas Gohr */ 9190b3fd2d3SAndreas Gohr protected function decodeTime(string $format, string $regex, array $matchMap, $length): array 9200b3fd2d3SAndreas Gohr { 921*dad993c5SAndreas Gohr $bytes = substr($this->binary, $this->pos, $length); 9220b3fd2d3SAndreas Gohr $this->pos += $length; 923*dad993c5SAndreas Gohr if (!preg_match($regex, $bytes, $matches)) { 9240b3fd2d3SAndreas Gohr throw new EncoderException('The datetime format is invalid and cannot be decoded.'); 9250b3fd2d3SAndreas Gohr } 9260b3fd2d3SAndreas Gohr if ($matches[$matchMap['hours']] === '24') { 9270b3fd2d3SAndreas Gohr throw new EncoderException('Midnight must only be specified by 00, but got 24.'); 9280b3fd2d3SAndreas Gohr } 9290b3fd2d3SAndreas Gohr $tzFormat = AbstractTimeType::TZ_LOCAL; 9300b3fd2d3SAndreas Gohr $dtFormat = AbstractTimeType::FORMAT_HOURS; 9310b3fd2d3SAndreas Gohr 9320b3fd2d3SAndreas Gohr # Minutes 9330b3fd2d3SAndreas Gohr if (isset($matches[$matchMap['minutes']]) && $matches[$matchMap['minutes']] !== '') { 9340b3fd2d3SAndreas Gohr $dtFormat = AbstractTimeType::FORMAT_MINUTES; 9350b3fd2d3SAndreas Gohr $format .= 'i'; 9360b3fd2d3SAndreas Gohr } 9370b3fd2d3SAndreas Gohr # Seconds 9380b3fd2d3SAndreas Gohr if (isset($matches[$matchMap['seconds']]) && $matches[$matchMap['seconds']] !== '') { 9390b3fd2d3SAndreas Gohr $dtFormat = AbstractTimeType::FORMAT_SECONDS; 9400b3fd2d3SAndreas Gohr $format .= 's'; 9410b3fd2d3SAndreas Gohr } 9420b3fd2d3SAndreas Gohr # Fractions of a second 9430b3fd2d3SAndreas Gohr if (isset($matchMap['fractions']) && isset($matches[$matchMap['fractions']]) && $matches[$matchMap['fractions']] !== '') { 9440b3fd2d3SAndreas Gohr $dtFormat = AbstractTimeType::FORMAT_FRACTIONS; 9450b3fd2d3SAndreas Gohr $format .= '.u'; 9460b3fd2d3SAndreas Gohr } 9470b3fd2d3SAndreas Gohr # Timezone 9480b3fd2d3SAndreas Gohr if (isset($matches[$matchMap['timezone']]) && $matches[$matchMap['timezone']] !== '') { 9490b3fd2d3SAndreas Gohr $tzFormat = $matches[$matchMap['timezone']] === 'Z' ? AbstractTimeType::TZ_UTC : AbstractTimeType::TZ_DIFF; 9500b3fd2d3SAndreas Gohr $format .= 'T'; 9510b3fd2d3SAndreas Gohr } 9520b3fd2d3SAndreas Gohr $this->validateDateFormat($matches, $matchMap); 9530b3fd2d3SAndreas Gohr 954*dad993c5SAndreas Gohr $dateTime = DateTime::createFromFormat($format, $bytes); 9550b3fd2d3SAndreas Gohr if ($dateTime === false) { 9560b3fd2d3SAndreas Gohr throw new EncoderException('Unable to decode time to a DateTime object.'); 9570b3fd2d3SAndreas Gohr } 9580b3fd2d3SAndreas Gohr $bytes = null; 9590b3fd2d3SAndreas Gohr 9600b3fd2d3SAndreas Gohr return [$dateTime, $dtFormat, $tzFormat]; 9610b3fd2d3SAndreas Gohr } 9620b3fd2d3SAndreas Gohr 9630b3fd2d3SAndreas Gohr /** 9640b3fd2d3SAndreas Gohr * Some encodings have specific restrictions. Allow them to override and validate this. 9650b3fd2d3SAndreas Gohr * 9660b3fd2d3SAndreas Gohr * @param array $matches 9670b3fd2d3SAndreas Gohr * @param array $matchMap 9680b3fd2d3SAndreas Gohr */ 9690b3fd2d3SAndreas Gohr protected function validateDateFormat(array $matches, array $matchMap) 9700b3fd2d3SAndreas Gohr { 9710b3fd2d3SAndreas Gohr } 9720b3fd2d3SAndreas Gohr 9730b3fd2d3SAndreas Gohr /** 974fd0855ecSAndreas Gohr * @param int $length 9750b3fd2d3SAndreas Gohr * @return string 9760b3fd2d3SAndreas Gohr * @throws EncoderException 9770b3fd2d3SAndreas Gohr */ 9780b3fd2d3SAndreas Gohr protected function decodeOid($length): string 9790b3fd2d3SAndreas Gohr { 9800b3fd2d3SAndreas Gohr if ($length === 0) { 9810b3fd2d3SAndreas Gohr throw new EncoderException('Zero length not permitted for an OID type.'); 9820b3fd2d3SAndreas Gohr } 983fd0855ecSAndreas Gohr # We need to get the first part here, as it's used to determine the first 2 components. 984fd0855ecSAndreas Gohr $startedAt = $this->pos; 985fd0855ecSAndreas Gohr $firstPart = $this->getVlqBytesToInt(); 9860b3fd2d3SAndreas Gohr 987fd0855ecSAndreas Gohr if ($firstPart < 80) { 988*dad993c5SAndreas Gohr $oid = floor($firstPart / 40) . '.' . ($firstPart % 40); 989fd0855ecSAndreas Gohr } else { 990fd0855ecSAndreas Gohr $isBigInt = ($firstPart > PHP_INT_MAX); 991fd0855ecSAndreas Gohr $this->throwIfBigIntGmpNeeded($isBigInt); 992fd0855ecSAndreas Gohr # In this case, the first identifier is always 2. 993fd0855ecSAndreas Gohr # But there is no limit on the value of the second identifier. 994*dad993c5SAndreas Gohr $oid = '2.' . ($isBigInt ? gmp_strval(gmp_sub($firstPart, '80')) : (int)$firstPart - 80); 9950b3fd2d3SAndreas Gohr } 9960b3fd2d3SAndreas Gohr 997fd0855ecSAndreas Gohr # We could potentially have nothing left to decode at this point. 998fd0855ecSAndreas Gohr $oidLength = $length - ($this->pos - $startedAt); 999fd0855ecSAndreas Gohr $subIdentifiers = ($oidLength === 0) ? '' : '.' . $this->decodeRelativeOid($oidLength); 1000fd0855ecSAndreas Gohr 1001fd0855ecSAndreas Gohr return $oid . $subIdentifiers; 10020b3fd2d3SAndreas Gohr } 10030b3fd2d3SAndreas Gohr 10040b3fd2d3SAndreas Gohr /** 1005fd0855ecSAndreas Gohr * @param int $length 10060b3fd2d3SAndreas Gohr * @return string 10070b3fd2d3SAndreas Gohr * @throws EncoderException 10080b3fd2d3SAndreas Gohr */ 10090b3fd2d3SAndreas Gohr protected function decodeRelativeOid($length): string 10100b3fd2d3SAndreas Gohr { 10110b3fd2d3SAndreas Gohr if ($length === 0) { 10120b3fd2d3SAndreas Gohr throw new EncoderException('Zero length not permitted for an OID type.'); 10130b3fd2d3SAndreas Gohr } 10140b3fd2d3SAndreas Gohr $oid = ''; 10150b3fd2d3SAndreas Gohr $endAt = $this->pos + $length; 10160b3fd2d3SAndreas Gohr 10170b3fd2d3SAndreas Gohr while ($this->pos < $endAt) { 10180b3fd2d3SAndreas Gohr $oid .= ($oid === '' ? '' : '.') . $this->getVlqBytesToInt(); 10190b3fd2d3SAndreas Gohr } 10200b3fd2d3SAndreas Gohr 10210b3fd2d3SAndreas Gohr return $oid; 10220b3fd2d3SAndreas Gohr } 10230b3fd2d3SAndreas Gohr 10240b3fd2d3SAndreas Gohr /** 10250b3fd2d3SAndreas Gohr * @return bool 10260b3fd2d3SAndreas Gohr */ 10270b3fd2d3SAndreas Gohr protected function decodeBoolean(): bool 10280b3fd2d3SAndreas Gohr { 10290b3fd2d3SAndreas Gohr return ($this->binary[$this->pos++] !== self::BOOL_FALSE); 10300b3fd2d3SAndreas Gohr } 10310b3fd2d3SAndreas Gohr 10320b3fd2d3SAndreas Gohr /** 10330b3fd2d3SAndreas Gohr * @param int $length 10340b3fd2d3SAndreas Gohr * @return string 10350b3fd2d3SAndreas Gohr * @throws EncoderException 10360b3fd2d3SAndreas Gohr */ 10370b3fd2d3SAndreas Gohr protected function decodeBitString($length): string 10380b3fd2d3SAndreas Gohr { 10390b3fd2d3SAndreas Gohr # The first byte represents the number of unused bits at the end. 1040*dad993c5SAndreas Gohr $unused = ord($this->binary[$this->pos++]); 10410b3fd2d3SAndreas Gohr 10420b3fd2d3SAndreas Gohr if ($unused > 7) { 10430b3fd2d3SAndreas Gohr throw new EncoderException(sprintf( 10440b3fd2d3SAndreas Gohr 'The unused bits in a bit string must be between 0 and 7, got: %s', 10450b3fd2d3SAndreas Gohr $unused 10460b3fd2d3SAndreas Gohr )); 10470b3fd2d3SAndreas Gohr } 10480b3fd2d3SAndreas Gohr if ($unused > 0 && $length < 1) { 10490b3fd2d3SAndreas Gohr throw new EncoderException(sprintf( 10500b3fd2d3SAndreas Gohr 'If the bit string is empty the unused bits must be set to 0. However, it is set to %s with %s octets.', 10510b3fd2d3SAndreas Gohr $unused, 10520b3fd2d3SAndreas Gohr $length 10530b3fd2d3SAndreas Gohr )); 10540b3fd2d3SAndreas Gohr } 10550b3fd2d3SAndreas Gohr $length--; 10560b3fd2d3SAndreas Gohr 10570b3fd2d3SAndreas Gohr return $this->binaryToBitString($length, $unused); 10580b3fd2d3SAndreas Gohr } 10590b3fd2d3SAndreas Gohr 10600b3fd2d3SAndreas Gohr /** 10610b3fd2d3SAndreas Gohr * @param int $length 10620b3fd2d3SAndreas Gohr * @param int $unused 10630b3fd2d3SAndreas Gohr * @return string 10640b3fd2d3SAndreas Gohr */ 10650b3fd2d3SAndreas Gohr protected function binaryToBitString(int $length, int $unused): string 10660b3fd2d3SAndreas Gohr { 10670b3fd2d3SAndreas Gohr $bitstring = ''; 10680b3fd2d3SAndreas Gohr $endAt = $this->pos + $length; 10690b3fd2d3SAndreas Gohr 10700b3fd2d3SAndreas Gohr for ($this->pos; $this->pos < $endAt; $this->pos++) { 1071*dad993c5SAndreas Gohr $octet = sprintf("%08d", decbin(ord($this->binary[$this->pos]))); 10720b3fd2d3SAndreas Gohr if ($this->pos === ($endAt - 1) && $unused) { 1073*dad993c5SAndreas Gohr $bitstring .= substr($octet, 0, ($unused * -1)); 10740b3fd2d3SAndreas Gohr } else { 10750b3fd2d3SAndreas Gohr $bitstring .= $octet; 10760b3fd2d3SAndreas Gohr } 10770b3fd2d3SAndreas Gohr } 10780b3fd2d3SAndreas Gohr 10790b3fd2d3SAndreas Gohr return $bitstring; 10800b3fd2d3SAndreas Gohr } 10810b3fd2d3SAndreas Gohr 10820b3fd2d3SAndreas Gohr /** 10830b3fd2d3SAndreas Gohr * @param int $length 10840b3fd2d3SAndreas Gohr * @return string|int number 10850b3fd2d3SAndreas Gohr * @throws EncoderException 10860b3fd2d3SAndreas Gohr */ 10870b3fd2d3SAndreas Gohr protected function decodeInteger($length) 10880b3fd2d3SAndreas Gohr { 10890b3fd2d3SAndreas Gohr if ($length === 0) { 10900b3fd2d3SAndreas Gohr throw new EncoderException('Zero length not permitted for an integer type.'); 10910b3fd2d3SAndreas Gohr } 1092*dad993c5SAndreas Gohr $bytes = substr($this->binary, $this->pos, $length); 10930b3fd2d3SAndreas Gohr $this->pos += $length; 10940b3fd2d3SAndreas Gohr 1095*dad993c5SAndreas Gohr $isNegative = (ord($bytes[0]) & 0x80); 10960b3fd2d3SAndreas Gohr # Need to reverse Two's Complement. Invert the bits... 10970b3fd2d3SAndreas Gohr if ($isNegative) { 10980b3fd2d3SAndreas Gohr for ($i = 0; $i < $length; $i++) { 10990b3fd2d3SAndreas Gohr $bytes[$i] = ~$bytes[$i]; 11000b3fd2d3SAndreas Gohr } 11010b3fd2d3SAndreas Gohr } 1102*dad993c5SAndreas Gohr $int = hexdec(bin2hex($bytes)); 11030b3fd2d3SAndreas Gohr 1104*dad993c5SAndreas Gohr $isBigInt = is_float($int); 11050b3fd2d3SAndreas Gohr $this->throwIfBigIntGmpNeeded($isBigInt); 11060b3fd2d3SAndreas Gohr if ($isBigInt) { 1107*dad993c5SAndreas Gohr $int = gmp_import($bytes); 11080b3fd2d3SAndreas Gohr } 11090b3fd2d3SAndreas Gohr $bytes = null; 11100b3fd2d3SAndreas Gohr 11110b3fd2d3SAndreas Gohr # Complete Two's Complement by adding 1 and turning it negative... 11120b3fd2d3SAndreas Gohr if ($isNegative) { 1113*dad993c5SAndreas Gohr $int = $isBigInt ? gmp_neg(gmp_add($int, '1')) : ($int + 1) * -1; 11140b3fd2d3SAndreas Gohr } 11150b3fd2d3SAndreas Gohr 1116*dad993c5SAndreas Gohr return $isBigInt ? gmp_strval($int) : $int; 11170b3fd2d3SAndreas Gohr } 11180b3fd2d3SAndreas Gohr 11190b3fd2d3SAndreas Gohr /** 11200b3fd2d3SAndreas Gohr * @param bool $isBigInt 11210b3fd2d3SAndreas Gohr * @throws EncoderException 11220b3fd2d3SAndreas Gohr */ 11230b3fd2d3SAndreas Gohr protected function throwIfBigIntGmpNeeded(bool $isBigInt): void 11240b3fd2d3SAndreas Gohr { 11250b3fd2d3SAndreas Gohr if ($isBigInt && !$this->isGmpAvailable) { 11260b3fd2d3SAndreas Gohr throw new EncoderException(sprintf( 11270b3fd2d3SAndreas Gohr 'An integer higher than PHP_INT_MAX int (%s) was encountered and the GMP extension is not loaded.', 11280b3fd2d3SAndreas Gohr PHP_INT_MAX 11290b3fd2d3SAndreas Gohr )); 11300b3fd2d3SAndreas Gohr } 11310b3fd2d3SAndreas Gohr } 11320b3fd2d3SAndreas Gohr 11330b3fd2d3SAndreas Gohr /** 11340b3fd2d3SAndreas Gohr * @param int $length 11350b3fd2d3SAndreas Gohr * @return float 11360b3fd2d3SAndreas Gohr * @throws EncoderException 11370b3fd2d3SAndreas Gohr */ 11380b3fd2d3SAndreas Gohr protected function decodeReal($length): float 11390b3fd2d3SAndreas Gohr { 11400b3fd2d3SAndreas Gohr if ($length === 0) { 11410b3fd2d3SAndreas Gohr return 0; 11420b3fd2d3SAndreas Gohr } 11430b3fd2d3SAndreas Gohr 1144*dad993c5SAndreas Gohr $ident = ord($this->binary[$this->pos++]); 11450b3fd2d3SAndreas Gohr if ($ident === 0x40) { 11460b3fd2d3SAndreas Gohr return INF; 11470b3fd2d3SAndreas Gohr } 11480b3fd2d3SAndreas Gohr if ($ident === 0x41) { 11490b3fd2d3SAndreas Gohr return -INF; 11500b3fd2d3SAndreas Gohr } 11510b3fd2d3SAndreas Gohr 11520b3fd2d3SAndreas Gohr // @todo Real type encoding/decoding is rather complex. Need to implement this yet. 11530b3fd2d3SAndreas Gohr throw new EncoderException('The real type encoding encountered is not yet implemented.'); 11540b3fd2d3SAndreas Gohr } 11550b3fd2d3SAndreas Gohr 11560b3fd2d3SAndreas Gohr /** 11570b3fd2d3SAndreas Gohr * Encoding subsets may require specific ordering on set types. Allow this to be overridden. 11580b3fd2d3SAndreas Gohr * 11590b3fd2d3SAndreas Gohr * @param SetType $set 11600b3fd2d3SAndreas Gohr * @return string 11610b3fd2d3SAndreas Gohr * @throws EncoderException 11620b3fd2d3SAndreas Gohr */ 11630b3fd2d3SAndreas Gohr protected function encodeSet(SetType $set) 11640b3fd2d3SAndreas Gohr { 11650b3fd2d3SAndreas Gohr return $this->encodeConstructedType(...$set->getChildren()); 11660b3fd2d3SAndreas Gohr } 11670b3fd2d3SAndreas Gohr 11680b3fd2d3SAndreas Gohr /** 11690b3fd2d3SAndreas Gohr * Encoding subsets may require specific ordering on set of types. Allow this to be overridden. 11700b3fd2d3SAndreas Gohr * 11710b3fd2d3SAndreas Gohr * @param SetOfType $set 11720b3fd2d3SAndreas Gohr * @return string 11730b3fd2d3SAndreas Gohr * @throws EncoderException 11740b3fd2d3SAndreas Gohr */ 11750b3fd2d3SAndreas Gohr protected function encodeSetOf(SetOfType $set) 11760b3fd2d3SAndreas Gohr { 11770b3fd2d3SAndreas Gohr return $this->encodeConstructedType(...$set->getChildren()); 11780b3fd2d3SAndreas Gohr } 11790b3fd2d3SAndreas Gohr 11800b3fd2d3SAndreas Gohr /** 1181fd0855ecSAndreas Gohr * @param AbstractType ...$types 11820b3fd2d3SAndreas Gohr * @return string 11830b3fd2d3SAndreas Gohr * @throws EncoderException 11840b3fd2d3SAndreas Gohr */ 11850b3fd2d3SAndreas Gohr protected function encodeConstructedType(AbstractType ...$types) 11860b3fd2d3SAndreas Gohr { 11870b3fd2d3SAndreas Gohr $bytes = ''; 11880b3fd2d3SAndreas Gohr 11890b3fd2d3SAndreas Gohr foreach ($types as $type) { 11900b3fd2d3SAndreas Gohr $bytes .= $this->encode($type); 11910b3fd2d3SAndreas Gohr } 11920b3fd2d3SAndreas Gohr 11930b3fd2d3SAndreas Gohr return $bytes; 11940b3fd2d3SAndreas Gohr } 11950b3fd2d3SAndreas Gohr 11960b3fd2d3SAndreas Gohr /** 11970b3fd2d3SAndreas Gohr * @param int $length 11980b3fd2d3SAndreas Gohr * @return array 11990b3fd2d3SAndreas Gohr * @throws EncoderException 12000b3fd2d3SAndreas Gohr * @throws PartialPduException 12010b3fd2d3SAndreas Gohr */ 12020b3fd2d3SAndreas Gohr protected function decodeConstructedType($length) 12030b3fd2d3SAndreas Gohr { 12040b3fd2d3SAndreas Gohr $children = []; 12050b3fd2d3SAndreas Gohr $endAt = $this->pos + $length; 12060b3fd2d3SAndreas Gohr 12070b3fd2d3SAndreas Gohr while ($this->pos < $endAt) { 12080b3fd2d3SAndreas Gohr $children[] = $this->decodeBytes(); 12090b3fd2d3SAndreas Gohr } 12100b3fd2d3SAndreas Gohr 12110b3fd2d3SAndreas Gohr return $children; 12120b3fd2d3SAndreas Gohr } 12130b3fd2d3SAndreas Gohr} 1214