diff options
Diffstat (limited to 'vendor/aws/aws-sdk-php/src/Api/Parser/DecodingEventStreamIterator.php')
-rw-r--r-- | vendor/aws/aws-sdk-php/src/Api/Parser/DecodingEventStreamIterator.php | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/vendor/aws/aws-sdk-php/src/Api/Parser/DecodingEventStreamIterator.php b/vendor/aws/aws-sdk-php/src/Api/Parser/DecodingEventStreamIterator.php new file mode 100644 index 0000000..7ad03bf --- /dev/null +++ b/vendor/aws/aws-sdk-php/src/Api/Parser/DecodingEventStreamIterator.php @@ -0,0 +1,340 @@ +<?php + +namespace Aws\Api\Parser; + +use \Iterator; +use Aws\Api\DateTimeResult; +use GuzzleHttp\Psr7; +use Psr\Http\Message\StreamInterface; +use Aws\Api\Parser\Exception\ParserException; + +/** + * @internal Implements a decoder for a binary encoded event stream that will + * decode, validate, and provide individual events from the stream. + */ +class DecodingEventStreamIterator implements Iterator +{ + const HEADERS = 'headers'; + const PAYLOAD = 'payload'; + + const LENGTH_TOTAL = 'total_length'; + const LENGTH_HEADERS = 'headers_length'; + + const CRC_PRELUDE = 'prelude_crc'; + + const BYTES_PRELUDE = 12; + const BYTES_TRAILING = 4; + + private static $preludeFormat = [ + self::LENGTH_TOTAL => 'decodeUint32', + self::LENGTH_HEADERS => 'decodeUint32', + self::CRC_PRELUDE => 'decodeUint32', + ]; + + private static $lengthFormatMap = [ + 1 => 'decodeUint8', + 2 => 'decodeUint16', + 4 => 'decodeUint32', + 8 => 'decodeUint64', + ]; + + private static $headerTypeMap = [ + 0 => 'decodeBooleanTrue', + 1 => 'decodeBooleanFalse', + 2 => 'decodeInt8', + 3 => 'decodeInt16', + 4 => 'decodeInt32', + 5 => 'decodeInt64', + 6 => 'decodeBytes', + 7 => 'decodeString', + 8 => 'decodeTimestamp', + 9 => 'decodeUuid', + ]; + + /** @var StreamInterface Stream of eventstream shape to parse. */ + private $stream; + + /** @var array Currently parsed event. */ + private $currentEvent; + + /** @var int Current in-order event key. */ + private $key; + + /** @var resource|\HashContext CRC32 hash context for event validation */ + private $hashContext; + + /** @var int $currentPosition */ + private $currentPosition; + + /** + * DecodingEventStreamIterator constructor. + * + * @param StreamInterface $stream + */ + public function __construct(StreamInterface $stream) + { + $this->stream = $stream; + $this->rewind(); + } + + private function parseHeaders($headerBytes) + { + $headers = []; + $bytesRead = 0; + + while ($bytesRead < $headerBytes) { + list($key, $numBytes) = $this->decodeString(1); + $bytesRead += $numBytes; + + list($type, $numBytes) = $this->decodeUint8(); + $bytesRead += $numBytes; + + $f = self::$headerTypeMap[$type]; + list($value, $numBytes) = $this->{$f}(); + $bytesRead += $numBytes; + + if (isset($headers[$key])) { + throw new ParserException('Duplicate key in event headers.'); + } + $headers[$key] = $value; + } + + return [$headers, $bytesRead]; + } + + private function parsePrelude() + { + $prelude = []; + $bytesRead = 0; + + $calculatedCrc = null; + foreach (self::$preludeFormat as $key => $decodeFunction) { + if ($key === self::CRC_PRELUDE) { + $hashCopy = hash_copy($this->hashContext); + $calculatedCrc = hash_final($this->hashContext, true); + $this->hashContext = $hashCopy; + } + list($value, $numBytes) = $this->{$decodeFunction}(); + $bytesRead += $numBytes; + + $prelude[$key] = $value; + } + + if (unpack('N', $calculatedCrc)[1] !== $prelude[self::CRC_PRELUDE]) { + throw new ParserException('Prelude checksum mismatch.'); + } + + return [$prelude, $bytesRead]; + } + + private function parseEvent() + { + $event = []; + + if ($this->stream->tell() < $this->stream->getSize()) { + $this->hashContext = hash_init('crc32b'); + + $bytesLeft = $this->stream->getSize() - $this->stream->tell(); + list($prelude, $numBytes) = $this->parsePrelude(); + if ($prelude[self::LENGTH_TOTAL] > $bytesLeft) { + throw new ParserException('Message length too long.'); + } + $bytesLeft -= $numBytes; + + if ($prelude[self::LENGTH_HEADERS] > $bytesLeft) { + throw new ParserException('Headers length too long.'); + } + + list( + $event[self::HEADERS], + $numBytes + ) = $this->parseHeaders($prelude[self::LENGTH_HEADERS]); + + $event[self::PAYLOAD] = Psr7\Utils::streamFor( + $this->readAndHashBytes( + $prelude[self::LENGTH_TOTAL] - self::BYTES_PRELUDE + - $numBytes - self::BYTES_TRAILING + ) + ); + + $calculatedCrc = hash_final($this->hashContext, true); + $messageCrc = $this->stream->read(4); + if ($calculatedCrc !== $messageCrc) { + throw new ParserException('Message checksum mismatch.'); + } + } + + return $event; + } + + // Iterator Functionality + + /** + * @return array + */ + #[\ReturnTypeWillChange] + public function current() + { + return $this->currentEvent; + } + + /** + * @return int + */ + #[\ReturnTypeWillChange] + public function key() + { + return $this->key; + } + + #[\ReturnTypeWillChange] + public function next() + { + $this->currentPosition = $this->stream->tell(); + if ($this->valid()) { + $this->key++; + $this->currentEvent = $this->parseEvent(); + } + } + + #[\ReturnTypeWillChange] + public function rewind() + { + $this->stream->rewind(); + $this->key = 0; + $this->currentPosition = 0; + $this->currentEvent = $this->parseEvent(); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function valid() + { + return $this->currentPosition < $this->stream->getSize(); + } + + // Decoding Utilities + + private function readAndHashBytes($num) + { + $bytes = $this->stream->read($num); + hash_update($this->hashContext, $bytes); + return $bytes; + } + + private function decodeBooleanTrue() + { + return [true, 0]; + } + + private function decodeBooleanFalse() + { + return [false, 0]; + } + + private function uintToInt($val, $size) + { + $signedCap = pow(2, $size - 1); + if ($val > $signedCap) { + $val -= (2 * $signedCap); + } + return $val; + } + + private function decodeInt8() + { + $val = (int)unpack('C', $this->readAndHashBytes(1))[1]; + return [$this->uintToInt($val, 8), 1]; + } + + private function decodeUint8() + { + return [unpack('C', $this->readAndHashBytes(1))[1], 1]; + } + + private function decodeInt16() + { + $val = (int)unpack('n', $this->readAndHashBytes(2))[1]; + return [$this->uintToInt($val, 16), 2]; + } + + private function decodeUint16() + { + return [unpack('n', $this->readAndHashBytes(2))[1], 2]; + } + + private function decodeInt32() + { + $val = (int)unpack('N', $this->readAndHashBytes(4))[1]; + return [$this->uintToInt($val, 32), 4]; + } + + private function decodeUint32() + { + return [unpack('N', $this->readAndHashBytes(4))[1], 4]; + } + + private function decodeInt64() + { + $val = $this->unpackInt64($this->readAndHashBytes(8))[1]; + return [$this->uintToInt($val, 64), 8]; + } + + private function decodeUint64() + { + return [$this->unpackInt64($this->readAndHashBytes(8))[1], 8]; + } + + private function unpackInt64($bytes) + { + if (version_compare(PHP_VERSION, '5.6.3', '<')) { + $d = unpack('N2', $bytes); + return [1 => $d[1] << 32 | $d[2]]; + } + return unpack('J', $bytes); + } + + private function decodeBytes($lengthBytes=2) + { + if (!isset(self::$lengthFormatMap[$lengthBytes])) { + throw new ParserException('Undefined variable length format.'); + } + $f = self::$lengthFormatMap[$lengthBytes]; + list($len, $bytes) = $this->{$f}(); + return [$this->readAndHashBytes($len), $len + $bytes]; + } + + private function decodeString($lengthBytes=2) + { + if (!isset(self::$lengthFormatMap[$lengthBytes])) { + throw new ParserException('Undefined variable length format.'); + } + $f = self::$lengthFormatMap[$lengthBytes]; + list($len, $bytes) = $this->{$f}(); + return [$this->readAndHashBytes($len), $len + $bytes]; + } + + private function decodeTimestamp() + { + list($val, $bytes) = $this->decodeInt64(); + return [ + DateTimeResult::createFromFormat('U.u', $val / 1000), + $bytes + ]; + } + + private function decodeUuid() + { + $val = unpack('H32', $this->readAndHashBytes(16))[1]; + return [ + substr($val, 0, 8) . '-' + . substr($val, 8, 4) . '-' + . substr($val, 12, 4) . '-' + . substr($val, 16, 4) . '-' + . substr($val, 20, 12), + 16 + ]; + } +} |