From 7b55258e18da52642e3c2691dd5c848f8ed32a16 Mon Sep 17 00:00:00 2001 From: Zi Xing Date: Sun, 17 Apr 2022 16:54:48 -0400 Subject: [PATCH] Added ZiProto --- src/ncc/Exceptions/MalformedJsonException.php | 12 + .../Extensions/ZiProto/Abstracts/Options.php | 22 + .../Extensions/ZiProto/Abstracts/Regex.php | 21 + src/ncc/Extensions/ZiProto/BufferStream.php | 795 ++++++++++++++++++ .../Extensions/ZiProto/DecodingOptions.php | 96 +++ .../Extensions/ZiProto/EncodingOptions.php | 171 ++++ .../Exception/DecodingFailedException.php | 32 + .../Exception/EncodingFailedException.php | 54 ++ .../Exception/InsufficientDataException.php | 25 + .../Exception/IntegerOverflowException.php | 35 + .../Exception/InvalidOptionException.php | 29 + src/ncc/Extensions/ZiProto/Ext.php | 30 + src/ncc/Extensions/ZiProto/Packet.php | 419 +++++++++ src/ncc/Extensions/ZiProto/Type/Binary.php | 24 + src/ncc/Extensions/ZiProto/Type/Map.php | 24 + .../TypeTransformer/BinaryTransformer.php | 28 + .../ZiProto/TypeTransformer/Extension.php | 24 + .../TypeTransformer/MapTransformer.php | 27 + .../ZiProto/TypeTransformer/Validator.php | 18 + src/ncc/Extensions/ZiProto/ZiProto.php | 44 + src/ncc/autoload.php | 22 +- .../load_configuration.php | 3 +- 22 files changed, 1953 insertions(+), 2 deletions(-) create mode 100644 src/ncc/Exceptions/MalformedJsonException.php create mode 100644 src/ncc/Extensions/ZiProto/Abstracts/Options.php create mode 100644 src/ncc/Extensions/ZiProto/Abstracts/Regex.php create mode 100644 src/ncc/Extensions/ZiProto/BufferStream.php create mode 100644 src/ncc/Extensions/ZiProto/DecodingOptions.php create mode 100644 src/ncc/Extensions/ZiProto/EncodingOptions.php create mode 100644 src/ncc/Extensions/ZiProto/Exception/DecodingFailedException.php create mode 100644 src/ncc/Extensions/ZiProto/Exception/EncodingFailedException.php create mode 100644 src/ncc/Extensions/ZiProto/Exception/InsufficientDataException.php create mode 100644 src/ncc/Extensions/ZiProto/Exception/IntegerOverflowException.php create mode 100644 src/ncc/Extensions/ZiProto/Exception/InvalidOptionException.php create mode 100644 src/ncc/Extensions/ZiProto/Ext.php create mode 100644 src/ncc/Extensions/ZiProto/Packet.php create mode 100644 src/ncc/Extensions/ZiProto/Type/Binary.php create mode 100644 src/ncc/Extensions/ZiProto/Type/Map.php create mode 100644 src/ncc/Extensions/ZiProto/TypeTransformer/BinaryTransformer.php create mode 100644 src/ncc/Extensions/ZiProto/TypeTransformer/Extension.php create mode 100644 src/ncc/Extensions/ZiProto/TypeTransformer/MapTransformer.php create mode 100644 src/ncc/Extensions/ZiProto/TypeTransformer/Validator.php create mode 100644 src/ncc/Extensions/ZiProto/ZiProto.php diff --git a/src/ncc/Exceptions/MalformedJsonException.php b/src/ncc/Exceptions/MalformedJsonException.php new file mode 100644 index 0000000..c3bc323 --- /dev/null +++ b/src/ncc/Exceptions/MalformedJsonException.php @@ -0,0 +1,12 @@ +isBigIntAsStr = $options->isBigIntAsStrMode(); + $this->isBigIntAsGmp = $options->isBigIntAsGmpMode(); + $this->buffer = $buffer; + } + + /** + * @param Extension $transformer + * @return BufferStream + */ + public function registerTransformer(Extension $transformer) : self + { + $this->transformers[$transformer->getType()] = $transformer; + return $this; + } + + /** + * @param string $data + * @return BufferStream + */ + public function append(string $data) : self + { + $this->buffer .= $data; + return $this; + } + + /** + * @param string $buffer + * @return BufferStream + */ + public function reset(string $buffer = '') : self + { + $this->buffer = $buffer; + $this->offset = 0; + return $this; + } + + /** + * Clone Method + */ + public function __clone() + { + $this->buffer = ''; + $this->offset = 0; + } + + /** + * @return array + */ + public function trydecode() : array + { + $data = []; + $offset = $this->offset; + + try + { + do + { + $data[] = $this->decode(); + $offset = $this->offset; + } while (isset($this->buffer[$this->offset])); + } + catch (InsufficientDataException $e) + { + $this->offset = $offset; + } + + if ($this->offset) + { + $this->buffer = isset($this->buffer[$this->offset]) ? substr($this->buffer, $this->offset) : ''; + $this->offset = 0; + } + + return $data; + } + + /** + * @return array|bool|int|mixed|resource|string|Ext|null + */ + public function decode() + { + if (!isset($this->buffer[$this->offset])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1); + } + + $c = ord($this->buffer[$this->offset]); + ++$this->offset; + + // fixint + if ($c <= 0x7f) + { + return $c; + } + + // fixstr + if ($c >= 0xa0 && $c <= 0xbf) + { + return ($c & 0x1f) ? $this->decodeStrData($c & 0x1f) : ''; + } + + // fixarray + if ($c >= 0x90 && $c <= 0x9f) + { + return ($c & 0xf) ? $this->decodeArrayData($c & 0xf) : []; + } + + // fixmap + if ($c >= 0x80 && $c <= 0x8f) + { + return ($c & 0xf) ? $this->decodeMapData($c & 0xf) : []; + } + + // negfixint + if ($c >= 0xe0) + { + return $c - 0x100; + } + + switch ($c) + { + case 0xc0: return null; + case 0xc2: return false; + case 0xc3: return true; + + // bin + case 0xc4: return $this->decodeStrData($this->decodeUint8()); + case 0xc5: return $this->decodeStrData($this->decodeUint16()); + case 0xc6: return $this->decodeStrData($this->decodeUint32()); + + // float + case 0xca: return $this->decodeFloat32(); + case 0xcb: return $this->decodeFloat64(); + + // uint + case 0xcc: return $this->decodeUint8(); + case 0xcd: return $this->decodeUint16(); + case 0xce: return $this->decodeUint32(); + case 0xcf: return $this->decodeUint64(); + + // int + case 0xd0: return $this->decodeInt8(); + case 0xd1: return $this->decodeInt16(); + case 0xd2: return $this->decodeInt32(); + case 0xd3: return $this->decodeInt64(); + + // str + case 0xd9: return $this->decodeStrData($this->decodeUint8()); + case 0xda: return $this->decodeStrData($this->decodeUint16()); + case 0xdb: return $this->decodeStrData($this->decodeUint32()); + + // array + case 0xdc: return $this->decodeArrayData($this->decodeUint16()); + case 0xdd: return $this->decodeArrayData($this->decodeUint32()); + + // map + case 0xde: return $this->decodeMapData($this->decodeUint16()); + case 0xdf: return $this->decodeMapData($this->decodeUint32()); + + // ext + case 0xd4: return $this->decodeExtData(1); + case 0xd5: return $this->decodeExtData(2); + case 0xd6: return $this->decodeExtData(4); + case 0xd7: return $this->decodeExtData(8); + case 0xd8: return $this->decodeExtData(16); + case 0xc7: return $this->decodeExtData($this->decodeUint8()); + case 0xc8: return $this->decodeExtData($this->decodeUint16()); + case 0xc9: return $this->decodeExtData($this->decodeUint32()); + } + + throw DecodingFailedException::unknownCode($c); + } + + /** + * @return null + */ + public function decodeNil() + { + if (!isset($this->buffer[$this->offset])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1); + } + + if ("\xc0" === $this->buffer[$this->offset]) + { + ++$this->offset; + return null; + } + + throw DecodingFailedException::unexpectedCode(ord($this->buffer[$this->offset++]), 'nil'); + } + + /** + * @return bool + */ + public function decodeBool() + { + if (!isset($this->buffer[$this->offset])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1); + } + + $c = ord($this->buffer[$this->offset]); + ++$this->offset; + + if (0xc2 === $c) + { + return false; + } + + if (0xc3 === $c) + { + return true; + } + + throw DecodingFailedException::unexpectedCode($c, 'bool'); + } + + /** + * @return int|resource|string + */ + public function decodeInt() + { + if (!isset($this->buffer[$this->offset])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1); + } + + $c = ord($this->buffer[$this->offset]); + ++$this->offset; + + // fixint + if ($c <= 0x7f) + { + return $c; + } + + // negfixint + if ($c >= 0xe0) + { + return $c - 0x100; + } + + switch ($c) + { + // uint + case 0xcc: return $this->decodeUint8(); + case 0xcd: return $this->decodeUint16(); + case 0xce: return $this->decodeUint32(); + case 0xcf: return $this->decodeUint64(); + + // int + case 0xd0: return $this->decodeInt8(); + case 0xd1: return $this->decodeInt16(); + case 0xd2: return $this->decodeInt32(); + case 0xd3: return $this->decodeInt64(); + } + + throw DecodingFailedException::unexpectedCode($c, 'int'); + } + + /** + * @return mixed + */ + public function decodeFloat() + { + if (!isset($this->buffer[$this->offset])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1); + } + + $c = ord($this->buffer[$this->offset]); + ++$this->offset; + + if (0xcb === $c) + { + return $this->decodeFloat64(); + } + + if (0xca === $c) + { + return $this->decodeFloat32(); + } + + throw DecodingFailedException::unexpectedCode($c, 'float'); + } + + /** + * @return bool|string + */ + public function decodeStr() + { + if (!isset($this->buffer[$this->offset])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1); + } + + $c = ord($this->buffer[$this->offset]); + ++$this->offset; + + if ($c >= 0xa0 && $c <= 0xbf) + { + return ($c & 0x1f) ? $this->decodeStrData($c & 0x1f) : ''; + } + + if (0xd9 === $c) + { + return $this->decodeStrData($this->decodeUint8()); + } + + if (0xda === $c) + { + return $this->decodeStrData($this->decodeUint16()); + } + + if (0xdb === $c) + { + return $this->decodeStrData($this->decodeUint32()); + } + + throw DecodingFailedException::unexpectedCode($c, 'str'); + } + + /** + * @return bool|string + */ + public function decodeBin() + { + if (!isset($this->buffer[$this->offset])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1); + } + + $c = ord($this->buffer[$this->offset]); + ++$this->offset; + + if (0xc4 === $c) + { + return $this->decodeStrData($this->decodeUint8()); + } + + if (0xc5 === $c) + { + return $this->decodeStrData($this->decodeUint16()); + } + + if (0xc6 === $c) + { + return $this->decodeStrData($this->decodeUint32()); + } + + throw DecodingFailedException::unexpectedCode($c, 'bin'); + } + + /** + * @return array + */ + public function decodeArray() + { + $size = $this->decodeArrayHeader(); + $array = []; + + while ($size--) + { + $array[] = $this->decode(); + } + + return $array; + } + + /** + * @return int + */ + public function decodeArrayHeader() + { + if (!isset($this->buffer[$this->offset])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1); + } + + $c = ord($this->buffer[$this->offset]); + ++$this->offset; + + if ($c >= 0x90 && $c <= 0x9f) + { + return $c & 0xf; + } + + if (0xdc === $c) + { + return $this->decodeUint16(); + } + + if (0xdd === $c) + { + return $this->decodeUint32(); + } + + throw DecodingFailedException::unexpectedCode($c, 'array header'); + } + + /** + * @return array + */ + public function decodeMap() + { + $size = $this->decodeMapHeader(); + $map = []; + + while ($size--) + { + $map[$this->decode()] = $this->decode(); + } + + return $map; + } + + /** + * @return int + */ + public function decodeMapHeader() + { + if (!isset($this->buffer[$this->offset])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1); + } + + $c = ord($this->buffer[$this->offset]); + ++$this->offset; + + if ($c >= 0x80 && $c <= 0x8f) + { + return $c & 0xf; + } + + if (0xde === $c) + { + return $this->decodeUint16(); + } + + if (0xdf === $c) + { + return $this->decodeUint32(); + } + + throw DecodingFailedException::unexpectedCode($c, 'map header'); + } + + /** + * @return mixed|Ext + */ + public function decodeExt() + { + if (!isset($this->buffer[$this->offset])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1); + } + + $c = ord($this->buffer[$this->offset]); + ++$this->offset; + + switch ($c) + { + case 0xd4: return $this->decodeExtData(1); + case 0xd5: return $this->decodeExtData(2); + case 0xd6: return $this->decodeExtData(4); + case 0xd7: return $this->decodeExtData(8); + case 0xd8: return $this->decodeExtData(16); + case 0xc7: return $this->decodeExtData($this->decodeUint8()); + case 0xc8: return $this->decodeExtData($this->decodeUint16()); + case 0xc9: return $this->decodeExtData($this->decodeUint32()); + } + + throw DecodingFailedException::unexpectedCode($c, 'ext header'); + } + + /** + * @return int + */ + private function decodeUint8() + { + if (!isset($this->buffer[$this->offset])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1); + } + + return ord($this->buffer[$this->offset++]); + } + + /** + * @return int + */ + private function decodeUint16() + { + if (!isset($this->buffer[$this->offset + 1])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 2); + } + + $hi = ord($this->buffer[$this->offset]); + $lo = ord($this->buffer[++$this->offset]); + ++$this->offset; + + return $hi << 8 | $lo; + } + + /** + * @return mixed + */ + private function decodeUint32() + { + if (!isset($this->buffer[$this->offset + 3])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 4); + } + + $num = unpack('N', $this->buffer, $this->offset)[1]; + $this->offset += 4; + + return $num; + } + + /** + * @return resource|string + */ + private function decodeUint64() + { + if (!isset($this->buffer[$this->offset + 7])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 8); + } + + $num = unpack('J', $this->buffer, $this->offset)[1]; + $this->offset += 8; + + return $num < 0 ? $this->handleIntOverflow($num) : $num; + } + + /** + * @return int + */ + private function decodeInt8() + { + if (!isset($this->buffer[$this->offset])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1); + } + + $num = ord($this->buffer[$this->offset]); + ++$this->offset; + + return $num > 0x7f ? $num - 0x100 : $num; + } + + /** + * @return int + */ + private function decodeInt16() + { + if (!isset($this->buffer[$this->offset + 1])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 2); + } + + $hi = ord($this->buffer[$this->offset]); + $lo = ord($this->buffer[++$this->offset]); + ++$this->offset; + + return $hi > 0x7f ? $hi << 8 | $lo - 0x10000 : $hi << 8 | $lo; + } + + /** + * @return int + */ + private function decodeInt32() + { + if (!isset($this->buffer[$this->offset + 3])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 4); + } + + $num = unpack('N', $this->buffer, $this->offset)[1]; + $this->offset += 4; + + return $num > 0x7fffffff ? $num - 0x100000000 : $num; + } + + /** + * @return mixed + */ + private function decodeInt64() + { + if (!isset($this->buffer[$this->offset + 7])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 8); + } + + $num = unpack('J', $this->buffer, $this->offset)[1]; + $this->offset += 8; + + return $num; + } + + /** + * @return mixed + */ + private function decodeFloat32() + { + if (!isset($this->buffer[$this->offset + 3])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 4); + } + + $num = unpack('G', $this->buffer, $this->offset)[1]; + $this->offset += 4; + + return $num; + } + + /** + * @return mixed + */ + private function decodeFloat64() + { + if (!isset($this->buffer[$this->offset + 7])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 8); + } + + $num = unpack('E', $this->buffer, $this->offset)[1]; + $this->offset += 8; + + return $num; + } + + /** + * @param $length + * @return string + */ + private function decodeStrData($length) + { + if (!isset($this->buffer[$this->offset + $length - 1])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, $length); + } + + $str = substr($this->buffer, $this->offset, $length); + $this->offset += $length; + + return $str; + } + + /** + * @param $size + * @return array + */ + private function decodeArrayData($size) + { + $array = []; + + while ($size--) + { + $array[] = $this->decode(); + } + + return $array; + } + + /** + * @param $size + * @return array + */ + private function decodeMapData($size) + { + $map = []; + + while ($size--) + { + $map[$this->decode()] = $this->decode(); + } + + return $map; + } + + /** + * @param $length + * @return mixed|Ext + */ + private function decodeExtData($length) + { + if (!isset($this->buffer[$this->offset + $length - 1])) + { + throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, $length); + } + + // int8 + $num = ord($this->buffer[$this->offset]); + ++$this->offset; + $type = $num > 0x7f ? $num - 0x100 : $num; + + if (isset($this->transformers[$type])) + { + return $this->transformers[$type]->decode($this, $length); + } + + $data = substr($this->buffer, $this->offset, $length); + $this->offset += $length; + + return new Ext($type, $data); + } + + /** + * @param $value + * @return resource|string + */ + private function handleIntOverflow($value) + { + if ($this->isBigIntAsStr) + { + return sprintf('%u', $value); + } + + if ($this->isBigIntAsGmp) + { + return gmp_init(sprintf('%u', $value)); + } + + throw new IntegerOverflowException($value); + } + } \ No newline at end of file diff --git a/src/ncc/Extensions/ZiProto/DecodingOptions.php b/src/ncc/Extensions/ZiProto/DecodingOptions.php new file mode 100644 index 0000000..d2fc7f8 --- /dev/null +++ b/src/ncc/Extensions/ZiProto/DecodingOptions.php @@ -0,0 +1,96 @@ +bigIntMode = Options::BIGINT_AS_STR; + + return $self; + } + + /** + * @param int $bitmask + * @return DecodingOptions + */ + public static function fromBitmask(int $bitmask) : self + { + $self = new self(); + + $self->bigIntMode = self::getSingleOption('bigint', $bitmask, + Options::BIGINT_AS_STR | + Options::BIGINT_AS_GMP | + Options::BIGINT_AS_EXCEPTION + ) ?: Options::BIGINT_AS_STR; + + return $self; + } + + /** + * @return bool + */ + public function isBigIntAsStrMode() : bool + { + return Options::BIGINT_AS_STR === $this->bigIntMode; + } + + /** + * @return bool + */ + public function isBigIntAsGmpMode() : bool + { + return Options::BIGINT_AS_GMP === $this->bigIntMode; + } + + /** + * @param string $name + * @param int $bitmask + * @param int $validBitmask + * @return int + */ + private static function getSingleOption(string $name, int $bitmask, int $validBitmask) : int + { + $option = $bitmask & $validBitmask; + if ($option === ($option & -$option)) + { + return $option; + } + + static $map = [ + Options::BIGINT_AS_STR => 'BIGINT_AS_STR', + Options::BIGINT_AS_GMP => 'BIGINT_AS_GMP', + Options::BIGINT_AS_EXCEPTION => 'BIGINT_AS_EXCEPTION', + ]; + + $validOptions = []; + for ($i = $validBitmask & -$validBitmask; $i <= $validBitmask; $i <<= 1) + { + $validOptions[] = __CLASS__.'::'.$map[$i]; + } + + throw InvalidOptionException::outOfRange($name, $validOptions); + } + } diff --git a/src/ncc/Extensions/ZiProto/EncodingOptions.php b/src/ncc/Extensions/ZiProto/EncodingOptions.php new file mode 100644 index 0000000..2525641 --- /dev/null +++ b/src/ncc/Extensions/ZiProto/EncodingOptions.php @@ -0,0 +1,171 @@ +strBinMode = Options::DETECT_STR_BIN; + $self->arrMapMode = Options::DETECT_ARR_MAP; + $self->floatMode = Options::FORCE_FLOAT64; + + return $self; + } + + /** + * @param int $bitmask + * @return EncodingOptions + */ + public static function fromBitmask(int $bitmask) : self + { + $self = new self(); + + if (self::getSingleOption('str/bin', $bitmask, Options::FORCE_STR | Options::FORCE_BIN | Options::DETECT_STR_BIN)) + { + $self->strBinMode = self::getSingleOption('str/bin', $bitmask, + Options::FORCE_STR | + Options::FORCE_BIN | + Options::DETECT_STR_BIN + ); + } + else + { + $self->strBinMode = Options::DETECT_STR_BIN; + } + + if (self::getSingleOption('arr/map', $bitmask, Options::FORCE_ARR | Options::FORCE_MAP | Options::DETECT_ARR_MAP)) + { + $self->arrMapMode = self::getSingleOption('arr/map', $bitmask, + Options::FORCE_ARR | + Options::FORCE_MAP | + Options::DETECT_ARR_MAP + ); + } + else + { + $self->arrMapMode = Options::DETECT_ARR_MAP; + } + + if (self::getSingleOption('float', $bitmask, Options::FORCE_FLOAT32 | Options::FORCE_FLOAT64)) + { + $self->floatMode = self::getSingleOption('float', $bitmask, + Options::FORCE_FLOAT32 | + Options::FORCE_FLOAT64 + ); + } + else + { + $self->floatMode = Options::FORCE_FLOAT64; + } + + return $self; + } + + /** + * @return bool + */ + public function isDetectStrBinMode() : bool + { + return Options::DETECT_STR_BIN === $this->strBinMode; + } + + /** + * @return bool + */ + public function isForceStrMode() : bool + { + return Options::FORCE_STR === $this->strBinMode; + } + + /** + * @return bool + */ + public function isDetectArrMapMode() : bool + { + return Options::DETECT_ARR_MAP === $this->arrMapMode; + } + + /** + * @return bool + */ + public function isForceArrMode() : bool + { + return Options::FORCE_ARR === $this->arrMapMode; + } + + /** + * @return bool + */ + public function isForceFloat32Mode() : bool + { + return Options::FORCE_FLOAT32 === $this->floatMode; + } + + /** + * @param string $name + * @param int $bitmask + * @param int $validBitmask + * @return int + */ + private static function getSingleOption(string $name, int $bitmask, int $validBitmask) : int + { + $option = $bitmask & $validBitmask; + + if ($option === ($option & -$option)) + { + return $option; + } + + static $map = [ + Options::FORCE_STR => 'FORCE_STR', + Options::FORCE_BIN => 'FORCE_BIN', + Options::DETECT_STR_BIN => 'DETECT_STR_BIN', + Options::FORCE_ARR => 'FORCE_ARR', + Options::FORCE_MAP => 'FORCE_MAP', + Options::DETECT_ARR_MAP => 'DETECT_ARR_MAP', + Options::FORCE_FLOAT32 => 'FORCE_FLOAT32', + Options::FORCE_FLOAT64 => 'FORCE_FLOAT64', + ]; + + $validOptions = []; + + for ($i = $validBitmask & -$validBitmask; $i <= $validBitmask; $i <<= 1) + { + $validOptions[] = __CLASS__.'::'.$map[$i]; + } + + throw InvalidOptionException::outOfRange($name, $validOptions); + } + } diff --git a/src/ncc/Extensions/ZiProto/Exception/DecodingFailedException.php b/src/ncc/Extensions/ZiProto/Exception/DecodingFailedException.php new file mode 100644 index 0000000..82e8f93 --- /dev/null +++ b/src/ncc/Extensions/ZiProto/Exception/DecodingFailedException.php @@ -0,0 +1,32 @@ +value = $value; + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * @param $value + * @return EncodingFailedException + */ + public static function unsupportedType($value) : self + { + $message = sprintf('Unsupported type: %s.', + is_object($value) ? get_class($value) : gettype($value) + ); + return new self($value, $message); + } + } \ No newline at end of file diff --git a/src/ncc/Extensions/ZiProto/Exception/InsufficientDataException.php b/src/ncc/Extensions/ZiProto/Exception/InsufficientDataException.php new file mode 100644 index 0000000..b36d44a --- /dev/null +++ b/src/ncc/Extensions/ZiProto/Exception/InsufficientDataException.php @@ -0,0 +1,25 @@ +value = $value; + } + + /** + * @return int + */ + public function getValue() : int + { + return $this->value; + } + } \ No newline at end of file diff --git a/src/ncc/Extensions/ZiProto/Exception/InvalidOptionException.php b/src/ncc/Extensions/ZiProto/Exception/InvalidOptionException.php new file mode 100644 index 0000000..1431184 --- /dev/null +++ b/src/ncc/Extensions/ZiProto/Exception/InvalidOptionException.php @@ -0,0 +1,29 @@ + 2 + ? sprintf('one of %2$s or %1$s', array_pop($validOptions), implode(', ', $validOptions)) + : implode(' or ', $validOptions); + return new self("Invalid option $invalidOption, use $use."); + } + } \ No newline at end of file diff --git a/src/ncc/Extensions/ZiProto/Ext.php b/src/ncc/Extensions/ZiProto/Ext.php new file mode 100644 index 0000000..d0f7ec2 --- /dev/null +++ b/src/ncc/Extensions/ZiProto/Ext.php @@ -0,0 +1,30 @@ +type = $type; + $this->data = $data; + } + } \ No newline at end of file diff --git a/src/ncc/Extensions/ZiProto/Packet.php b/src/ncc/Extensions/ZiProto/Packet.php new file mode 100644 index 0000000..bcc21bd --- /dev/null +++ b/src/ncc/Extensions/ZiProto/Packet.php @@ -0,0 +1,419 @@ +isDetectStrBin = $options->isDetectStrBinMode(); + $this->isForceStr = $options->isForceStrMode(); + $this->isDetectArrMap = $options->isDetectArrMapMode(); + $this->isForceArr = $options->isForceArrMode(); + $this->isForceFloat32 = $options->isForceFloat32Mode(); + } + + /** + * @param Validator $transformer + * @return Packet + */ + public function registerTransformer(Validator $transformer) : self + { + $this->transformers[] = $transformer; + + return $this; + } + + /** + * @param $value + * @return false|string + */ + public function encode($value) + { + if (is_int($value)) + { + return $this->encodeInt($value); + } + + if (is_string($value)) + { + if ($this->isForceStr) + { + return $this->encodeStr($value); + } + + if ($this->isDetectStrBin) + { + return preg_match(Regex::UTF8_REGEX, $value) + ? $this->encodeStr($value) + : $this->encodeBin($value); + } + + return $this->encodeBin($value); + } + + if (is_array($value)) + { + if ($this->isDetectArrMap) + { + return array_values($value) === $value + ? $this->encodeArray($value) + : $this->encodeMap($value); + } + + return $this->isForceArr ? $this->encodeArray($value) : $this->encodeMap($value); + } + + if (null === $value) + { + return "\xc0"; + } + + if (is_bool($value)) + { + return $value ? "\xc3" : "\xc2"; + } + + if (is_float($value)) + { + return $this->encodeFloat($value); + } + + if ($value instanceof Ext) + { + return $this->encodeExt($value->type, $value->data); + } + + if ($this->transformers) + { + foreach ($this->transformers as $transformer) + { + if (null !== $encoded = $transformer->check($this, $value)) + { + return $encoded; + } + } + } + + throw EncodingFailedException::unsupportedType($value); + } + + /** + * @return string + */ + public function encodeNil() + { + return "\xc0"; + } + + /** + * @param $bool + * @return string + */ + public function encodeBool($bool) + { + return $bool ? "\xc3" : "\xc2"; + } + + /** + * @param $int + * @return string + */ + public function encodeInt($int) + { + if ($int >= 0) + { + if ($int <= 0x7f) + { + return chr($int); + } + + if ($int <= 0xff) + { + return "\xcc". chr($int); + } + + if ($int <= 0xffff) + { + return "\xcd". chr($int >> 8). chr($int); + } + + if ($int <= 0xffffffff) + { + return pack('CN', 0xce, $int); + } + + return pack('CJ', 0xcf, $int); + } + + if ($int >= -0x20) + { + return chr(0xe0 | $int); + } + + if ($int >= -0x80) + { + return "\xd0". chr($int); + } + + if ($int >= -0x8000) + { + return "\xd1". chr($int >> 8). chr($int); + } + + if ($int >= -0x80000000) + { + return pack('CN', 0xd2, $int); + } + + return pack('CJ', 0xd3, $int); + } + + /** + * @param $float + * @return string + */ + public function encodeFloat($float) + { + return $this->isForceFloat32 + ? "\xca". pack('G', $float) + : "\xcb". pack('E', $float); + } + + /** + * @param $str + * @return string + */ + public function encodeStr($str) + { + $length = strlen($str); + + if ($length < 32) + { + return chr(0xa0 | $length).$str; + } + + if ($length <= 0xff) + { + return "\xd9". chr($length).$str; + } + + if ($length <= 0xffff) + { + return "\xda". chr($length >> 8). chr($length).$str; + } + + return pack('CN', 0xdb, $length).$str; + } + + /** + * @param $str + * @return string + */ + public function encodeBin($str) + { + $length = strlen($str); + + if ($length <= 0xff) + { + return "\xc4". chr($length).$str; + } + + if ($length <= 0xffff) + { + return "\xc5". chr($length >> 8). chr($length).$str; + } + + return pack('CN', 0xc6, $length).$str; + } + + /** + * @param $array + * @return false|string + */ + public function encodeArray($array) + { + $data = $this->encodeArrayHeader(count($array)); + + foreach ($array as $val) + { + $data .= $this->encode($val); + } + + return $data; + } + + /** + * @param $size + * @return string + */ + public function encodeArrayHeader($size) + { + if ($size <= 0xf) + { + return chr(0x90 | $size); + } + + if ($size <= 0xffff) + { + return "\xdc". chr($size >> 8). chr($size); + } + + return pack('CN', 0xdd, $size); + } + + /** + * @param $map + * @return false|string + */ + public function encodeMap($map) + { + $data = $this->encodeMapHeader(count($map)); + + if ($this->isForceStr) + { + foreach ($map as $key => $val) + { + $data .= is_string($key) ? $this->encodeStr($key) : $this->encodeInt($key); + $data .= $this->encode($val); + } + + return $data; + } + + if ($this->isDetectStrBin) + { + foreach ($map as $key => $val) + { + $data .= is_string($key) + ? (preg_match(Regex::UTF8_REGEX, $key) ? $this->encodeStr($key) : $this->encodeBin($key)) + : $this->encodeInt($key); + $data .= $this->encode($val); + } + + return $data; + } + + foreach ($map as $key => $val) + { + $data .= is_string($key) ? $this->encodeBin($key) : $this->encodeInt($key); + $data .= $this->encode($val); + } + + return $data; + } + + /** + * @param $size + * @return string + */ + public function encodeMapHeader($size) + { + if ($size <= 0xf) + { + return chr(0x80 | $size); + } + + if ($size <= 0xffff) + { + return "\xde". chr($size >> 8). chr($size); + } + + return pack('CN', 0xdf, $size); + } + + /** + * @param $type + * @param $data + * @return string + */ + public function encodeExt($type, $data) + { + $length = strlen($data); + + switch ($length) + { + case 1: return "\xd4". chr($type).$data; + case 2: return "\xd5". chr($type).$data; + case 4: return "\xd6". chr($type).$data; + case 8: return "\xd7". chr($type).$data; + case 16: return "\xd8". chr($type).$data; + } + + if ($length <= 0xff) + { + return "\xc7". chr($length). chr($type).$data; + } + + if ($length <= 0xffff) + { + return pack('CnC', 0xc8, $length, $type).$data; + } + + return pack('CNC', 0xc9, $length, $type).$data; + } + } diff --git a/src/ncc/Extensions/ZiProto/Type/Binary.php b/src/ncc/Extensions/ZiProto/Type/Binary.php new file mode 100644 index 0000000..2bb9078 --- /dev/null +++ b/src/ncc/Extensions/ZiProto/Type/Binary.php @@ -0,0 +1,24 @@ +data = $data; + } + } \ No newline at end of file diff --git a/src/ncc/Extensions/ZiProto/Type/Map.php b/src/ncc/Extensions/ZiProto/Type/Map.php new file mode 100644 index 0000000..9b1b640 --- /dev/null +++ b/src/ncc/Extensions/ZiProto/Type/Map.php @@ -0,0 +1,24 @@ +map = $map; + } + } \ No newline at end of file diff --git a/src/ncc/Extensions/ZiProto/TypeTransformer/BinaryTransformer.php b/src/ncc/Extensions/ZiProto/TypeTransformer/BinaryTransformer.php new file mode 100644 index 0000000..e4ea2ab --- /dev/null +++ b/src/ncc/Extensions/ZiProto/TypeTransformer/BinaryTransformer.php @@ -0,0 +1,28 @@ +encodeBin($value->data); + } + + return null; + } + } \ No newline at end of file diff --git a/src/ncc/Extensions/ZiProto/TypeTransformer/Extension.php b/src/ncc/Extensions/ZiProto/TypeTransformer/Extension.php new file mode 100644 index 0000000..e40d02f --- /dev/null +++ b/src/ncc/Extensions/ZiProto/TypeTransformer/Extension.php @@ -0,0 +1,24 @@ +encodeMap($value->map); + } + + return null; + } + } \ No newline at end of file diff --git a/src/ncc/Extensions/ZiProto/TypeTransformer/Validator.php b/src/ncc/Extensions/ZiProto/TypeTransformer/Validator.php new file mode 100644 index 0000000..f5a1cae --- /dev/null +++ b/src/ncc/Extensions/ZiProto/TypeTransformer/Validator.php @@ -0,0 +1,18 @@ +encode($value); + } + + /** + * @param string $data + * @param DecodingOptions|int|null $options + * + * @throws InvalidOptionException + * @throws DecodingFailedException + * + * @return mixed + */ + public static function decode(string $data, $options = null) + { + return (new BufferStream($data, $options))->decode(); + } + } \ No newline at end of file diff --git a/src/ncc/autoload.php b/src/ncc/autoload.php index a0b90cf..ae60038 100644 --- a/src/ncc/autoload.php +++ b/src/ncc/autoload.php @@ -22,7 +22,11 @@ spl_autoload_register( 'ncc\\exceptions\\filenotfoundexception' => '/Exceptions/FileNotFoundException.php', 'ncc\\exceptions\\invalidprojectconfigurationexception' => '/Exceptions/InvalidProjectConfigurationException.php', 'ncc\\exceptions\\invalidscopeexception' => '/Exceptions/InvalidScopeException.php', + 'ncc\\exceptions\\malformedjsonexception' => '/Exceptions/MalformedJsonException.php', 'ncc\\ncc' => '/ncc.php', + 'ncc\\ncc\\ziproto\\typetransformer\\binarytransformer' => '/Extensions/ZiProto/TypeTransformer/BinaryTransformer.php', + 'ncc\\ncc\\ziproto\\typetransformer\\extension' => '/Extensions/ZiProto/TypeTransformer/Extension.php', + 'ncc\\ncc\\ziproto\\typetransformer\\validator' => '/Extensions/ZiProto/TypeTransformer/Validator.php', 'ncc\\objects\\projectconfiguration' => '/Objects/ProjectConfiguration.php', 'ncc\\objects\\projectconfiguration\\assembly' => '/Objects/ProjectConfiguration/Assembly.php', 'ncc\\objects\\projectconfiguration\\build' => '/Objects/ProjectConfiguration/Build.php', @@ -104,7 +108,23 @@ spl_autoload_register( 'ncc\\utilities\\pathfinder' => '/Utilities/PathFinder.php', 'ncc\\utilities\\resolver' => '/Utilities/Resolver.php', 'ncc\\utilities\\security' => '/Utilities/Security.php', - 'ncc\\utilities\\validate' => '/Utilities/Validate.php' + 'ncc\\utilities\\validate' => '/Utilities/Validate.php', + 'ncc\\ziproto\\abstracts\\options' => '/Extensions/ZiProto/Abstracts/Options.php', + 'ncc\\ziproto\\abstracts\\regex' => '/Extensions/ZiProto/Abstracts/Regex.php', + 'ncc\\ziproto\\bufferstream' => '/Extensions/ZiProto/BufferStream.php', + 'ncc\\ziproto\\decodingoptions' => '/Extensions/ZiProto/DecodingOptions.php', + 'ncc\\ziproto\\encodingoptions' => '/Extensions/ZiProto/EncodingOptions.php', + 'ncc\\ziproto\\exception\\decodingfailedexception' => '/Extensions/ZiProto/Exception/DecodingFailedException.php', + 'ncc\\ziproto\\exception\\encodingfailedexception' => '/Extensions/ZiProto/Exception/EncodingFailedException.php', + 'ncc\\ziproto\\exception\\insufficientdataexception' => '/Extensions/ZiProto/Exception/InsufficientDataException.php', + 'ncc\\ziproto\\exception\\integeroverflowexception' => '/Extensions/ZiProto/Exception/IntegerOverflowException.php', + 'ncc\\ziproto\\exception\\invalidoptionexception' => '/Extensions/ZiProto/Exception/InvalidOptionException.php', + 'ncc\\ziproto\\ext' => '/Extensions/ZiProto/Ext.php', + 'ncc\\ziproto\\packet' => '/Extensions/ZiProto/Packet.php', + 'ncc\\ziproto\\type\\binary' => '/Extensions/ZiProto/Type/Binary.php', + 'ncc\\ziproto\\type\\map' => '/Extensions/ZiProto/Type/Map.php', + 'ncc\\ziproto\\typetransformer\\maptransformer' => '/Extensions/ZiProto/TypeTransformer/MapTransformer.php', + 'ncc\\ziproto\\ziproto' => '/Extensions/ZiProto/ZiProto.php' ); } $cn = strtolower($class); diff --git a/tests/project_configuration/load_configuration.php b/tests/project_configuration/load_configuration.php index e878b2c..992fe8f 100644 --- a/tests/project_configuration/load_configuration.php +++ b/tests/project_configuration/load_configuration.php @@ -1,3 +1,4 @@