From 55981e8de97e52bcbe9b0741e7858259fca1d88c Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Fri, 31 May 2019 14:24:46 +0200 Subject: [PATCH 01/23] Base project skeleton --- .gitignore | 11 + .idea/modules.xml | 8 + .idea/php-mysql-binary-protocol.iml | 11 + composer.json | 27 + composer.lock | 1539 +++++++++++++++++++++++++++ src/ReadBuffer.php | 35 + 6 files changed, 1631 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/modules.xml create mode 100644 .idea/php-mysql-binary-protocol.iml create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 src/ReadBuffer.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c361ba7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# OSX +.DS_Store + +# Composer +/vendor/ + +# PHPStorm (except class maps) +/.idea/* +!/.idea/*.iml +!/.idea/modules.xml + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..fe17300 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/php-mysql-binary-protocol.iml b/.idea/php-mysql-binary-protocol.iml new file mode 100644 index 0000000..807507e --- /dev/null +++ b/.idea/php-mysql-binary-protocol.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..7dbc239 --- /dev/null +++ b/composer.json @@ -0,0 +1,27 @@ +{ + "name": "ecomdv/mysql-binary-protocol", + "description": "Implementation of MySQL binary protocol independent from async frameworks", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Ivan Chepurnyi", + "email": "ivan.chepurnyi@ecomdev.org" + } + ], + "minimum-stability": "dev", + "require": {}, + "autoload": { + "psr-4": { + "EcomDev\\MySQLBinaryProtocol\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "EcomDev\\MySQLBinaryProtocol\\": "tests/" + } + }, + "require-dev": { + "phpunit/phpunit": "^8.1" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..ddc34d9 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1539 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "2de4be3196229246eed07eab97a2c08c", + "content-hash": "401ef717f2e491fdbe6fdf1b4b8f400f", + "packages": [], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "fb21dea73761426178f8665f6b567c58fee488ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/fb21dea73761426178f8665f6b567c58fee488ff", + "reference": "fb21dea73761426178f8665f6b567c58fee488ff", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2019-05-24 18:15:18" + }, + { + "name": "myclabs/deep-copy", + "version": "1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72", + "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2019-04-07 13:18:21" + }, + { + "name": "phar-io/manifest", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^2.0", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2018-07-08 19:23:20" + }, + { + "name": "phar-io/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2018-07-08 19:19:57" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2017-09-11 18:02:19" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", + "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "~1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2019-04-30 17:48:53" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2017-07-14 14:27:02" + }, + { + "name": "phpspec/prophecy", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "7e272180527c34a97680de85eb5aba0847a664e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/7e272180527c34a97680de85eb5aba0847a664e0", + "reference": "7e272180527c34a97680de85eb5aba0847a664e0", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2018-12-18 15:40:51" + }, + { + "name": "phpunit/php-code-coverage", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "d720d14ef701c38cc7d50be1843a34d744e919f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d720d14ef701c38cc7d50be1843a34d744e919f6", + "reference": "d720d14ef701c38cc7d50be1843a34d744e919f6", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.2", + "phpunit/php-file-iterator": "^2.0.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^4.1", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.0" + }, + "suggest": { + "ext-xdebug": "^2.6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2019-05-29 11:51:37" + }, + { + "name": "phpunit/php-file-iterator", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "80ca798a9fb7b60c6a2169d8c6cad9f00eb3a06a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/80ca798a9fb7b60c6a2169d8c6cad9f00eb3a06a", + "reference": "80ca798a9fb7b60c6a2169d8c6cad9f00eb3a06a", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2019-05-24 06:36:01" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "e826e3ab11b1ca789b10a00b1a2188feb381beb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e826e3ab11b1ca789b10a00b1a2188feb381beb5", + "reference": "e826e3ab11b1ca789b10a00b1a2188feb381beb5", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2019-05-24 06:37:41" + }, + { + "name": "phpunit/php-token-stream", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "2d574d6767341959316726121431ecb9c4a61037" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/2d574d6767341959316726121431ecb9c4a61037", + "reference": "2d574d6767341959316726121431ecb9c4a61037", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2019-05-24 06:39:22" + }, + { + "name": "phpunit/phpunit", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "e4390fae89bf940f430604f6fd4b58804a530816" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e4390fae89bf940f430604f6fd4b58804a530816", + "reference": "e4390fae89bf940f430604f6fd4b58804a530816", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.7", + "phar-io/manifest": "^1.0.2", + "phar-io/version": "^2.0", + "php": "^7.2", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^7.0", + "phpunit/php-file-iterator": "^2.0.1", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^2.1", + "sebastian/comparator": "^3.0", + "sebastian/diff": "^3.0", + "sebastian/environment": "^4.2.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^3.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^2.0", + "sebastian/type": "^1.0", + "sebastian/version": "^2.0.1" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*", + "phpunit/php-invoker": "^2.0" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2019-05-29 04:38:41" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "aa169192fe98c7270af0ec5c57e631094ad930df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/aa169192fe98c7270af0ec5c57e631094ad930df", + "reference": "aa169192fe98c7270af0ec5c57e631094ad930df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2019-05-24 06:36:34" + }, + { + "name": "sebastian/comparator", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "6ddb43a0106887320958344fc6e55aa033154b9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/6ddb43a0106887320958344fc6e55aa033154b9d", + "reference": "6ddb43a0106887320958344fc6e55aa033154b9d", + "shasum": "" + }, + "require": { + "php": "^7.1", + "sebastian/diff": "^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2019-05-24 06:32:56" + }, + { + "name": "sebastian/diff", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "1d90f91424a056ebd763d7046ee5957d160c1c24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/1d90f91424a056ebd763d7046ee5957d160c1c24", + "reference": "1d90f91424a056ebd763d7046ee5957d160c1c24", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "time": "2019-05-24 06:38:22" + }, + { + "name": "sebastian/environment", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "48c9235dc43ce325b15d950f53996da748f2d571" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/48c9235dc43ce325b15d950f53996da748f2d571", + "reference": "48c9235dc43ce325b15d950f53996da748f2d571", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2019-05-24 06:34:26" + }, + { + "name": "sebastian/exporter", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "cf3a70cdc3af7b80ad571adae1ab718eb578be2b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/cf3a70cdc3af7b80ad571adae1ab718eb578be2b", + "reference": "cf3a70cdc3af7b80ad571adae1ab718eb578be2b", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2019-05-24 06:35:43" + }, + { + "name": "sebastian/global-state", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "6ddc744049960cad6878d2b9c82d7ccd37afb061" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/6ddc744049960cad6878d2b9c82d7ccd37afb061", + "reference": "6ddc744049960cad6878d2b9c82d7ccd37afb061", + "shasum": "" + }, + "require": { + "php": "^7.2", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^8.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2019-05-24 06:34:46" + }, + { + "name": "sebastian/object-enumerator", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "eb45b4b8fd5f845d36851767a6fe00ee15cb8952" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/eb45b4b8fd5f845d36851767a6fe00ee15cb8952", + "reference": "eb45b4b8fd5f845d36851767a6fe00ee15cb8952", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2019-05-24 06:37:20" + }, + { + "name": "sebastian/object-reflector", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "a2539861984780016bb3d75420452d95f798e25d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/a2539861984780016bb3d75420452d95f798e25d", + "reference": "a2539861984780016bb3d75420452d95f798e25d", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2019-05-24 06:35:22" + }, + { + "name": "sebastian/recursion-context", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "b6a7e76741fee33ec10ccb55772e65d817a8f2fd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b6a7e76741fee33ec10ccb55772e65d817a8f2fd", + "reference": "b6a7e76741fee33ec10ccb55772e65d817a8f2fd", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2019-05-24 06:37:03" + }, + { + "name": "sebastian/resource-operations", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "366bbb128ea2fd570ab9986e6d79a7167fd5c041" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/366bbb128ea2fd570ab9986e6d79a7167fd5c041", + "reference": "366bbb128ea2fd570ab9986e6d79a7167fd5c041", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2019-05-24 06:40:48" + }, + { + "name": "sebastian/type", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "5de1c9e0753b992b0e6d711a3bb6f8f94ed56d62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/5de1c9e0753b992b0e6d711a3bb6f8f94ed56d62", + "reference": "5de1c9e0753b992b0e6d711a3bb6f8f94ed56d62", + "shasum": "" + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "time": "2019-05-26 06:11:23" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03 07:35:21" + }, + { + "name": "symfony/polyfill-ctype", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "82ebae02209c21113908c229e9883c419720738a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2019-02-06 07:57:58" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "1c42705be2b6c1de5904f8afacef5895cab44bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/1c42705be2b6c1de5904f8afacef5895cab44bf8", + "reference": "1c42705be2b6c1de5904f8afacef5895cab44bf8", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2019-04-04 09:56:43" + }, + { + "name": "webmozart/assert", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2018-12-25 11:19:39" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/src/ReadBuffer.php b/src/ReadBuffer.php new file mode 100644 index 0000000..fe68142 --- /dev/null +++ b/src/ReadBuffer.php @@ -0,0 +1,35 @@ + Date: Fri, 31 May 2019 23:15:29 +0200 Subject: [PATCH 02/23] Add base skeleton --- src/InMemoryReadBuffer.php | 52 +++++++++++++++++++++++++++ src/IncompleteBufferException.php | 15 ++++++++ src/ReadBuffer.php | 17 +++++---- src/ReadBufferFragment.php | 59 +++++++++++++++++++++++++++++++ tests/InMemoryReadBufferTest.php | 40 +++++++++++++++++++++ 5 files changed, 176 insertions(+), 7 deletions(-) create mode 100644 src/InMemoryReadBuffer.php create mode 100644 src/IncompleteBufferException.php create mode 100644 src/ReadBufferFragment.php create mode 100644 tests/InMemoryReadBufferTest.php diff --git a/src/InMemoryReadBuffer.php b/src/InMemoryReadBuffer.php new file mode 100644 index 0000000..692a05e --- /dev/null +++ b/src/InMemoryReadBuffer.php @@ -0,0 +1,52 @@ +buffer = $data; + $this->bufferSize = 0; + } + + /** + * {@inheritDoc} + */ + public function readFragment(callable $reader): bool + { + // TODO: Implement readFragment() method. + } + + /** + * {@inheritDoc} + */ + public function isFullPacket(): bool + { + return false; + } + + /** + * {@inheritDoc} + */ + public function nextPacket(): void + { + // TODO: Implement nextPacket() method. + } +} diff --git a/src/IncompleteBufferException.php b/src/IncompleteBufferException.php new file mode 100644 index 0000000..e0ec68b --- /dev/null +++ b/src/IncompleteBufferException.php @@ -0,0 +1,15 @@ +buffer = new InMemoryReadBuffer(); + } + + /** @test */ + public function reportsIncompleteBufferWhenNotFullLengthProvided() + { + $this->buffer->append("\x00\x00\x01\x00"); + + $this->assertFalse($this->buffer->isFullPacket()); + } + + /** @test */ + public function reportsCompleteBufferWhenAllDataIsProvidedAtOnce() + { + $this->buffer->append("\x01\x00\x00\x00\x01"); + + $this->assertTrue($this->buffer->isFullPacket()); + } +} From c22b14f77c13c96dedf5e1dd743e4958621cde4a Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Sun, 2 Jun 2019 01:45:54 +0200 Subject: [PATCH 03/23] Rename buffer to reflect its implementation, create integer reader for buffer --- composer.json | 6 +- composer.lock | 237 +++++++----------- src/BinaryIntegerReader.php | 50 ++++ src/ReadBuffer.php | 9 +- ...oryReadBuffer.php => StringReadBuffer.php} | 39 ++- tests/BinaryIntegerReaderTest.php | 114 +++++++++ ...ufferTest.php => StringReadBufferTest.php} | 11 +- 7 files changed, 309 insertions(+), 157 deletions(-) create mode 100644 src/BinaryIntegerReader.php rename src/{InMemoryReadBuffer.php => StringReadBuffer.php} (55%) create mode 100644 tests/BinaryIntegerReaderTest.php rename tests/{InMemoryReadBufferTest.php => StringReadBufferTest.php} (77%) diff --git a/composer.json b/composer.json index 7dbc239..9b31b9f 100644 --- a/composer.json +++ b/composer.json @@ -9,8 +9,10 @@ "email": "ivan.chepurnyi@ecomdev.org" } ], - "minimum-stability": "dev", - "require": {}, + "minimum-stability": "stable", + "require": { + "ext-bcmath": "" + }, "autoload": { "psr-4": { "EcomDev\\MySQLBinaryProtocol\\": "src/" diff --git a/composer.lock b/composer.lock index ddc34d9..3ff1432 100644 --- a/composer.lock +++ b/composer.lock @@ -4,22 +4,22 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "2de4be3196229246eed07eab97a2c08c", - "content-hash": "401ef717f2e491fdbe6fdf1b4b8f400f", + "hash": "9c7a720e2cbf882dc19d228afcddcfcd", + "content-hash": "c49bf819e0695a2361ebfe74ca8378ee", "packages": [], "packages-dev": [ { "name": "doctrine/instantiator", - "version": "dev-master", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "fb21dea73761426178f8665f6b567c58fee488ff" + "reference": "a2c590166b2133a4633738648b6b064edae0814a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/fb21dea73761426178f8665f6b567c58fee488ff", - "reference": "fb21dea73761426178f8665f6b567c58fee488ff", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", "shasum": "" }, "require": { @@ -62,11 +62,11 @@ "constructor", "instantiate" ], - "time": "2019-05-24 18:15:18" + "time": "2019-03-17 17:37:11" }, { "name": "myclabs/deep-copy", - "version": "1.x-dev", + "version": "1.9.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", @@ -114,7 +114,7 @@ }, { "name": "phar-io/manifest", - "version": "dev-master", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", @@ -368,16 +368,16 @@ }, { "name": "phpspec/prophecy", - "version": "dev-master", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "7e272180527c34a97680de85eb5aba0847a664e0" + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/7e272180527c34a97680de85eb5aba0847a664e0", - "reference": "7e272180527c34a97680de85eb5aba0847a664e0", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", "shasum": "" }, "require": { @@ -398,8 +398,8 @@ } }, "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" + "psr-0": { + "Prophecy\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -427,20 +427,20 @@ "spy", "stub" ], - "time": "2018-12-18 15:40:51" + "time": "2018-08-05 17:53:17" }, { "name": "phpunit/php-code-coverage", - "version": "dev-master", + "version": "7.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "d720d14ef701c38cc7d50be1843a34d744e919f6" + "reference": "6024c8a6cb962d496b7bd049ed8f48473824176d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d720d14ef701c38cc7d50be1843a34d744e919f6", - "reference": "d720d14ef701c38cc7d50be1843a34d744e919f6", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6024c8a6cb962d496b7bd049ed8f48473824176d", + "reference": "6024c8a6cb962d496b7bd049ed8f48473824176d", "shasum": "" }, "require": { @@ -490,20 +490,20 @@ "testing", "xunit" ], - "time": "2019-05-29 11:51:37" + "time": "2019-05-29 09:59:31" }, { "name": "phpunit/php-file-iterator", - "version": "dev-master", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "80ca798a9fb7b60c6a2169d8c6cad9f00eb3a06a" + "reference": "050bedf145a257b1ff02746c31894800e5122946" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/80ca798a9fb7b60c6a2169d8c6cad9f00eb3a06a", - "reference": "80ca798a9fb7b60c6a2169d8c6cad9f00eb3a06a", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", + "reference": "050bedf145a257b1ff02746c31894800e5122946", "shasum": "" }, "require": { @@ -540,7 +540,7 @@ "filesystem", "iterator" ], - "time": "2019-05-24 06:36:01" + "time": "2018-09-13 20:33:42" }, { "name": "phpunit/php-text-template", @@ -585,16 +585,16 @@ }, { "name": "phpunit/php-timer", - "version": "dev-master", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "e826e3ab11b1ca789b10a00b1a2188feb381beb5" + "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e826e3ab11b1ca789b10a00b1a2188feb381beb5", - "reference": "e826e3ab11b1ca789b10a00b1a2188feb381beb5", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b389aebe1b8b0578430bda0c7c95a829608e059", + "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059", "shasum": "" }, "require": { @@ -630,20 +630,20 @@ "keywords": [ "timer" ], - "time": "2019-05-24 06:37:41" + "time": "2019-02-20 10:12:59" }, { "name": "phpunit/php-token-stream", - "version": "dev-master", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "2d574d6767341959316726121431ecb9c4a61037" + "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/2d574d6767341959316726121431ecb9c4a61037", - "reference": "2d574d6767341959316726121431ecb9c4a61037", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/c99e3be9d3e85f60646f152f9002d46ed7770d18", + "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18", "shasum": "" }, "require": { @@ -679,20 +679,20 @@ "keywords": [ "tokenizer" ], - "time": "2019-05-24 06:39:22" + "time": "2018-10-30 05:52:18" }, { "name": "phpunit/phpunit", - "version": "dev-master", + "version": "8.1.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "e4390fae89bf940f430604f6fd4b58804a530816" + "reference": "e3c9da6e645492c461e0a11eca117f83f4f4c840" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e4390fae89bf940f430604f6fd4b58804a530816", - "reference": "e4390fae89bf940f430604f6fd4b58804a530816", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e3c9da6e645492c461e0a11eca117f83f4f4c840", + "reference": "e3c9da6e645492c461e0a11eca117f83f4f4c840", "shasum": "" }, "require": { @@ -714,12 +714,11 @@ "phpunit/php-timer": "^2.1", "sebastian/comparator": "^3.0", "sebastian/diff": "^3.0", - "sebastian/environment": "^4.2.1", + "sebastian/environment": "^4.1", "sebastian/exporter": "^3.1", "sebastian/global-state": "^3.0", "sebastian/object-enumerator": "^3.0.3", "sebastian/resource-operations": "^2.0", - "sebastian/type": "^1.0", "sebastian/version": "^2.0.1" }, "require-dev": { @@ -736,7 +735,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "8.2-dev" + "dev-master": "8.1-dev" } }, "autoload": { @@ -762,20 +761,20 @@ "testing", "xunit" ], - "time": "2019-05-29 04:38:41" + "time": "2019-05-28 11:53:42" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "dev-master", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "aa169192fe98c7270af0ec5c57e631094ad930df" + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/aa169192fe98c7270af0ec5c57e631094ad930df", - "reference": "aa169192fe98c7270af0ec5c57e631094ad930df", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", "shasum": "" }, "require": { @@ -807,20 +806,20 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2019-05-24 06:36:34" + "time": "2017-03-04 06:30:41" }, { "name": "sebastian/comparator", - "version": "dev-master", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "6ddb43a0106887320958344fc6e55aa033154b9d" + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/6ddb43a0106887320958344fc6e55aa033154b9d", - "reference": "6ddb43a0106887320958344fc6e55aa033154b9d", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", "shasum": "" }, "require": { @@ -871,20 +870,20 @@ "compare", "equality" ], - "time": "2019-05-24 06:32:56" + "time": "2018-07-12 15:12:46" }, { "name": "sebastian/diff", - "version": "dev-master", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "1d90f91424a056ebd763d7046ee5957d160c1c24" + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/1d90f91424a056ebd763d7046ee5957d160c1c24", - "reference": "1d90f91424a056ebd763d7046ee5957d160c1c24", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", "shasum": "" }, "require": { @@ -927,20 +926,20 @@ "unidiff", "unified diff" ], - "time": "2019-05-24 06:38:22" + "time": "2019-02-04 06:01:07" }, { "name": "sebastian/environment", - "version": "dev-master", + "version": "4.2.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "48c9235dc43ce325b15d950f53996da748f2d571" + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/48c9235dc43ce325b15d950f53996da748f2d571", - "reference": "48c9235dc43ce325b15d950f53996da748f2d571", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404", + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404", "shasum": "" }, "require": { @@ -980,20 +979,20 @@ "environment", "hhvm" ], - "time": "2019-05-24 06:34:26" + "time": "2019-05-05 09:05:15" }, { "name": "sebastian/exporter", - "version": "dev-master", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "cf3a70cdc3af7b80ad571adae1ab718eb578be2b" + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/cf3a70cdc3af7b80ad571adae1ab718eb578be2b", - "reference": "cf3a70cdc3af7b80ad571adae1ab718eb578be2b", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", "shasum": "" }, "require": { @@ -1047,20 +1046,20 @@ "export", "exporter" ], - "time": "2019-05-24 06:35:43" + "time": "2017-04-03 13:19:02" }, { "name": "sebastian/global-state", - "version": "dev-master", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "6ddc744049960cad6878d2b9c82d7ccd37afb061" + "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/6ddc744049960cad6878d2b9c82d7ccd37afb061", - "reference": "6ddc744049960cad6878d2b9c82d7ccd37afb061", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", + "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", "shasum": "" }, "require": { @@ -1101,20 +1100,20 @@ "keywords": [ "global state" ], - "time": "2019-05-24 06:34:46" + "time": "2019-02-01 05:30:01" }, { "name": "sebastian/object-enumerator", - "version": "dev-master", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "eb45b4b8fd5f845d36851767a6fe00ee15cb8952" + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/eb45b4b8fd5f845d36851767a6fe00ee15cb8952", - "reference": "eb45b4b8fd5f845d36851767a6fe00ee15cb8952", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", "shasum": "" }, "require": { @@ -1148,20 +1147,20 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2019-05-24 06:37:20" + "time": "2017-08-03 12:35:26" }, { "name": "sebastian/object-reflector", - "version": "dev-master", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "a2539861984780016bb3d75420452d95f798e25d" + "reference": "773f97c67f28de00d397be301821b06708fca0be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/a2539861984780016bb3d75420452d95f798e25d", - "reference": "a2539861984780016bb3d75420452d95f798e25d", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", "shasum": "" }, "require": { @@ -1193,20 +1192,20 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2019-05-24 06:35:22" + "time": "2017-03-29 09:07:27" }, { "name": "sebastian/recursion-context", - "version": "dev-master", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "b6a7e76741fee33ec10ccb55772e65d817a8f2fd" + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b6a7e76741fee33ec10ccb55772e65d817a8f2fd", - "reference": "b6a7e76741fee33ec10ccb55772e65d817a8f2fd", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", "shasum": "" }, "require": { @@ -1246,20 +1245,20 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2019-05-24 06:37:03" + "time": "2017-03-03 06:23:57" }, { "name": "sebastian/resource-operations", - "version": "dev-master", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "366bbb128ea2fd570ab9986e6d79a7167fd5c041" + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/366bbb128ea2fd570ab9986e6d79a7167fd5c041", - "reference": "366bbb128ea2fd570ab9986e6d79a7167fd5c041", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", "shasum": "" }, "require": { @@ -1288,53 +1287,7 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2019-05-24 06:40:48" - }, - { - "name": "sebastian/type", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "5de1c9e0753b992b0e6d711a3bb6f8f94ed56d62" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/5de1c9e0753b992b0e6d711a3bb6f8f94ed56d62", - "reference": "5de1c9e0753b992b0e6d711a3bb6f8f94ed56d62", - "shasum": "" - }, - "require": { - "php": "^7.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", - "time": "2019-05-26 06:11:23" + "time": "2018-10-04 04:07:39" }, { "name": "sebastian/version", @@ -1381,7 +1334,7 @@ }, { "name": "symfony/polyfill-ctype", - "version": "dev-master", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -1530,7 +1483,7 @@ } ], "aliases": [], - "minimum-stability": "dev", + "minimum-stability": "stable", "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, diff --git a/src/BinaryIntegerReader.php b/src/BinaryIntegerReader.php new file mode 100644 index 0000000..f8b94c5 --- /dev/null +++ b/src/BinaryIntegerReader.php @@ -0,0 +1,50 @@ +readFixed(substr($binary, 0, 4), 4); + $high = $this->readFixed(substr($binary, 4, 4), 4); + + return hexdec(dechex($low) . dechex($high)); + } + + if ($size > 8) { + throw new \RuntimeException('Cannot read integers above 8 bytes'); + } + + if ($size > 4) { + return current( + unpack('P', $binary . str_pad("\x00", 8-$size)) + ); + } + + for ($i = 1; $i < $size; $i ++) { + $result += ord($binary[$i]) << (8*$i); + } + + return $result; + } +} diff --git a/src/ReadBuffer.php b/src/ReadBuffer.php index a8faead..bf30b71 100644 --- a/src/ReadBuffer.php +++ b/src/ReadBuffer.php @@ -16,7 +16,7 @@ interface ReadBuffer public function append(string $data): void; /** - * Executes callable to read a fragment of the buffer + * Executes callable to read a fragment of the buffer by using ReadBufferFragment * * In case of IncompleteBufferException is thrown during reading, * method returns false and same data will be returned on the next read. @@ -26,13 +26,14 @@ public function append(string $data): void; public function readFragment(callable $reader): bool; /** - * Checks if current packet readable till the end + * Checks if current packet readable completely to the end */ public function isFullPacket(): bool; /** - * Moves internal pointer to start reading next packet, - * ignoring any left data in current one + * Moves internal pointer to start reading next packet + * + * Any data that is left unread in the packet will be discarded */ public function nextPacket(): void; } diff --git a/src/InMemoryReadBuffer.php b/src/StringReadBuffer.php similarity index 55% rename from src/InMemoryReadBuffer.php rename to src/StringReadBuffer.php index 692a05e..7b8b82e 100644 --- a/src/InMemoryReadBuffer.php +++ b/src/StringReadBuffer.php @@ -9,21 +9,36 @@ namespace EcomDev\MySQLBinaryProtocol; -class InMemoryReadBuffer implements ReadBuffer +class StringReadBuffer implements ReadBuffer { - /** @var string */ - private $buffer = ''; /** @var int */ private $bufferSize = 0; + /** @var int */ + private $currentPosition = 0; + + /** + * @var int[] + */ + private $currentPacket = []; + + /** + * @var string + */ + private $buffer = ''; + /** * {@inheritDoc} */ public function append(string $data): void { - $this->buffer = $data; - $this->bufferSize = 0; + $this->buffer .= $data; + $this->bufferSize += strlen($data); + + if (!$this->currentPacket) { + $this->initPacket(); + } } /** @@ -49,4 +64,18 @@ public function nextPacket(): void { // TODO: Implement nextPacket() method. } + + private function initPacket(): void + { + $totalLength = ord($this->buffer[0]) + + (ord($this->buffer[1]) << 8) + + (ord($this->buffer[1]) << 16) + ; + $this->currentPacket = [ + + ]; + } + + + } diff --git a/tests/BinaryIntegerReaderTest.php b/tests/BinaryIntegerReaderTest.php new file mode 100644 index 0000000..4f31211 --- /dev/null +++ b/tests/BinaryIntegerReaderTest.php @@ -0,0 +1,114 @@ +reader = new BinaryIntegerReader(); + } + + /** + * @test + * @dataProvider oneByteIntegers + */ + public function readsFixedOneByteInteger(string $binary, int $expectedResult) + { + $this->assertEquals($expectedResult, $this->reader->readFixed($binary, 1)); + } + + /** + * @test + * @dataProvider twoByteIntegers + */ + public function readsFixedTwoByteInteger(string $binary, int $expectedResult) + { + $this->assertEquals($expectedResult, $this->reader->readFixed($binary, 2)); + } + + /** + * @test + * @dataProvider threeByteIntegers + */ + public function readsFixedThreeByteInteger(string $binary, int $expectedResult) + { + $this->assertEquals($expectedResult, $this->reader->readFixed($binary, 3)); + } + + /** + * @test + */ + public function readsSevenByteInteger() + { + $this->assertEquals(72057594037927935, $this->reader->readFixed("\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 7)); + } + + /** + * @test + * @dataProvider eightByteIntegers + */ + public function readsEightByteIntegers(string $binary, $expectedResult) + { + $this->assertEquals($expectedResult, $this->reader->readFixed($binary, 8)); + } + + /** @test */ + public function doesNotSupportValuesHigherThan8Bytes() + { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Cannot read integers above 8 bytes'); + + $this->reader->readFixed("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 9); + } + + public function oneByteIntegers() + { + return [ + 'zero' => ["\x00", 0], + 'one' => ["\x01", 1], + 'ten' => ["\x0A", 10], + 'hundred' => ["\x64", 100], + 'max' => ["\xFF", 255], + ]; + } + + public function twoByteIntegers() + { + return [ + 'zero' => ["\x00\x00", 0], + 'ten' => ["\x0A\x00", 10], + 'thousand' => ["\xE8\x03", 1000], + 'max' => ["\xFF\xFF", 65535], + ]; + } + + public function threeByteIntegers() + { + return [ + 'zero' => ["\x00\x00\x00", 0], + 'two_byte_max' => ["\xFF\xFF\x00", 65535], + 'two_byte_max+1' => ["\x00\x00\x01", 65536], + 'max' => ["\xFF\xFF\xFF", 16777215], + ]; + } + + public function eightByteIntegers() + { + return [ + 'seven_byte_max' => ["\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00", 72057594037927935], + 'eight_byte_max' => ["\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 18446744073709551615], + ]; + } +} diff --git a/tests/InMemoryReadBufferTest.php b/tests/StringReadBufferTest.php similarity index 77% rename from tests/InMemoryReadBufferTest.php rename to tests/StringReadBufferTest.php index 548a22e..cea8540 100644 --- a/tests/InMemoryReadBufferTest.php +++ b/tests/StringReadBufferTest.php @@ -10,7 +10,7 @@ use PHPUnit\Framework\TestCase; -class InMemoryReadBufferTest extends TestCase +class StringReadBufferTest extends TestCase { /** * @var ReadBuffer @@ -19,13 +19,16 @@ class InMemoryReadBufferTest extends TestCase protected function setUp(): void { - $this->buffer = new InMemoryReadBuffer(); + $this->buffer = new StringReadBuffer(); } - /** @test */ + /** + * @test + * + */ public function reportsIncompleteBufferWhenNotFullLengthProvided() { - $this->buffer->append("\x00\x00\x01\x00"); + $this->buffer->append("\xFF\xFF\xFF\x00Some Payload"); $this->assertFalse($this->buffer->isFullPacket()); } From 9bc963ce8a865e276090de2fded4e9ab5b722062 Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Sun, 2 Jun 2019 13:18:05 +0200 Subject: [PATCH 04/23] Implement all cases of 8byte unsigned integer decoding --- src/BinaryIntegerReader.php | 19 +++++-------------- tests/BinaryIntegerReaderTest.php | 4 ++++ 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/BinaryIntegerReader.php b/src/BinaryIntegerReader.php index f8b94c5..8a5faae 100644 --- a/src/BinaryIntegerReader.php +++ b/src/BinaryIntegerReader.php @@ -21,24 +21,15 @@ public function readFixed(string $binary, int $size) return $result; } - - if ($size === 8) { - // This is a workaround of the bug in unpack for values - // above signed int value for little endian - $low = $this->readFixed(substr($binary, 0, 4), 4); - $high = $this->readFixed(substr($binary, 4, 4), 4); - - return hexdec(dechex($low) . dechex($high)); - } - if ($size > 8) { throw new \RuntimeException('Cannot read integers above 8 bytes'); } - if ($size > 4) { - return current( - unpack('P', $binary . str_pad("\x00", 8-$size)) - ); + + if ($size === 8) { + // This is a workaround of the bug in unpack for values + // above signed int value for little endian + return hexdec(bin2hex(strrev(substr($binary, 0, 8)))); } for ($i = 1; $i < $size; $i ++) { diff --git a/tests/BinaryIntegerReaderTest.php b/tests/BinaryIntegerReaderTest.php index 4f31211..56cb83c 100644 --- a/tests/BinaryIntegerReaderTest.php +++ b/tests/BinaryIntegerReaderTest.php @@ -90,6 +90,7 @@ public function twoByteIntegers() 'zero' => ["\x00\x00", 0], 'ten' => ["\x0A\x00", 10], 'thousand' => ["\xE8\x03", 1000], + 'random' => ["\x20\x8a", 35360], 'max' => ["\xFF\xFF", 65535], ]; } @@ -100,6 +101,7 @@ public function threeByteIntegers() 'zero' => ["\x00\x00\x00", 0], 'two_byte_max' => ["\xFF\xFF\x00", 65535], 'two_byte_max+1' => ["\x00\x00\x01", 65536], + 'random' => ["\x68\xD6\xEF", 15717992], 'max' => ["\xFF\xFF\xFF", 16777215], ]; } @@ -108,7 +110,9 @@ public function eightByteIntegers() { return [ 'seven_byte_max' => ["\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00", 72057594037927935], + 'above_singed_eight_max' => ["\x00\x00\x00\x00\x00\x00\x00\x80", 9223372036854775808], 'eight_byte_max' => ["\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 18446744073709551615], + 'eight_byte_max_redundant_byte' => ["\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 18446744073709551615], ]; } } From 2139dadfb589fff83e3a1fd0bc44b0b14f98d2ad Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Sun, 2 Jun 2019 15:41:35 +0200 Subject: [PATCH 05/23] Change to unpack for better performance --- src/BinaryIntegerReader.php | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/BinaryIntegerReader.php b/src/BinaryIntegerReader.php index 8a5faae..5795c4a 100644 --- a/src/BinaryIntegerReader.php +++ b/src/BinaryIntegerReader.php @@ -8,6 +8,8 @@ namespace EcomDev\MySQLBinaryProtocol; +use function unpack; + /** * @internal */ @@ -15,27 +17,34 @@ class BinaryIntegerReader { public function readFixed(string $binary, int $size) { - $result = ord($binary); - if ($size === 1) { - return $result; + return unpack('C', $binary)[1]; } - if ($size > 8) { - throw new \RuntimeException('Cannot read integers above 8 bytes'); + if ($size === 2) { + return unpack('v', $binary)[1]; + } + + if ($size === 3) { + return unpack('V', $binary."\x00")[1]; } + if ($size === 4) { + return unpack('V', $binary)[1]; + } if ($size === 8) { - // This is a workaround of the bug in unpack for values - // above signed int value for little endian - return hexdec(bin2hex(strrev(substr($binary, 0, 8)))); + if (strlen($binary) > $size) { + $binary = substr($binary, 0, $size); + } + + return \hexdec(\bin2hex(\strrev($binary))); } - for ($i = 1; $i < $size; $i ++) { - $result += ord($binary[$i]) << (8*$i); + if ($size > 8) { + throw new \RuntimeException('Cannot read integers above 8 bytes'); } - return $result; + return unpack('P', str_pad($binary, 8, "\x00"))[1]; } } From f98d5de70fe331af9d3f83221383a69565206078 Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Sun, 2 Jun 2019 17:03:01 +0200 Subject: [PATCH 06/23] Refactor method names, add more coverage for BinaryIntegerReader --- phpunit.xml.dist | 22 ++++++++++++++++ src/BinaryIntegerReader.php | 42 +++++++++++++++++++++++-------- tests/BinaryIntegerReaderTest.php | 28 ++++++++++++++++++--- 3 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 phpunit.xml.dist diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..6faa706 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,22 @@ + + + + + + + tests/ + + + + + + src/ + + + + diff --git a/src/BinaryIntegerReader.php b/src/BinaryIntegerReader.php index 5795c4a..a0213f5 100644 --- a/src/BinaryIntegerReader.php +++ b/src/BinaryIntegerReader.php @@ -18,33 +18,55 @@ class BinaryIntegerReader public function readFixed(string $binary, int $size) { if ($size === 1) { - return unpack('C', $binary)[1]; + return $this->readUnsigned1ByteInteger($binary); } if ($size === 2) { - return unpack('v', $binary)[1]; + return $this->readUnsigned2ByteInteger($binary); } if ($size === 3) { - return unpack('V', $binary."\x00")[1]; + return $this->readUnsigned4ByteInteger($binary."\x00"); } if ($size === 4) { - return unpack('V', $binary)[1]; + return $this->readUnsigned4ByteInteger($binary); } if ($size === 8) { - if (strlen($binary) > $size) { - $binary = substr($binary, 0, $size); - } - - return \hexdec(\bin2hex(\strrev($binary))); + return $this->readUnsigned8ByteInteger($binary); } if ($size > 8) { - throw new \RuntimeException('Cannot read integers above 8 bytes'); + throw new \InvalidArgumentException('Cannot read integers above 8 bytes'); } return unpack('P', str_pad($binary, 8, "\x00"))[1]; } + + private function readUnsigned8ByteInteger(string $binary) + { + // Unpack does not support unsigned 64 bit integers, + // so we have to improvise here + if (strlen($binary) > 8) { + $binary = substr($binary, 0, 8); + } + + return \hexdec(\bin2hex(\strrev($binary))); + } + + private function readUnsigned4ByteInteger(string $binary): int + { + return unpack('V', $binary)[1]; + } + + private function readUnsigned2ByteInteger(string $binary) + { + return unpack('v', $binary)[1]; + } + + private function readUnsigned1ByteInteger(string $binary) + { + return unpack('C', $binary)[1]; + } } diff --git a/tests/BinaryIntegerReaderTest.php b/tests/BinaryIntegerReaderTest.php index 56cb83c..0ae406b 100644 --- a/tests/BinaryIntegerReaderTest.php +++ b/tests/BinaryIntegerReaderTest.php @@ -49,8 +49,17 @@ public function readsFixedThreeByteInteger(string $binary, int $expectedResult) /** * @test + * @dataProvider fourByteIntegers */ - public function readsSevenByteInteger() + public function readsFixedFourByteInteger(string $binary, int $expectedResult) + { + $this->assertEquals($expectedResult, $this->reader->readFixed($binary, 4)); + } + + /** + * @test + */ + public function readsFixedSevenByteInteger() { $this->assertEquals(72057594037927935, $this->reader->readFixed("\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 7)); } @@ -59,15 +68,15 @@ public function readsSevenByteInteger() * @test * @dataProvider eightByteIntegers */ - public function readsEightByteIntegers(string $binary, $expectedResult) + public function readsFixedEightByteIntegers(string $binary, $expectedResult) { $this->assertEquals($expectedResult, $this->reader->readFixed($binary, 8)); } /** @test */ - public function doesNotSupportValuesHigherThan8Bytes() + public function fixedReaderDoesNotSupportValuesHigherThan8Bytes() { - $this->expectException(\RuntimeException::class); + $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Cannot read integers above 8 bytes'); $this->reader->readFixed("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 9); @@ -106,6 +115,17 @@ public function threeByteIntegers() ]; } + public function fourByteIntegers() + { + return [ + 'zero' => ["\x00\x00\x00\x00", 0], + 'three_byte_max' => ["\xFF\xFF\xFF\x00", 16777215], + 'three_byte_max+1' => ["\x00\x00\x00\x01", 16777216], + 'random' => ["\xd1\x7c\xe4\x0e", 249855185], + 'max' => ["\xFF\xFF\xFF\xFF", 4294967295], + ]; + } + public function eightByteIntegers() { return [ From a3274164b66c48796f134b7ed7c1c966458e58e3 Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Tue, 4 Jun 2019 23:47:58 +0200 Subject: [PATCH 07/23] Implement check for simple use case of full packet check --- src/StringReadBuffer.php | 17 ++++++++++------- src/StringReadBufferFactory.php | 18 ++++++++++++++++++ tests/StringReadBufferTest.php | 2 +- 3 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 src/StringReadBufferFactory.php diff --git a/src/StringReadBuffer.php b/src/StringReadBuffer.php index 7b8b82e..6a7ab4d 100644 --- a/src/StringReadBuffer.php +++ b/src/StringReadBuffer.php @@ -28,6 +28,14 @@ class StringReadBuffer implements ReadBuffer */ private $buffer = ''; + /** @var BinaryIntegerReader */ + private $binaryIntegerReader; + + public function __construct(BinaryIntegerReader $binaryIntegerReader) + { + $this->binaryIntegerReader = $binaryIntegerReader; + } + /** * {@inheritDoc} */ @@ -54,7 +62,7 @@ public function readFragment(callable $reader): bool */ public function isFullPacket(): bool { - return false; + return strlen($this->buffer) > $this->currentPacket[0]; } /** @@ -67,15 +75,10 @@ public function nextPacket(): void private function initPacket(): void { - $totalLength = ord($this->buffer[0]) - + (ord($this->buffer[1]) << 8) - + (ord($this->buffer[1]) << 16) - ; $this->currentPacket = [ - + $this->binaryIntegerReader->readFixed($this->buffer, 3) ]; } - } diff --git a/src/StringReadBufferFactory.php b/src/StringReadBufferFactory.php new file mode 100644 index 0000000..60d201c --- /dev/null +++ b/src/StringReadBufferFactory.php @@ -0,0 +1,18 @@ +buffer = new StringReadBuffer(); + $this->buffer = (new StringReadBufferFactory())->createWithDefaultSettings(); } /** From 0bf777b85c906dafc0972058860aab1a879207ee Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Sat, 8 Jun 2019 10:07:26 +0200 Subject: [PATCH 08/23] Add one byte reader --- src/StringReadBuffer.php | 72 ++++++++++++++++++++++++++++++++-- tests/StringReadBufferTest.php | 13 ++++++ 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/src/StringReadBuffer.php b/src/StringReadBuffer.php index 6a7ab4d..da9fa0f 100644 --- a/src/StringReadBuffer.php +++ b/src/StringReadBuffer.php @@ -9,7 +9,7 @@ namespace EcomDev\MySQLBinaryProtocol; -class StringReadBuffer implements ReadBuffer +class StringReadBuffer implements ReadBuffer, ReadBufferFragment { /** @var int */ @@ -54,7 +54,9 @@ public function append(string $data): void */ public function readFragment(callable $reader): bool { - // TODO: Implement readFragment() method. + $reader($this); + + return false; } /** @@ -76,9 +78,73 @@ public function nextPacket(): void private function initPacket(): void { $this->currentPacket = [ - $this->binaryIntegerReader->readFixed($this->buffer, 3) + $this->binaryIntegerReader->readFixed($this->read(3), 3), + $this->binaryIntegerReader->readFixed($this->read(1), 1) ]; } + private function read(int $length): string + { + $value = substr($this->buffer, $this->currentPosition, $length); + $this->currentPosition += $length; + return $value; + } + + /** + * {@inheritDoc} + */ + public function readFixedInteger(int $bytes): int + { + return $this->binaryIntegerReader->readFixed($this->read($bytes), $bytes); + } + /** + * Reads length encoded integer + * + * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_integers.html + */ + public function readLengthEncodedInteger(): int + { + // TODO: Implement readLengthEncodedInteger() method. + } + + /** + * Reads string of specified length from buffer + * + * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html + */ + public function readFixedString(int $length): string + { + // TODO: Implement readFixedString() method. + } + + /** + * Reads string that is has length as the first part of the fragment + * + * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html + */ + public function readLengthEncodedStringOrNull(): ?string + { + // TODO: Implement readLengthEncodedStringOrNull() method. + } + + /** + * Reads string till x00 character + * + * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html + */ + public function readNullTerminatedString(): string + { + // TODO: Implement readNullTerminatedString() method. + } + + /** + * Reads string that is rest of payload + * + * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html + */ + public function readRestOfPacketString(): string + { + // TODO: Implement readRestOfPacketString() method. + } } diff --git a/tests/StringReadBufferTest.php b/tests/StringReadBufferTest.php index 19a4817..38c862b 100644 --- a/tests/StringReadBufferTest.php +++ b/tests/StringReadBufferTest.php @@ -40,4 +40,17 @@ public function reportsCompleteBufferWhenAllDataIsProvidedAtOnce() $this->assertTrue($this->buffer->isFullPacket()); } + + /** @test */ + public function allowsToReadSingleByteIntegerFromPayload() + { + $this->buffer->append("\x01\x00\x00\x00\xF1"); + + $data = []; + $this->buffer->readFragment(function (ReadBufferFragment $fragment) use (&$data) { + $data[] = $fragment->readFixedInteger(1); + }); + + $this->assertEquals([241], $data); + } } From 553fa42016a8bacc471cd459388d4bfa4a274f2f Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Mon, 10 Jun 2019 20:41:50 +0200 Subject: [PATCH 09/23] Fix composer.json - remove bcmath, as not needed anymore. Lock in 7.1 as minimum php version requirement. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9b31b9f..0de5961 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ ], "minimum-stability": "stable", "require": { - "ext-bcmath": "" + "php": "~7.1" }, "autoload": { "psr-4": { From f7eb7338934cb87fa07e0b2a57d7b29e2eab427d Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Tue, 11 Jun 2019 20:24:38 +0200 Subject: [PATCH 10/23] Rename read buffer into packet reader Create pure buffer implementation --- src/DefaultPacketReaderFactory.php | 18 ++ ...rFragment.php => PacketFragmentReader.php} | 2 +- src/PacketReader.php | 39 ++++ src/ReadBuffer.php | 76 +++++-- ...gReadBuffer.php => StringPacketReader.php} | 66 +++--- src/StringReadBufferFactory.php | 18 -- tests/PacketReaderTest.php | 115 +++++++++++ tests/ReadBufferTest.php | 194 ++++++++++++++++++ tests/StringReadBufferTest.php | 56 ----- 9 files changed, 458 insertions(+), 126 deletions(-) create mode 100644 src/DefaultPacketReaderFactory.php rename src/{ReadBufferFragment.php => PacketFragmentReader.php} (98%) create mode 100644 src/PacketReader.php rename src/{StringReadBuffer.php => StringPacketReader.php} (68%) delete mode 100644 src/StringReadBufferFactory.php create mode 100644 tests/PacketReaderTest.php create mode 100644 tests/ReadBufferTest.php delete mode 100644 tests/StringReadBufferTest.php diff --git a/src/DefaultPacketReaderFactory.php b/src/DefaultPacketReaderFactory.php new file mode 100644 index 0000000..26aaa57 --- /dev/null +++ b/src/DefaultPacketReaderFactory.php @@ -0,0 +1,18 @@ +bufferSize = $bufferSize; + } + + public function append(string $data): void + { + $this->buffer .= $data; + } + + public function read(int $length): string + { + if (!$this->isReadable($length)) { + $this->currentPosition = $this->readPosition; + throw new IncompleteBufferException(); + } + + $data = substr($this->buffer, $this->currentPosition, $length); + + $this->currentPosition += $length; + return $data; + } + + public function isReadable(int $length): bool + { + return strlen($this->buffer) - $this->currentPosition >= $length; + } + + public function flush(): int + { + $bytesRead = $this->currentPosition - $this->readPosition; + $this->readPosition = $this->currentPosition; + + if ($this->readPosition >= $this->bufferSize) { + $this->buffer = substr($this->buffer, $this->readPosition); + $this->readPosition = 0; + $this->currentPosition = 0; + } + + return $bytesRead; + } } diff --git a/src/StringReadBuffer.php b/src/StringPacketReader.php similarity index 68% rename from src/StringReadBuffer.php rename to src/StringPacketReader.php index da9fa0f..ffb0462 100644 --- a/src/StringReadBuffer.php +++ b/src/StringPacketReader.php @@ -9,31 +9,31 @@ namespace EcomDev\MySQLBinaryProtocol; -class StringReadBuffer implements ReadBuffer, ReadBufferFragment +class StringPacketReader implements PacketReader, PacketFragmentReader { - /** @var int */ - private $bufferSize = 0; - - /** @var int */ - private $currentPosition = 0; - /** - * @var int[] + * @var int */ - private $currentPacket = []; + private $currentPacketLength; /** - * @var string + * @var int */ - private $buffer = ''; + private $currentPacketSequence; /** @var BinaryIntegerReader */ private $binaryIntegerReader; - public function __construct(BinaryIntegerReader $binaryIntegerReader) + /** + * @var ReadBuffer + */ + private $readBuffer; + + public function __construct(BinaryIntegerReader $binaryIntegerReader, ReadBuffer $readBuffer) { $this->binaryIntegerReader = $binaryIntegerReader; + $this->readBuffer = $readBuffer; } /** @@ -41,10 +41,9 @@ public function __construct(BinaryIntegerReader $binaryIntegerReader) */ public function append(string $data): void { - $this->buffer .= $data; - $this->bufferSize += strlen($data); + $this->readBuffer->append($data); - if (!$this->currentPacket) { + if ($this->currentPacketLength === null) { $this->initPacket(); } } @@ -54,9 +53,14 @@ public function append(string $data): void */ public function readFragment(callable $reader): bool { - $reader($this); + try { + $reader($this); + $this->currentPacketLength -= $this->readBuffer->flush(); + } catch (IncompleteBufferException $exception) { + return false; + } - return false; + return true; } /** @@ -64,7 +68,7 @@ public function readFragment(callable $reader): bool */ public function isFullPacket(): bool { - return strlen($this->buffer) > $this->currentPacket[0]; + return $this->readBuffer->isReadable($this->currentPacketLength); } /** @@ -72,22 +76,19 @@ public function isFullPacket(): bool */ public function nextPacket(): void { - // TODO: Implement nextPacket() method. + $this->initPacket(); } private function initPacket(): void { - $this->currentPacket = [ - $this->binaryIntegerReader->readFixed($this->read(3), 3), - $this->binaryIntegerReader->readFixed($this->read(1), 1) - ]; - } - - private function read(int $length): string - { - $value = substr($this->buffer, $this->currentPosition, $length); - $this->currentPosition += $length; - return $value; + $this->currentPacketLength = $this->binaryIntegerReader->readFixed( + $this->readBuffer->read(3), + 3 + ); + $this->currentPacketSequence = $this->binaryIntegerReader->readFixed( + $this->readBuffer->read(1), + 1 + ); } /** @@ -95,7 +96,10 @@ private function read(int $length): string */ public function readFixedInteger(int $bytes): int { - return $this->binaryIntegerReader->readFixed($this->read($bytes), $bytes); + return $this->binaryIntegerReader->readFixed( + $this->readBuffer->read($bytes), + $bytes + ); } /** diff --git a/src/StringReadBufferFactory.php b/src/StringReadBufferFactory.php deleted file mode 100644 index 60d201c..0000000 --- a/src/StringReadBufferFactory.php +++ /dev/null @@ -1,18 +0,0 @@ -reader = (new DefaultPacketReaderFactory())->createWithDefaultSettings(); + } + + /** + * @test + * + */ + public function reportsIncompleteBufferWhenNotFullLengthProvided() + { + $this->reader->append("\xFF\xFF\xFF\x00Some Payload"); + + $this->assertFalse($this->reader->isFullPacket()); + } + + /** @test */ + public function reportsCompleteBufferWhenAllDataIsProvidedAtOnce() + { + $this->reader->append("\x01\x00\x00\x00\x01"); + + $this->assertTrue($this->reader->isFullPacket()); + } + + /** @test */ + public function allowsToReadSingleByteIntegerFromPayload() + { + $this->reader->append("\x01\x00\x00\x00\xF1"); + + $data = []; + $this->reader->readFragment(function (PacketFragmentReader $fragment) use (&$data) { + $data[] = $fragment->readFixedInteger(1); + }); + + $this->assertEquals([241], $data); + } + + /** @test */ + public function allowsToReadMultiplePackets() + { + $this->reader->append("\x01\x00\x00\x00\xF1"); + $this->reader->append("\x01\x00\x00\x00\xF2"); + + $data = []; + $readOneByte = function (PacketFragmentReader $fragment) use (&$data) { + $data[] = $fragment->readFixedInteger(1); + }; + + $this->reader->readFragment($readOneByte); + $this->reader->nextPacket(); + $this->reader->readFragment($readOneByte); + + $this->assertEquals([241, 242], $data); + } + + /** @test */ + public function reportsCompletePacketAvailableEvenAfterFragmentIsRead() + { + $this->reader->append("\x02\x00\x00\x00\xF1\xF2"); + + $this->reader->readFragment( + function (PacketFragmentReader $fragment) { + $fragment->readFixedInteger(1); + } + ); + + $this->assertEquals(true, $this->reader->isFullPacket()); + } + + /** @test */ + public function reportsFragmentIsRead() + { + $this->reader->append("\x01\x00\x00\x00\x01"); + + $this->assertEquals( + true, + $this->reader->readFragment(function (PacketFragmentReader $reader) { + $reader->readFixedInteger(1); + }) + ); + } + + /** @test */ + public function reportsFragmentIsNotRead() + { + $this->reader->append("\x02\x00\x00\x00\x01"); + + $this->assertEquals( + false, + $this->reader->readFragment(function (PacketFragmentReader $reader) { + $reader->readFixedInteger(1); + $reader->readFixedInteger(1); + }) + ); + } +} diff --git a/tests/ReadBufferTest.php b/tests/ReadBufferTest.php new file mode 100644 index 0000000..cc00dbf --- /dev/null +++ b/tests/ReadBufferTest.php @@ -0,0 +1,194 @@ +readBuffer = new ReadBuffer(); + } + + + /** @test */ + public function readsBufferByLength() + { + $this->readBuffer->append('Some string'); + + $this->assertEquals('Some', $this->readBuffer->read(4)); + } + + /** @test */ + public function readsBufferByMovingPositionForward() + { + $this->readBuffer->append('TDD is awesome'); + + $this->readBuffer->read(3); + + $this->assertEquals(' is awesome', $this->readBuffer->read(11)); + } + + /** @test */ + public function throwsIncompleteBufferExceptionWhenNotBufferIsSmallerThenReadSize() + { + $this->readBuffer->append('TDD is'); + + $this->expectException(IncompleteBufferException::class); + + $this->readBuffer->read(11); + } + + /** @test */ + public function throwIncompleteBufferExceptionWhenNotEnoughDataIsLeftToRead() + { + $this->readBuffer->append('TDD is great'); + + $this->readBuffer->read(7); + + $this->expectException(IncompleteBufferException::class); + + $this->readBuffer->read(7); + } + + /** @test */ + public function allowsToReadAllAddedPiecesToBuffer() + { + $this->readBuffer->append('TDD is'); + + $this->readBuffer->read(4); + + $this->readBuffer->append(' great'); + + $this->assertEquals('is great', $this->readBuffer->read(8)); + } + + /** @test */ + public function isReadableWhenAskedBytesAreBelowBufferLength() + { + $this->readBuffer->append('Some data'); + + $this->assertEquals(true, $this->readBuffer->isReadable(4)); + } + + /** @test */ + public function isNotReadableWhenBytesAreLongerThenBufferLength() + { + $this->readBuffer->append('Some'); + + $this->assertEquals(false, $this->readBuffer->isReadable(5)); + } + + /** @test */ + public function isNotReadableWhenAskedLengthIsLowerThenRemainingBytesToRead() + { + $this->readBuffer->append('Some data'); + $this->readBuffer->read(5); + + $this->assertEquals(false, $this->readBuffer->isReadable(5)); + } + + /** @test */ + public function isReadableWhenExactAmountOfBytesAvailableToRead() + { + $this->readBuffer->append('Data in buffer'); + + $this->readBuffer->read(7); + + $this->assertEquals(true, $this->readBuffer->isReadable(7)); + } + + /** @test */ + public function allowsToReadDataAgainIfPreviousSessionWasNotReadCompletely() + { + $this->readBuffer->append('Data in buffer'); + $this->readBuffer->read(4); + $this->readBuffer->read(4); + + try { + $this->readBuffer->read(7); + } catch (IncompleteBufferException $exception) { } + + $this->assertEquals('Data in ', $this->readBuffer->read(8)); + } + + /** @test */ + public function allowsToMoveReadBufferPointerAfterRead() + { + $this->readBuffer->append('Data in buffer'); + + $this->readBuffer->read(5); + $this->readBuffer->flush(); + + try { + $this->readBuffer->read(10); + } catch (IncompleteBufferException $e) { + + } + + $this->assertEquals('in buffer', $this->readBuffer->read(9)); + } + + /** @test */ + public function clearsBufferWhenReadLimitIsReached() + { + $limitedReadBuffer = new ReadBuffer(20); + + $limitedReadBuffer->append('Some data to read 2 remainder of buffer'); + $limitedReadBuffer->read(10); + $limitedReadBuffer->read(10); + $limitedReadBuffer->flush(); + + $expectedReadBuffer = new ReadBuffer(20); + $expectedReadBuffer->append('remainder of buffer'); + + $this->assertEquals($expectedReadBuffer, $limitedReadBuffer); + } + + /** @test */ + public function clearsBufferLimitIsReachedALongTimeAgo() + { + $limitedReadBuffer = new ReadBuffer(20); + + $limitedReadBuffer->append('Some data to read 2 very long string to read remainder of buffer'); + $limitedReadBuffer->read(10); + $limitedReadBuffer->flush(); + $limitedReadBuffer->read(20); + $limitedReadBuffer->read(15); + $limitedReadBuffer->flush(); + + $expectedReadBuffer = new ReadBuffer(20); + $expectedReadBuffer->append('remainder of buffer'); + + $this->assertEquals($expectedReadBuffer, $limitedReadBuffer); + } + + /** @test */ + public function flushReturnsNumberOfReadBytes() + { + $this->readBuffer->append('Some data'); + $this->readBuffer->read(4); + $this->readBuffer->read(2); + $this->assertEquals(6, $this->readBuffer->flush()); + } + + /** @test */ + public function flushReturnsZeroWhenNoBytesRead() + { + $this->readBuffer->append('Some data'); + + $this->assertEquals(0, $this->readBuffer->flush()); + } + +} diff --git a/tests/StringReadBufferTest.php b/tests/StringReadBufferTest.php deleted file mode 100644 index 38c862b..0000000 --- a/tests/StringReadBufferTest.php +++ /dev/null @@ -1,56 +0,0 @@ -buffer = (new StringReadBufferFactory())->createWithDefaultSettings(); - } - - /** - * @test - * - */ - public function reportsIncompleteBufferWhenNotFullLengthProvided() - { - $this->buffer->append("\xFF\xFF\xFF\x00Some Payload"); - - $this->assertFalse($this->buffer->isFullPacket()); - } - - /** @test */ - public function reportsCompleteBufferWhenAllDataIsProvidedAtOnce() - { - $this->buffer->append("\x01\x00\x00\x00\x01"); - - $this->assertTrue($this->buffer->isFullPacket()); - } - - /** @test */ - public function allowsToReadSingleByteIntegerFromPayload() - { - $this->buffer->append("\x01\x00\x00\x00\xF1"); - - $data = []; - $this->buffer->readFragment(function (ReadBufferFragment $fragment) use (&$data) { - $data[] = $fragment->readFixedInteger(1); - }); - - $this->assertEquals([241], $data); - } -} From 3803e3d46180c9db76396b784acc0c8be02cfa68 Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Thu, 13 Jun 2019 21:19:39 +0200 Subject: [PATCH 11/23] Add test for reading various binary fixed integer sizes --- tests/PacketReaderTest.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/PacketReaderTest.php b/tests/PacketReaderTest.php index 87de1d4..30096e0 100644 --- a/tests/PacketReaderTest.php +++ b/tests/PacketReaderTest.php @@ -112,4 +112,28 @@ public function reportsFragmentIsNotRead() }) ); } + + /** @test */ + public function allowsReadingVariousFixedIntegers() + { + $this->reader->append("\x0D\x00\x00\x00\x00\x02\x02\x00\x00\x00\x00\x00\x00\x00\x00\xF0\x00"); + + $data = []; + $this->reader->readFragment(function (PacketFragmentReader $fragment) use (&$data) { + $data[] = $fragment->readFixedInteger(2); // 512 + $data[] = $fragment->readFixedInteger(3); // 2 + $data[] = $fragment->readFixedInteger(8); // 67553994410557440 + }); + + $this->assertEquals( + [ + 512, + 2, + 67553994410557440 + ], + $data + ); + } + + } From 14305e41ccc88eea8db70e3e7aa72cf5667058b1 Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Fri, 14 Jun 2019 14:34:04 +0200 Subject: [PATCH 12/23] Add unit test cache into ignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c361ba7..02a92bd 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ !/.idea/*.iml !/.idea/modules.xml +# PHPUnit +/.phpunit.result.cache From 7c98c25907e0472c751dccc91e83bafe73a1d841 Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Fri, 14 Jun 2019 18:41:42 +0200 Subject: [PATCH 13/23] Work in progress tests --- composer.lock | 188 +++++++++++++++++++++++-------------- tests/PacketReaderTest.php | 20 ++++ 2 files changed, 138 insertions(+), 70 deletions(-) diff --git a/composer.lock b/composer.lock index 3ff1432..056d938 100644 --- a/composer.lock +++ b/composer.lock @@ -1,11 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "hash": "9c7a720e2cbf882dc19d228afcddcfcd", - "content-hash": "c49bf819e0695a2361ebfe74ca8378ee", + "content-hash": "aff8eb49ceefb3611292997c36e8967e", "packages": [], "packages-dev": [ { @@ -62,7 +61,7 @@ "constructor", "instantiate" ], - "time": "2019-03-17 17:37:11" + "time": "2019-03-17T17:37:11+00:00" }, { "name": "myclabs/deep-copy", @@ -110,7 +109,7 @@ "object", "object graph" ], - "time": "2019-04-07 13:18:21" + "time": "2019-04-07T13:18:21+00:00" }, { "name": "phar-io/manifest", @@ -165,7 +164,7 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2018-07-08 19:23:20" + "time": "2018-07-08T19:23:20+00:00" }, { "name": "phar-io/version", @@ -212,7 +211,7 @@ } ], "description": "Library for handling version information and constraints", - "time": "2018-07-08 19:19:57" + "time": "2018-07-08T19:19:57+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -266,7 +265,7 @@ "reflection", "static analysis" ], - "time": "2017-09-11 18:02:19" + "time": "2017-09-11T18:02:19+00:00" }, { "name": "phpdocumentor/reflection-docblock", @@ -317,7 +316,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2019-04-30 17:48:53" + "time": "2019-04-30T17:48:53+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -364,20 +363,20 @@ "email": "me@mikevanriel.com" } ], - "time": "2017-07-14 14:27:02" + "time": "2017-07-14T14:27:02+00:00" }, { "name": "phpspec/prophecy", - "version": "1.8.0", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76", "shasum": "" }, "require": { @@ -398,8 +397,8 @@ } }, "autoload": { - "psr-0": { - "Prophecy\\": "src/" + "psr-4": { + "Prophecy\\": "src/Prophecy" } }, "notification-url": "https://packagist.org/downloads/", @@ -427,20 +426,20 @@ "spy", "stub" ], - "time": "2018-08-05 17:53:17" + "time": "2019-06-13T12:50:23+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "7.0.4", + "version": "7.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "6024c8a6cb962d496b7bd049ed8f48473824176d" + "reference": "aed67b57d459dcab93e84a5c9703d3deb5025dff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6024c8a6cb962d496b7bd049ed8f48473824176d", - "reference": "6024c8a6cb962d496b7bd049ed8f48473824176d", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aed67b57d459dcab93e84a5c9703d3deb5025dff", + "reference": "aed67b57d459dcab93e84a5c9703d3deb5025dff", "shasum": "" }, "require": { @@ -490,7 +489,7 @@ "testing", "xunit" ], - "time": "2019-05-29 09:59:31" + "time": "2019-06-06T12:28:18+00:00" }, { "name": "phpunit/php-file-iterator", @@ -540,7 +539,7 @@ "filesystem", "iterator" ], - "time": "2018-09-13 20:33:42" + "time": "2018-09-13T20:33:42+00:00" }, { "name": "phpunit/php-text-template", @@ -581,20 +580,20 @@ "keywords": [ "template" ], - "time": "2015-06-21 13:50:34" + "time": "2015-06-21T13:50:34+00:00" }, { "name": "phpunit/php-timer", - "version": "2.1.1", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059" + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b389aebe1b8b0578430bda0c7c95a829608e059", - "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", "shasum": "" }, "require": { @@ -630,7 +629,7 @@ "keywords": [ "timer" ], - "time": "2019-02-20 10:12:59" + "time": "2019-06-07T04:22:29+00:00" }, { "name": "phpunit/php-token-stream", @@ -679,46 +678,47 @@ "keywords": [ "tokenizer" ], - "time": "2018-10-30 05:52:18" + "time": "2018-10-30T05:52:18+00:00" }, { "name": "phpunit/phpunit", - "version": "8.1.6", + "version": "8.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "e3c9da6e645492c461e0a11eca117f83f4f4c840" + "reference": "047f771e34dccacb6c432a1a70e9980e087eac92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e3c9da6e645492c461e0a11eca117f83f4f4c840", - "reference": "e3c9da6e645492c461e0a11eca117f83f4f4c840", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/047f771e34dccacb6c432a1a70e9980e087eac92", + "reference": "047f771e34dccacb6c432a1a70e9980e087eac92", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.1", + "doctrine/instantiator": "^1.2.0", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.7", - "phar-io/manifest": "^1.0.2", - "phar-io/version": "^2.0", + "myclabs/deep-copy": "^1.9.1", + "phar-io/manifest": "^1.0.3", + "phar-io/version": "^2.0.1", "php": "^7.2", - "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^7.0", - "phpunit/php-file-iterator": "^2.0.1", + "phpspec/prophecy": "^1.8.0", + "phpunit/php-code-coverage": "^7.0.5", + "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.1", - "sebastian/comparator": "^3.0", - "sebastian/diff": "^3.0", - "sebastian/environment": "^4.1", - "sebastian/exporter": "^3.1", - "sebastian/global-state": "^3.0", + "phpunit/php-timer": "^2.1.2", + "sebastian/comparator": "^3.0.2", + "sebastian/diff": "^3.0.2", + "sebastian/environment": "^4.2.2", + "sebastian/exporter": "^3.1.0", + "sebastian/global-state": "^3.0.0", "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^2.0", + "sebastian/resource-operations": "^2.0.1", + "sebastian/type": "^1.1.0", "sebastian/version": "^2.0.1" }, "require-dev": { @@ -727,7 +727,7 @@ "suggest": { "ext-soap": "*", "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0" + "phpunit/php-invoker": "^2.0.0" }, "bin": [ "phpunit" @@ -735,7 +735,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "8.1-dev" + "dev-master": "8.2-dev" } }, "autoload": { @@ -761,7 +761,7 @@ "testing", "xunit" ], - "time": "2019-05-28 11:53:42" + "time": "2019-06-07T14:04:13+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -806,7 +806,7 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2017-03-04 06:30:41" + "time": "2017-03-04T06:30:41+00:00" }, { "name": "sebastian/comparator", @@ -870,7 +870,7 @@ "compare", "equality" ], - "time": "2018-07-12 15:12:46" + "time": "2018-07-12T15:12:46+00:00" }, { "name": "sebastian/diff", @@ -926,7 +926,7 @@ "unidiff", "unified diff" ], - "time": "2019-02-04 06:01:07" + "time": "2019-02-04T06:01:07+00:00" }, { "name": "sebastian/environment", @@ -979,7 +979,7 @@ "environment", "hhvm" ], - "time": "2019-05-05 09:05:15" + "time": "2019-05-05T09:05:15+00:00" }, { "name": "sebastian/exporter", @@ -1046,7 +1046,7 @@ "export", "exporter" ], - "time": "2017-04-03 13:19:02" + "time": "2017-04-03T13:19:02+00:00" }, { "name": "sebastian/global-state", @@ -1100,7 +1100,7 @@ "keywords": [ "global state" ], - "time": "2019-02-01 05:30:01" + "time": "2019-02-01T05:30:01+00:00" }, { "name": "sebastian/object-enumerator", @@ -1147,7 +1147,7 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-08-03 12:35:26" + "time": "2017-08-03T12:35:26+00:00" }, { "name": "sebastian/object-reflector", @@ -1192,7 +1192,7 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2017-03-29 09:07:27" + "time": "2017-03-29T09:07:27+00:00" }, { "name": "sebastian/recursion-context", @@ -1245,7 +1245,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2017-03-03 06:23:57" + "time": "2017-03-03T06:23:57+00:00" }, { "name": "sebastian/resource-operations", @@ -1287,7 +1287,53 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2018-10-04 04:07:39" + "time": "2018-10-04T04:07:39+00:00" + }, + { + "name": "sebastian/type", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "b2a7f9aac51ce18cd7ac8b31e37c8ce5646fc741" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b2a7f9aac51ce18cd7ac8b31e37c8ce5646fc741", + "reference": "b2a7f9aac51ce18cd7ac8b31e37c8ce5646fc741", + "shasum": "" + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "time": "2019-06-08T04:53:27+00:00" }, { "name": "sebastian/version", @@ -1330,7 +1376,7 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-10-03 07:35:21" + "time": "2016-10-03T07:35:21+00:00" }, { "name": "symfony/polyfill-ctype", @@ -1388,20 +1434,20 @@ "polyfill", "portable" ], - "time": "2019-02-06 07:57:58" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "theseer/tokenizer", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "1c42705be2b6c1de5904f8afacef5895cab44bf8" + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/1c42705be2b6c1de5904f8afacef5895cab44bf8", - "reference": "1c42705be2b6c1de5904f8afacef5895cab44bf8", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", "shasum": "" }, "require": { @@ -1428,7 +1474,7 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2019-04-04 09:56:43" + "time": "2019-06-13T22:48:21+00:00" }, { "name": "webmozart/assert", @@ -1479,7 +1525,7 @@ "check", "validate" ], - "time": "2018-12-25 11:19:39" + "time": "2018-12-25T11:19:39+00:00" } ], "aliases": [], @@ -1487,6 +1533,8 @@ "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, - "platform": [], + "platform": { + "php": "~7.1" + }, "platform-dev": [] } diff --git a/tests/PacketReaderTest.php b/tests/PacketReaderTest.php index 30096e0..40dbbfa 100644 --- a/tests/PacketReaderTest.php +++ b/tests/PacketReaderTest.php @@ -134,6 +134,26 @@ public function allowsReadingVariousFixedIntegers() $data ); } + + /** @test */ + public function allowsReadingLengthDifferentLengthEncodedInteger() + { + $this->reader->append("\x01\x00\x00\x00\xfa\xfc\xf1\00"); + $data = []; + $this->reader->readFragment(function (PacketFragmentReader $fragment) use (&$data) { + $data[] = $fragment->readFixedInteger(2); // 512 + $data[] = $fragment->readFixedInteger(3); // 2 + $data[] = $fragment->readFixedInteger(8); // 67553994410557440 + }); + $this->assertEquals( + [ + 512, + 2, + 67553994410557440 + ], + $data + ); + } } From 59915d736afa44398161056a997a6203c9adbcbf Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Wed, 19 Jun 2019 19:30:47 +0200 Subject: [PATCH 14/23] Add string and length encoded integer reading --- src/InvalidBinaryDataException.php | 14 ++++ src/PacketFragmentReader.php | 6 +- src/StringPacketReader.php | 35 ++++++++-- tests/PacketReaderTest.php | 102 ++++++++++++++++++++++++++--- 4 files changed, 142 insertions(+), 15 deletions(-) create mode 100644 src/InvalidBinaryDataException.php diff --git a/src/InvalidBinaryDataException.php b/src/InvalidBinaryDataException.php new file mode 100644 index 0000000..d971273 --- /dev/null +++ b/src/InvalidBinaryDataException.php @@ -0,0 +1,14 @@ + 2, + 0xfd => 3, + 0xfe => 8 + ]; /** * @var int @@ -94,7 +99,7 @@ private function initPacket(): void /** * {@inheritDoc} */ - public function readFixedInteger(int $bytes): int + public function readFixedInteger(int $bytes) { return $this->binaryIntegerReader->readFixed( $this->readBuffer->read($bytes), @@ -107,9 +112,23 @@ public function readFixedInteger(int $bytes): int * * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_integers.html */ - public function readLengthEncodedInteger(): int + public function readLengthEncodedIntegerOrNull() { - // TODO: Implement readLengthEncodedInteger() method. + $value = $this->readFixedInteger(1); + + if ($value === 0xfb) { + return null; + } + + if ($value < 251) { + return $value; + } + + if (!isset(self::INTEGER_LENGTH_MARKER[$value])) { + throw new InvalidBinaryDataException(); + } + + return $this->readFixedInteger(self::INTEGER_LENGTH_MARKER[$value]); } /** @@ -119,7 +138,7 @@ public function readLengthEncodedInteger(): int */ public function readFixedString(int $length): string { - // TODO: Implement readFixedString() method. + return $this->readBuffer->read($length); } /** @@ -129,7 +148,13 @@ public function readFixedString(int $length): string */ public function readLengthEncodedStringOrNull(): ?string { - // TODO: Implement readLengthEncodedStringOrNull() method. + $stringLength = $this->readLengthEncodedIntegerOrNull(); + + if ($stringLength === null) { + return null; + } + + return $this->readFixedString($stringLength); } /** diff --git a/tests/PacketReaderTest.php b/tests/PacketReaderTest.php index 40dbbfa..98f336c 100644 --- a/tests/PacketReaderTest.php +++ b/tests/PacketReaderTest.php @@ -136,22 +136,108 @@ public function allowsReadingVariousFixedIntegers() } /** @test */ - public function allowsReadingLengthDifferentLengthEncodedInteger() + public function allowsReadingDifferentLengthEncodedIntegers() { - $this->reader->append("\x01\x00\x00\x00\xfa\xfc\xf1\00"); + $this->reader->append( + "\x12\x00\x00\x00\xf9\xfa\xfc\xfb\00\xfd\xff\xff\xf0\xfe\xff\xff\xff\xff\xff\xff\xff\xf0" + ); $data = []; $this->reader->readFragment(function (PacketFragmentReader $fragment) use (&$data) { - $data[] = $fragment->readFixedInteger(2); // 512 - $data[] = $fragment->readFixedInteger(3); // 2 - $data[] = $fragment->readFixedInteger(8); // 67553994410557440 + $data[] = $fragment->readLengthEncodedIntegerOrNull(); // 249 + $data[] = $fragment->readLengthEncodedIntegerOrNull(); // 250 + $data[] = $fragment->readLengthEncodedIntegerOrNull(); // 251 + $data[] = $fragment->readLengthEncodedIntegerOrNull(); // 15794175 + $data[] = $fragment->readLengthEncodedIntegerOrNull(); // 17365880163140632575 }); $this->assertEquals( [ - 512, - 2, - 67553994410557440 + 249, + 250, + 251, + 15794175, + 17365880163140632575 + ], + $data + ); + } + + + /** + * @test + */ + public function throwsInvalidBinaryDataExceptionWhenLengthEncodedIntegerDoesNotMatchExpectedFormat() + { + + $this->reader->append( + "\x09\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xf0" + ); + + $this->expectException(InvalidBinaryDataException::class); + + $this->reader->readFragment(function (PacketFragmentReader $fragment) { + $fragment->readLengthEncodedIntegerOrNull(); + }); + } + + /** @test */ + public function readsNullForLengthEncodedInteger() + { + $this->reader->append( + "\x01\x00\x00\x00\xfb" + ); + + $this->reader->readFragment(function (PacketFragmentReader $fragment) { + $this->assertEquals(null, $fragment->readLengthEncodedIntegerOrNull()); + }); + } + + /** @test */ + public function readsFixedLengthString() + { + $this->reader->append("\x18\x00\x00\x00helloworld!awesomestring"); + $data = []; + + $this->reader->readFragment(function (PacketFragmentReader $fragment) use (&$data) { + $data[] = $fragment->readFixedString(5); + $data[] = $fragment->readFixedString(6); + $data[] = $fragment->readFixedString(13); + }); + + $this->assertEquals( + [ + 'hello', + 'world!', + 'awesomestring' + ], + $data + ); + } + + /** @test */ + public function readsLengthEncodedString() + { + $veryLongString = str_repeat('a', 0xff); + + + $this->reader->append("\x0c\x01\x00\x00\xfc\xff\x00$veryLongString\x05hello\xfb\x0202"); + + $data = []; + + $this->reader->readFragment(function (PacketFragmentReader $fragment) use (&$data) { + $data[] = $fragment->readLengthEncodedStringOrNull(); + $data[] = $fragment->readLengthEncodedStringOrNull(); + $data[] = $fragment->readLengthEncodedStringOrNull(); + $data[] = $fragment->readLengthEncodedStringOrNull(); + }); + + $this->assertEquals( + [ + $veryLongString, + 'hello', + null, + '02' ], $data ); From 2a4cb89025fb5b60f6a1e5572501665d43a1d0ae Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Thu, 20 Jun 2019 21:47:11 +0200 Subject: [PATCH 15/23] Added - Reading of null terminated strings - Reading of EOF strings - Multi-packet reading of payload Improved - Naming of the classes to reflect better their purpose Removed - NextPacket and isFullPacket methods as readPayload() already covers those usecases --- composer.json | 3 + src/DefaultPacketReaderFactory.php | 4 +- ...mentReader.php => PacketPayloadReader.php} | 2 +- src/PacketReader.php | 16 +- src/ReadBuffer.php | 47 ++- ...eader.php => UncompressedPacketReader.php} | 130 +++++--- tests/PacketReaderTest.php | 277 +++++++++++++----- tests/ReadBufferTest.php | 97 ++++++ 8 files changed, 429 insertions(+), 147 deletions(-) rename src/{PacketFragmentReader.php => PacketPayloadReader.php} (98%) rename src/{StringPacketReader.php => UncompressedPacketReader.php} (51%) diff --git a/composer.json b/composer.json index 0de5961..eb756cd 100644 --- a/composer.json +++ b/composer.json @@ -25,5 +25,8 @@ }, "require-dev": { "phpunit/phpunit": "^8.1" + }, + "scripts": { + "test": "phpunit --colors --coverage-text" } } diff --git a/src/DefaultPacketReaderFactory.php b/src/DefaultPacketReaderFactory.php index 26aaa57..67409c0 100644 --- a/src/DefaultPacketReaderFactory.php +++ b/src/DefaultPacketReaderFactory.php @@ -11,8 +11,8 @@ class DefaultPacketReaderFactory { - public function createWithDefaultSettings(): StringPacketReader + public function createWithDefaultSettings(): UncompressedPacketReader { - return new StringPacketReader(new BinaryIntegerReader(), new ReadBuffer()); + return new UncompressedPacketReader(new BinaryIntegerReader(), new ReadBuffer()); } } diff --git a/src/PacketFragmentReader.php b/src/PacketPayloadReader.php similarity index 98% rename from src/PacketFragmentReader.php rename to src/PacketPayloadReader.php index 7ba7a8a..0cc58f4 100644 --- a/src/PacketFragmentReader.php +++ b/src/PacketPayloadReader.php @@ -12,7 +12,7 @@ * Reader for buffer fragments * */ -interface PacketFragmentReader +interface PacketPayloadReader { /** * Reads fixed value integer diff --git a/src/PacketReader.php b/src/PacketReader.php index 2f6d097..c55d285 100644 --- a/src/PacketReader.php +++ b/src/PacketReader.php @@ -16,24 +16,12 @@ interface PacketReader public function append(string $data): void; /** - * Executes callable to read a fragment of the buffer by using ReadBufferFragment + * Executes callable to read a packed payload by using payload reader * * In case of IncompleteBufferException is thrown during reading, * method returns false and same data will be returned on the next read. * * The code of $reader MUST NOT catch this exception */ - public function readFragment(callable $reader): bool; - - /** - * Checks if current packet readable completely to the end - */ - public function isFullPacket(): bool; - - /** - * Moves internal pointer to start reading next packet - * - * Any data that is left unread in the packet will be discarded - */ - public function nextPacket(): void; + public function readPayload(callable $reader): bool; } diff --git a/src/ReadBuffer.php b/src/ReadBuffer.php index 72b2188..ce843d3 100644 --- a/src/ReadBuffer.php +++ b/src/ReadBuffer.php @@ -10,6 +10,7 @@ use function strlen; use function substr; +use function strpos; class ReadBuffer { @@ -21,10 +22,10 @@ class ReadBuffer private $buffer = ''; /** @var int */ - private $currentPosition = 0; + private $currentBufferOffset = 0; /** @var int */ - private $readPosition = 0; + private $readBufferOffset = 0; /** * @var int @@ -44,32 +45,54 @@ public function append(string $data): void public function read(int $length): string { if (!$this->isReadable($length)) { - $this->currentPosition = $this->readPosition; + $this->currentBufferOffset = $this->readBufferOffset; throw new IncompleteBufferException(); } - $data = substr($this->buffer, $this->currentPosition, $length); + $data = substr($this->buffer, $this->currentBufferOffset, $length); - $this->currentPosition += $length; + $this->currentBufferOffset += $length; return $data; } public function isReadable(int $length): bool { - return strlen($this->buffer) - $this->currentPosition >= $length; + return strlen($this->buffer) - $this->currentBufferOffset >= $length; } public function flush(): int { - $bytesRead = $this->currentPosition - $this->readPosition; - $this->readPosition = $this->currentPosition; + $bytesRead = $this->currentPosition(); - if ($this->readPosition >= $this->bufferSize) { - $this->buffer = substr($this->buffer, $this->readPosition); - $this->readPosition = 0; - $this->currentPosition = 0; + $this->readBufferOffset = $this->currentBufferOffset; + + if ($this->readBufferOffset >= $this->bufferSize) { + $this->buffer = substr($this->buffer, $this->readBufferOffset); + $this->readBufferOffset = 0; + $this->currentBufferOffset = 0; } return $bytesRead; } + + public function scan(string $pattern): int + { + $position = strpos($this->buffer, $pattern, $this->currentBufferOffset); + + return $position === false ? -1 : ($position - $this->currentBufferOffset) + 1; + } + + public function advance(int $length): void + { + if (!$this->isReadable($length)) { + throw new IncompleteBufferException(); + } + + $this->currentBufferOffset += $length; + } + + public function currentPosition(): int + { + return $this->currentBufferOffset - $this->readBufferOffset; + } } diff --git a/src/StringPacketReader.php b/src/UncompressedPacketReader.php similarity index 51% rename from src/StringPacketReader.php rename to src/UncompressedPacketReader.php index f816abf..c85250c 100644 --- a/src/StringPacketReader.php +++ b/src/UncompressedPacketReader.php @@ -9,23 +9,34 @@ namespace EcomDev\MySQLBinaryProtocol; -class StringPacketReader implements PacketReader, PacketFragmentReader +class UncompressedPacketReader implements PacketReader, PacketPayloadReader { - const INTEGER_LENGTH_MARKER = [ + private const INTEGER_LENGTH_MARKER = [ 0xfc => 2, 0xfd => 3, 0xfe => 8 ]; + private const UNREAD_LENGTH = 2; + private const LENGTH = 0; + private const SEQUENCE = 1; + /** * @var int */ - private $currentPacketLength; + private $awaitedPacketLength = 0; /** - * @var int + * Registry of packets + * + * [ + * [lengthOfPacket, sequence, remainingToReadLength], + * [lengthOfPacket, sequence, remainingToReadLength], + * [lengthOfPacket, sequence, remainingToReadLength], + * ] + * @var array */ - private $currentPacketSequence; + private $packets = []; /** @var BinaryIntegerReader */ private $binaryIntegerReader; @@ -46,21 +57,19 @@ public function __construct(BinaryIntegerReader $binaryIntegerReader, ReadBuffer */ public function append(string $data): void { - $this->readBuffer->append($data); - - if ($this->currentPacketLength === null) { - $this->initPacket(); - } + do { + $data = $this->registerPacket($data); + } while ($data !== ''); } /** * {@inheritDoc} */ - public function readFragment(callable $reader): bool + public function readPayload(callable $reader): bool { try { - $reader($this); - $this->currentPacketLength -= $this->readBuffer->flush(); + $reader($this, $this->packets[0][self::LENGTH], $this->packets[0][self::SEQUENCE]); + $this->advancePacketLength($this->readBuffer->flush()); } catch (IncompleteBufferException $exception) { return false; } @@ -68,34 +77,6 @@ public function readFragment(callable $reader): bool return true; } - /** - * {@inheritDoc} - */ - public function isFullPacket(): bool - { - return $this->readBuffer->isReadable($this->currentPacketLength); - } - - /** - * {@inheritDoc} - */ - public function nextPacket(): void - { - $this->initPacket(); - } - - private function initPacket(): void - { - $this->currentPacketLength = $this->binaryIntegerReader->readFixed( - $this->readBuffer->read(3), - 3 - ); - $this->currentPacketSequence = $this->binaryIntegerReader->readFixed( - $this->readBuffer->read(1), - 1 - ); - } - /** * {@inheritDoc} */ @@ -164,7 +145,15 @@ public function readLengthEncodedStringOrNull(): ?string */ public function readNullTerminatedString(): string { - // TODO: Implement readNullTerminatedString() method. + $nullPosition = $this->readBuffer->scan("\x00"); + + if ($nullPosition === -1) { + throw new IncompleteBufferException(); + } + + $string = $this->readBuffer->read($nullPosition - 1); + $this->readBuffer->advance(1); + return $string; } /** @@ -174,6 +163,61 @@ public function readNullTerminatedString(): string */ public function readRestOfPacketString(): string { - // TODO: Implement readRestOfPacketString() method. + return $this->readBuffer->read( + $this->remainingPacketLengthToRead() + ); + } + + private function registerPacket(string $dataToParse): string + { + if ($this->awaitedPacketLength) { + $trimLength = min(strlen($dataToParse), $this->awaitedPacketLength); + $this->readBuffer->append(substr($dataToParse, 0, $trimLength)); + $this->awaitedPacketLength -= $trimLength; + return substr($dataToParse, $trimLength); + } + + $this->awaitedPacketLength = $this->binaryIntegerReader->readFixed( + substr($dataToParse, 0, 3), + 3 + ); + + $this->packets[] = [ + self::LENGTH => $this->awaitedPacketLength, + self::SEQUENCE => $this->binaryIntegerReader->readFixed($dataToParse[3], 1), + self::UNREAD_LENGTH => $this->awaitedPacketLength + ]; + + return substr($dataToParse, 4); + } + + + private function remainingPacketLengthToRead(): int + { + $currentBufferPosition = $this->readBuffer->currentPosition(); + + $currentPacketIndex = 0; + while ($this->packets[$currentPacketIndex][self::UNREAD_LENGTH] <= $currentBufferPosition) { + $currentBufferPosition -= $this->packets[$currentPacketIndex][self::UNREAD_LENGTH]; + $currentPacketIndex++; + } + + return $this->packets[$currentPacketIndex][self::UNREAD_LENGTH] - $currentBufferPosition; + } + + private function advancePacketLength(int $readLength): void + { + + while ($this->packets[0][self::UNREAD_LENGTH] <= $readLength) { + $readLength -= $this->packets[0][self::UNREAD_LENGTH]; + array_shift($this->packets); + + if (!$this->packets) { + return; + } + } + + + $this->packets[0][self::UNREAD_LENGTH] -= $readLength; } } diff --git a/tests/PacketReaderTest.php b/tests/PacketReaderTest.php index 98f336c..d6ec9f9 100644 --- a/tests/PacketReaderTest.php +++ b/tests/PacketReaderTest.php @@ -22,32 +22,13 @@ protected function setUp(): void $this->reader = (new DefaultPacketReaderFactory())->createWithDefaultSettings(); } - /** - * @test - * - */ - public function reportsIncompleteBufferWhenNotFullLengthProvided() - { - $this->reader->append("\xFF\xFF\xFF\x00Some Payload"); - - $this->assertFalse($this->reader->isFullPacket()); - } - - /** @test */ - public function reportsCompleteBufferWhenAllDataIsProvidedAtOnce() - { - $this->reader->append("\x01\x00\x00\x00\x01"); - - $this->assertTrue($this->reader->isFullPacket()); - } - /** @test */ public function allowsToReadSingleByteIntegerFromPayload() { $this->reader->append("\x01\x00\x00\x00\xF1"); $data = []; - $this->reader->readFragment(function (PacketFragmentReader $fragment) use (&$data) { + $this->reader->readPayload(function (PacketPayloadReader $fragment) use (&$data) { $data[] = $fragment->readFixedInteger(1); }); @@ -55,37 +36,26 @@ public function allowsToReadSingleByteIntegerFromPayload() } /** @test */ - public function allowsToReadMultiplePackets() + public function allowsToReadMultiplePacketsOfIntegers() { $this->reader->append("\x01\x00\x00\x00\xF1"); $this->reader->append("\x01\x00\x00\x00\xF2"); + $this->reader->append("\x01\x00\x00\x00\xF3"); + $this->reader->append("\x01\x00\x00\x00\xF4"); $data = []; - $readOneByte = function (PacketFragmentReader $fragment) use (&$data) { + $readOneByte = function (PacketPayloadReader $fragment) use (&$data) { $data[] = $fragment->readFixedInteger(1); }; - $this->reader->readFragment($readOneByte); - $this->reader->nextPacket(); - $this->reader->readFragment($readOneByte); + $this->reader->readPayload($readOneByte); + $this->reader->readPayload($readOneByte); + $this->reader->readPayload($readOneByte); + $this->reader->readPayload($readOneByte); - $this->assertEquals([241, 242], $data); + $this->assertEquals([241, 242, 243, 244], $data); } - /** @test */ - public function reportsCompletePacketAvailableEvenAfterFragmentIsRead() - { - $this->reader->append("\x02\x00\x00\x00\xF1\xF2"); - - $this->reader->readFragment( - function (PacketFragmentReader $fragment) { - $fragment->readFixedInteger(1); - } - ); - - $this->assertEquals(true, $this->reader->isFullPacket()); - } - /** @test */ public function reportsFragmentIsRead() { @@ -93,7 +63,7 @@ public function reportsFragmentIsRead() $this->assertEquals( true, - $this->reader->readFragment(function (PacketFragmentReader $reader) { + $this->reader->readPayload(function (PacketPayloadReader $reader) { $reader->readFixedInteger(1); }) ); @@ -106,7 +76,7 @@ public function reportsFragmentIsNotRead() $this->assertEquals( false, - $this->reader->readFragment(function (PacketFragmentReader $reader) { + $this->reader->readPayload(function (PacketPayloadReader $reader) { $reader->readFixedInteger(1); $reader->readFixedInteger(1); }) @@ -118,11 +88,12 @@ public function allowsReadingVariousFixedIntegers() { $this->reader->append("\x0D\x00\x00\x00\x00\x02\x02\x00\x00\x00\x00\x00\x00\x00\x00\xF0\x00"); - $data = []; - $this->reader->readFragment(function (PacketFragmentReader $fragment) use (&$data) { - $data[] = $fragment->readFixedInteger(2); // 512 - $data[] = $fragment->readFixedInteger(3); // 2 - $data[] = $fragment->readFixedInteger(8); // 67553994410557440 + $data = $this->readPayload(function (PacketPayloadReader $fragment) { + return [ + $fragment->readFixedInteger(2), // 512 + $fragment->readFixedInteger(3), // 2 + $fragment->readFixedInteger(8), // 67553994410557440 + ]; }); $this->assertEquals( @@ -142,13 +113,14 @@ public function allowsReadingDifferentLengthEncodedIntegers() "\x12\x00\x00\x00\xf9\xfa\xfc\xfb\00\xfd\xff\xff\xf0\xfe\xff\xff\xff\xff\xff\xff\xff\xf0" ); - $data = []; - $this->reader->readFragment(function (PacketFragmentReader $fragment) use (&$data) { - $data[] = $fragment->readLengthEncodedIntegerOrNull(); // 249 - $data[] = $fragment->readLengthEncodedIntegerOrNull(); // 250 - $data[] = $fragment->readLengthEncodedIntegerOrNull(); // 251 - $data[] = $fragment->readLengthEncodedIntegerOrNull(); // 15794175 - $data[] = $fragment->readLengthEncodedIntegerOrNull(); // 17365880163140632575 + $data = $this->readPayload(function (PacketPayloadReader $fragment) { + return [ + $fragment->readLengthEncodedIntegerOrNull(), // 249 + $fragment->readLengthEncodedIntegerOrNull(), // 250 + $fragment->readLengthEncodedIntegerOrNull(), // 251 + $fragment->readLengthEncodedIntegerOrNull(), // 15794175 + $fragment->readLengthEncodedIntegerOrNull(), // 17365880163140632575 + ]; }); $this->assertEquals( @@ -162,11 +134,8 @@ public function allowsReadingDifferentLengthEncodedIntegers() $data ); } - - - /** - * @test - */ + + /** @test */ public function throwsInvalidBinaryDataExceptionWhenLengthEncodedIntegerDoesNotMatchExpectedFormat() { @@ -176,7 +145,7 @@ public function throwsInvalidBinaryDataExceptionWhenLengthEncodedIntegerDoesNotM $this->expectException(InvalidBinaryDataException::class); - $this->reader->readFragment(function (PacketFragmentReader $fragment) { + $this->reader->readPayload(function (PacketPayloadReader $fragment) { $fragment->readLengthEncodedIntegerOrNull(); }); } @@ -188,21 +157,25 @@ public function readsNullForLengthEncodedInteger() "\x01\x00\x00\x00\xfb" ); - $this->reader->readFragment(function (PacketFragmentReader $fragment) { - $this->assertEquals(null, $fragment->readLengthEncodedIntegerOrNull()); - }); + $this->assertEquals( + null, + $this->readPayload(function (PacketPayloadReader $fragment) { + return $fragment->readLengthEncodedIntegerOrNull(); + }) + ); } /** @test */ public function readsFixedLengthString() { $this->reader->append("\x18\x00\x00\x00helloworld!awesomestring"); - $data = []; - $this->reader->readFragment(function (PacketFragmentReader $fragment) use (&$data) { - $data[] = $fragment->readFixedString(5); - $data[] = $fragment->readFixedString(6); - $data[] = $fragment->readFixedString(13); + $data = $this->readPayload(function (PacketPayloadReader $fragment) { + return [ + $fragment->readFixedString(5), + $fragment->readFixedString(6), + $fragment->readFixedString(13) + ]; }); $this->assertEquals( @@ -223,15 +196,16 @@ public function readsLengthEncodedString() $this->reader->append("\x0c\x01\x00\x00\xfc\xff\x00$veryLongString\x05hello\xfb\x0202"); - $data = []; - - $this->reader->readFragment(function (PacketFragmentReader $fragment) use (&$data) { - $data[] = $fragment->readLengthEncodedStringOrNull(); - $data[] = $fragment->readLengthEncodedStringOrNull(); - $data[] = $fragment->readLengthEncodedStringOrNull(); - $data[] = $fragment->readLengthEncodedStringOrNull(); + $data = $this->readPayload(function (PacketPayloadReader $fragment) { + return [ + $fragment->readLengthEncodedStringOrNull(), + $fragment->readLengthEncodedStringOrNull(), + $fragment->readLengthEncodedStringOrNull(), + $fragment->readLengthEncodedStringOrNull() + ]; }); + $this->assertEquals( [ $veryLongString, @@ -242,4 +216,157 @@ public function readsLengthEncodedString() $data ); } + + /** @test */ + public function readsMultipleNullTerminatedStrings() + { + $this->reader->append("\x31\x00\x00\x00first_string\x00second_string\x00third_string\x00"); + $data = $this->readPayload(function (PacketPayloadReader $fragmentReader) { + return [ + $fragmentReader->readNullTerminatedString(), + $fragmentReader->readNullTerminatedString(), + $fragmentReader->readNullTerminatedString() + ]; + }); + + $this->assertEquals( + [ + 'first_string', + 'second_string', + 'third_string' + ], + $data + ); + + } + + /** @test */ + public function stopsReadingPayloadWhenNullCharacterIsNotFoundForAString() + { + $this->reader->append("\x31\x00\x00\x00first_string"); + $data = $this->readPayload(function (PacketPayloadReader $fragmentReader) { + return $fragmentReader->readNullTerminatedString(); + }); + + $this->assertEquals(null, $data); + } + + /** @test */ + public function reportIncompletePayloadReadWhenNullTerminatedStringIsNotCompletelyRead() + { + $this->reader->append("\x31\x00\x00\x00first_string"); + + $this->assertEquals(false, $this->reader->readPayload(function (PacketPayloadReader $fragmentReader) { + $fragmentReader->readNullTerminatedString(); + })); + } + + /** @test */ + public function readsStringThatRepresentsCompletePacket() + { + $this->reader->append("\x0a\x00\x00\x00This is 10\x00\x00\x00\x00"); + + $this->assertEquals('This is 10', $this->readPayload(function (PacketPayloadReader $payloadReader) { + return $payloadReader->readRestOfPacketString(); + })); + } + + /** @test */ + public function readsStringThatRepresentsRemainderOfThePacket() + { + $this->reader->append("\x0C\x00\x00\x00\x01\x02This is 10\x00\x00\x00\x00"); + + $this->assertEquals('This is 10', $this->readPayload(function (PacketPayloadReader $payloadReader) { + $payloadReader->readFixedInteger(1); + $payloadReader->readFixedInteger(1); + return $payloadReader->readRestOfPacketString(); + })); + } + + /** @test */ + public function readMultiplePacketsPacketStringsAddedAsSingleNetworkPacketInSinglePayload() + { + $this->reader->append("\x03\x00\x00\x00one\x03\x00\x00\x00two\x05\x00\x00\x00three\x04\x00\x00\x00four"); + + $this->assertEquals( + [ + 'one', + 'two', + 'three', + 'four' + ], + $this->readPayload(function (PacketPayloadReader $payloadReader) { + return [ + $payloadReader->readRestOfPacketString(), + $payloadReader->readRestOfPacketString(), + $payloadReader->readRestOfPacketString(), + $payloadReader->readRestOfPacketString() + ]; + }) + ); + } + + /** @test */ + public function readMultiplePacketsPacketStringsAddedAsSingleNetworkPacketInMultiplePayloads() + { + $this->reader->append("\x03\x00\x00\x00one\x03\x00\x00\x00two\x05\x00\x00\x00three\x04\x00\x00\x00four"); + + + $readString = function (PacketPayloadReader $payloadReader) { + return $payloadReader->readRestOfPacketString(); + }; + + $this->assertEquals( + [ + 'one', + 'two', + 'three', + 'four' + ], + [ + $this->readPayload($readString), + $this->readPayload($readString), + $this->readPayload($readString), + $this->readPayload($readString) + ] + ); + } + + /** @test */ + public function providesSequenceNumberAndPacketLengthDuringReadingOfPayload() + { + $this->reader + ->append("\x08\x00\x00\x00one\x00two\x00\x06\x00\x00\x01three\x00\x05\x00\x00\x05four\x00"); + + + $readString = function (PacketPayloadReader $payloadReader, int $length, int $sequence) { + $payloadReader->readNullTerminatedString(); + + return [$length, $sequence]; + }; + + $this->assertEquals( + [ + [8, 0], + [8, 0], + [6, 1], + [5, 5] + ], + [ + $this->readPayload($readString), + $this->readPayload($readString), + $this->readPayload($readString), + $this->readPayload($readString), + ] + ); + } + + private function readPayload(callable $fragmentCallable) + { + $this->reader->readPayload(function (...$args) use ($fragmentCallable, &$data) { + $data = $fragmentCallable(...$args); + }); + + return $data; + } } diff --git a/tests/ReadBufferTest.php b/tests/ReadBufferTest.php index cc00dbf..16b0266 100644 --- a/tests/ReadBufferTest.php +++ b/tests/ReadBufferTest.php @@ -190,5 +190,102 @@ public function flushReturnsZeroWhenNoBytesRead() $this->assertEquals(0, $this->readBuffer->flush()); } + + /** @test */ + public function returnsLengthOfReadInOrderToReadDataUpToThisCharacter() + { + $this->readBuffer->append('some:data'); + + $this->assertEquals(5, $this->readBuffer->scan(':')); + } + + /** @test */ + public function returnsNegativeIndexWhenNoMatchFoundForScan() + { + $this->readBuffer->append('some data without character'); + + $this->assertEquals(-1, $this->readBuffer->scan(':')); + } + + /** @test */ + public function returnsLengthOfReadEvenIfCharacterForSearchIsAFirstOneInBuffer() + { + $this->readBuffer->append(':some other data'); + + $this->assertEquals(1, $this->readBuffer->scan(':')); + } + + /** @test */ + public function returnsLengthOfRequiredReadForTheNextCharacterOccurrence() + { + $this->readBuffer->append('some:other:data'); + $this->readBuffer->read(5); + + $this->assertEquals(6, $this->readBuffer->scan(':')); + } + + /** @test */ + public function advancesBufferPositionFromBeginningOfBuffer() + { + $this->readBuffer->append('some very long data'); + + $this->readBuffer->advance(10); + + $this->assertEquals('long data', $this->readBuffer->read(9)); + } + + /** @test */ + public function advancesBufferPositionAfterRead() + { + $this->readBuffer->append('some other nice data after advance'); + $this->readBuffer->read(10); + $this->readBuffer->advance(11); + + $this->assertEquals('after advance', $this->readBuffer->read(13)); + } + /** @test */ + public function reportsIncompleteBufferWhenAdvanceIsLargerThanCurrentData() + { + $this->readBuffer->append('some data'); + + $this->readBuffer->read(5); + + $this->expectException(IncompleteBufferException::class); + + $this->readBuffer->advance(5); + } + + + /** @test */ + public function defaultReadPositionInBufferIsZero() + { + $this->assertEquals(0, $this->readBuffer->currentPosition()); + } + + /** @test */ + public function currentPositionIsMovedWithNumberOfReadBytes() + { + $this->readBuffer->append('Some very long string data'); + + $this->readBuffer->read(4); + $this->readBuffer->read(6); + + $this->assertEquals(10, $this->readBuffer->currentPosition()); + } + + /** @test */ + public function currentPositionIsRelativeToFlushedReadData() + { + $this->readBuffer->append('Some very long string data'); + + $this->readBuffer->read(10); + + $this->readBuffer->flush(); + + $this->readBuffer->read(3); + + $this->assertEquals(3, $this->readBuffer->currentPosition()); + + } } From ef84c6786ed19082fd2afdc8c58398e5be171f92 Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Thu, 20 Jun 2019 23:41:14 +0200 Subject: [PATCH 16/23] Remove advance method from buffer --- src/ReadBuffer.php | 9 --------- src/UncompressedPacketReader.php | 2 +- tests/ReadBufferTest.php | 33 -------------------------------- 3 files changed, 1 insertion(+), 43 deletions(-) diff --git a/src/ReadBuffer.php b/src/ReadBuffer.php index ce843d3..7033965 100644 --- a/src/ReadBuffer.php +++ b/src/ReadBuffer.php @@ -82,15 +82,6 @@ public function scan(string $pattern): int return $position === false ? -1 : ($position - $this->currentBufferOffset) + 1; } - public function advance(int $length): void - { - if (!$this->isReadable($length)) { - throw new IncompleteBufferException(); - } - - $this->currentBufferOffset += $length; - } - public function currentPosition(): int { return $this->currentBufferOffset - $this->readBufferOffset; diff --git a/src/UncompressedPacketReader.php b/src/UncompressedPacketReader.php index c85250c..6fe555a 100644 --- a/src/UncompressedPacketReader.php +++ b/src/UncompressedPacketReader.php @@ -152,7 +152,7 @@ public function readNullTerminatedString(): string } $string = $this->readBuffer->read($nullPosition - 1); - $this->readBuffer->advance(1); + $this->readBuffer->read(1); return $string; } diff --git a/tests/ReadBufferTest.php b/tests/ReadBufferTest.php index 16b0266..414d145 100644 --- a/tests/ReadBufferTest.php +++ b/tests/ReadBufferTest.php @@ -223,39 +223,6 @@ public function returnsLengthOfRequiredReadForTheNextCharacterOccurrence() $this->assertEquals(6, $this->readBuffer->scan(':')); } - - /** @test */ - public function advancesBufferPositionFromBeginningOfBuffer() - { - $this->readBuffer->append('some very long data'); - - $this->readBuffer->advance(10); - - $this->assertEquals('long data', $this->readBuffer->read(9)); - } - - /** @test */ - public function advancesBufferPositionAfterRead() - { - $this->readBuffer->append('some other nice data after advance'); - $this->readBuffer->read(10); - $this->readBuffer->advance(11); - - $this->assertEquals('after advance', $this->readBuffer->read(13)); - } - - /** @test */ - public function reportsIncompleteBufferWhenAdvanceIsLargerThanCurrentData() - { - $this->readBuffer->append('some data'); - - $this->readBuffer->read(5); - - $this->expectException(IncompleteBufferException::class); - - $this->readBuffer->advance(5); - } - /** @test */ public function defaultReadPositionInBufferIsZero() From e6887be1787a4b8c84d7688846f1ba2f0c5bcca8 Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Fri, 21 Jun 2019 15:07:07 +0200 Subject: [PATCH 17/23] Added: - Constants for Capabilities Flags - Constants for Server Status Flags --- src/CapabilityFlags.php | 38 ++++++++++++++++++++++++++++++++++++++ src/StatusFlags.php | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 src/CapabilityFlags.php create mode 100644 src/StatusFlags.php diff --git a/src/CapabilityFlags.php b/src/CapabilityFlags.php new file mode 100644 index 0000000..03d8eae --- /dev/null +++ b/src/CapabilityFlags.php @@ -0,0 +1,38 @@ + Date: Mon, 24 Jun 2019 21:21:59 +0200 Subject: [PATCH 18/23] First terrible iteration of handshake parser... A lot to refactor --- src/CharsetIdentifiers.php | 17 ++++ src/Frame/HandshakeV10.php | 48 ++++++++++ src/Frame/HandshakeV10Builder.php | 112 ++++++++++++++++++++++++ src/HandshakeParser.php | 66 ++++++++++++++ tests/Frame/HandshakeBuilderV10Test.php | 83 ++++++++++++++++++ tests/HandshakeParserTest.php | 88 +++++++++++++++++++ 6 files changed, 414 insertions(+) create mode 100644 src/CharsetIdentifiers.php create mode 100644 src/Frame/HandshakeV10.php create mode 100644 src/Frame/HandshakeV10Builder.php create mode 100644 src/HandshakeParser.php create mode 100644 tests/Frame/HandshakeBuilderV10Test.php create mode 100644 tests/HandshakeParserTest.php diff --git a/src/CharsetIdentifiers.php b/src/CharsetIdentifiers.php new file mode 100644 index 0000000..80b304e --- /dev/null +++ b/src/CharsetIdentifiers.php @@ -0,0 +1,17 @@ +serverVersion = $serverVersion; + $this->connectionId = $connectionId; + $this->authData = $authData; + $this->capabilities = $capabilities; + $this->charset = $charset; + $this->status = $status; + $this->authPlugin = $authPlugin; + } + + +} diff --git a/src/Frame/HandshakeV10Builder.php b/src/Frame/HandshakeV10Builder.php new file mode 100644 index 0000000..d2ff793 --- /dev/null +++ b/src/Frame/HandshakeV10Builder.php @@ -0,0 +1,112 @@ +serverVersion = $serverVersion; + $builder->clientId = $clientId; + return $builder; + } + + public function withAuthData(string $authData): self + { + $builder = clone $this; + $builder->authData = $authData; + return $builder; + } + + public function withCapabilities(int $flags): self + { + $builder = clone $this; + $builder->capabilities = $flags; + return $builder; + } + + public function withCharset(int $charsetId): self + { + $builder = clone $this; + + $builder->charset = $charsetId; + return $builder; + } + + + public function withStatus(int $status): self + { + $builder = clone $this; + + $builder->status = $status; + return $builder; + } + + public function withAuthPlugin(string $authPlugin): self + { + $builder = clone $this; + + $builder->authPlugin = $authPlugin; + + return $builder; + } + + public function build(): HandshakeV10 + { + return new HandshakeV10( + $this->serverVersion, + $this->clientId, + $this->authData, + $this->capabilities, + $this->charset, + $this->status, + $this->authPlugin + ); + } + + + +} diff --git a/src/HandshakeParser.php b/src/HandshakeParser.php new file mode 100644 index 0000000..a75942f --- /dev/null +++ b/src/HandshakeParser.php @@ -0,0 +1,66 @@ +frameBuilder = $frameBuilder; + $this->frameReceiver = $frameReceiver; + } + + public function __invoke(PacketPayloadReader $reader) + { + $reader->readFixedInteger(1); + + $frameBuilder = $this->frameBuilder->withServerInfo( + $reader->readNullTerminatedString(), + $reader->readFixedInteger(4) + ); + + $authData = $reader->readFixedString(8); + + $reader->readFixedString(1); + + $capabilities = $reader->readFixedInteger(2); + $frameBuilder = $frameBuilder->withCharset($reader->readFixedInteger(1)) + ->withStatus($reader->readFixedInteger(2)); + + $capabilities += $reader->readFixedInteger(2) << 16; + + $frameBuilder = $frameBuilder->withCapabilities($capabilities); + + $totalAuthDataLenght = $reader->readFixedInteger(1); + + $reader->readFixedString(10); + + $authData .= $reader->readFixedString($totalAuthDataLenght - 8); + + $frameBuilder = $frameBuilder->withAuthData($authData); + + $authPlugin = $reader->readNullTerminatedString(); + + ($this->frameReceiver)($frameBuilder->withAuthPlugin($authPlugin)->build()); + } +} diff --git a/tests/Frame/HandshakeBuilderV10Test.php b/tests/Frame/HandshakeBuilderV10Test.php new file mode 100644 index 0000000..248ce37 --- /dev/null +++ b/tests/Frame/HandshakeBuilderV10Test.php @@ -0,0 +1,83 @@ +builder = new HandshakeV10Builder(); + } + + /** @test */ + public function createHandshakeWithMinimalData() + { + $this->assertEquals( + new HandshakeV10( + '5.5.1', + 10, + "\x01\x02\x03\x04\x05\x06\x07\x08", + CapabilityFlags::CLIENT_PROTOCOL_41 + ), + $this->builder->withServerInfo('5.5.1', 10) + ->withAuthData("\x01\x02\x03\x04\x05\x06\x07\x08") + ->withCapabilities(CapabilityFlags::CLIENT_PROTOCOL_41) + ->build() + ); + } + + /** @test */ + public function createsHandshakeWithServerStatusAndCharset() + { + $this->assertEquals( + new HandshakeV10( + '1.0.0', + 1, + 'thisisstringdata', + 0, + CharsetIdentifiers::UTF8, + StatusFlags::SERVER_STATUS_AUTOCOMMIT + ), + $this->builder->withServerInfo('1.0.0', 1) + ->withAuthData('thisisstringdata') + ->withCharset(CharsetIdentifiers::UTF8) + ->withStatus(StatusFlags::SERVER_STATUS_AUTOCOMMIT) + ->build() + ); + } + + /** @test */ + public function createsHandshakeWithAuthPluginSpecified() + { + $this->assertEquals( + new HandshakeV10( + '1.0.0', + 1, + 'thisisstringdata', + 0, + 0, + 0, + 'mysql_native_password' + ), + $this->builder->withServerInfo('1.0.0', 1) + ->withAuthData('thisisstringdata') + ->withAuthPlugin('mysql_native_password') + ->build() + ); + } +} diff --git a/tests/HandshakeParserTest.php b/tests/HandshakeParserTest.php new file mode 100644 index 0000000..5edd086 --- /dev/null +++ b/tests/HandshakeParserTest.php @@ -0,0 +1,88 @@ +frameBuilder = new Frame\HandshakeV10Builder(); + $this->packetReader = (new DefaultPacketReaderFactory())->createWithDefaultSettings(); + + $this->parser = new HandshakeParser( + $this->frameBuilder, + function (HandshakeV10 $handshake) { + $this->frames[] = $handshake; + } + ); + } + + /** @test */ + public function parsesMySQL8HandshakeInitMessage() + { + $this->packetReader->append( + "\x4a\x00\x00\x00\x0a8.0.16\x00\x0d\x00\x00\x00\x10\x4a\x12\x05" + . "\x71\x5d\x78\x63\x00\xff\xff\xff\x02\x00\xff\xc3\x15\x00\x00\x00" + . "\x00\x00\x00\x00\x00\x00\x00\x6e\x48\x49\x48\x56\x78\x42\x33\x76" + . "\x39\x3d\x5c\x00\x63\x61\x63\x68\x69\x6e\x67\x5f\x73\x68\x61\x32" + . "\x5f\x70\x61\x73\x73\x77\x6f\x72\x64\x00" + ); + + $this->packetReader->readPayload($this->parser); + + $this->assertEquals( + $this->frameBuilder + ->withServerInfo( + '8.0.16', + 13 + ) + ->withStatus( + StatusFlags::SERVER_STATUS_AUTOCOMMIT + ) + ->withCharset( + CharsetIdentifiers::UTF8MB4 + ) + ->withCapabilities(0xc3ffffff) + ->withAuthData( + "\x10\x4a\x12\x05\x71\x5d\x78\x63\x6e\x48\x49\x48\x56\x78\x42\x33\x76\x39\x3d\x5c\x00" + ) + ->withAuthPlugin( + "caching_sha2_password" + ) + ->build(), + current($this->frames) + ); + + } + + +} From 12c1c4c9c09936b9e9b333bbdb36c4b891590b8e Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Thu, 27 Jun 2019 19:38:53 +0200 Subject: [PATCH 19/23] Refactored uncompressed packet reader into simplier implementation to be able use payload reader outside of packet structure --- src/BufferPayloadReader.php | 144 ++++++++ src/BufferPayloadReaderFactory.php | 18 + src/DefaultPacketReaderFactory.php | 6 +- src/HandshakeParser.php | 2 +- ...ketPayloadReader.php => PayloadReader.php} | 2 +- src/UncompressedPacketReader.php | 146 ++------ tests/BufferPayloadReaderTest.php | 317 ++++++++++++++++++ tests/PacketReaderTest.php | 36 +- 8 files changed, 534 insertions(+), 137 deletions(-) create mode 100644 src/BufferPayloadReader.php create mode 100644 src/BufferPayloadReaderFactory.php rename src/{PacketPayloadReader.php => PayloadReader.php} (98%) create mode 100644 tests/BufferPayloadReaderTest.php diff --git a/src/BufferPayloadReader.php b/src/BufferPayloadReader.php new file mode 100644 index 0000000..64860d4 --- /dev/null +++ b/src/BufferPayloadReader.php @@ -0,0 +1,144 @@ + 2, + 0xfd => 3, + 0xfe => 8 + ]; + + private const NULL_MARKER = 0xfb; + + /** @var ReadBuffer */ + private $buffer; + + /** + * @var BinaryIntegerReader + */ + private $integerReader; + + /** @var int[] */ + private $unreadPacketLength; + + + public function __construct(ReadBuffer $buffer, array $unreadPacketLength, BinaryIntegerReader $integerReader) + { + $this->buffer = $buffer; + $this->integerReader = $integerReader; + $this->unreadPacketLength = $unreadPacketLength; + } + + /** + * {@inheritDoc} + */ + public function readFixedInteger(int $bytes) + { + return $this->integerReader->readFixed( + $this->buffer->read($bytes), + $bytes + ); + } + + /** + * {@inheritDoc} + */ + public function readLengthEncodedIntegerOrNull() + { + $firstByte = $this->readFixedInteger(1); + + if ($firstByte < 251) { + return $firstByte; + } + + if ($firstByte === self::NULL_MARKER) { + return null; + } + + if (isset(self::LENGTH_MARKERS[$firstByte])) { + return $this->readFixedInteger(self::LENGTH_MARKERS[$firstByte]); + } + + throw new InvalidBinaryDataException(); + } + + /** + * Reads string of specified length from buffer + * + * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html + */ + public function readFixedString(int $length): string + { + return $this->buffer->read($length); + } + + /** + * Reads string that is has length as the first part of the fragment + * + * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html + */ + public function readLengthEncodedStringOrNull(): ?string + { + $length = $this->readLengthEncodedIntegerOrNull(); + + if ($length === null) { + return null; + } + + return $this->buffer->read($length); + } + + /** + * Reads string till x00 character + * + * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html + */ + public function readNullTerminatedString(): string + { + $nullPosition = $this->buffer->scan("\x00"); + + if ($nullPosition === -1) { + throw new IncompleteBufferException(); + } + + $string = $this->buffer->read($nullPosition - 1); + $this->buffer->read(1); + + return $string; + } + + /** + * Reads string that is rest of payload + * + * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html + */ + public function readRestOfPacketString(): string + { + return $this->buffer->read( + $this->remainingPacketLengthToRead() + ); + } + + private function remainingPacketLengthToRead(): int + { + $currentBufferPosition = $this->buffer->currentPosition(); + + $currentPacketIndex = 0; + while ($this->unreadPacketLength[$currentPacketIndex] <= $currentBufferPosition) { + $currentBufferPosition -= $this->unreadPacketLength[$currentPacketIndex]; + $currentPacketIndex++; + } + + return $this->unreadPacketLength[$currentPacketIndex] - $currentBufferPosition; + } + +} diff --git a/src/BufferPayloadReaderFactory.php b/src/BufferPayloadReaderFactory.php new file mode 100644 index 0000000..890e009 --- /dev/null +++ b/src/BufferPayloadReaderFactory.php @@ -0,0 +1,18 @@ +frameReceiver = $frameReceiver; } - public function __invoke(PacketPayloadReader $reader) + public function __invoke(PayloadReader $reader) { $reader->readFixedInteger(1); diff --git a/src/PacketPayloadReader.php b/src/PayloadReader.php similarity index 98% rename from src/PacketPayloadReader.php rename to src/PayloadReader.php index 0cc58f4..05bd7db 100644 --- a/src/PacketPayloadReader.php +++ b/src/PayloadReader.php @@ -12,7 +12,7 @@ * Reader for buffer fragments * */ -interface PacketPayloadReader +interface PayloadReader { /** * Reads fixed value integer diff --git a/src/UncompressedPacketReader.php b/src/UncompressedPacketReader.php index 6fe555a..4e5a709 100644 --- a/src/UncompressedPacketReader.php +++ b/src/UncompressedPacketReader.php @@ -9,7 +9,7 @@ namespace EcomDev\MySQLBinaryProtocol; -class UncompressedPacketReader implements PacketReader, PacketPayloadReader +class UncompressedPacketReader implements PacketReader { private const INTEGER_LENGTH_MARKER = [ 0xfc => 2, @@ -30,14 +30,17 @@ class UncompressedPacketReader implements PacketReader, PacketPayloadReader * Registry of packets * * [ - * [lengthOfPacket, sequence, remainingToReadLength], - * [lengthOfPacket, sequence, remainingToReadLength], - * [lengthOfPacket, sequence, remainingToReadLength], + * [lengthOfPacket, sequence], + * [lengthOfPacket, sequence], + * [lengthOfPacket, sequence], * ] * @var array */ private $packets = []; + /** @var int[] */ + private $remainingPacketLength = []; + /** @var BinaryIntegerReader */ private $binaryIntegerReader; @@ -45,11 +48,20 @@ class UncompressedPacketReader implements PacketReader, PacketPayloadReader * @var ReadBuffer */ private $readBuffer; + /** + * @var BufferPayloadReaderFactory + */ + private $payloadReaderFactory; - public function __construct(BinaryIntegerReader $binaryIntegerReader, ReadBuffer $readBuffer) + public function __construct( + BinaryIntegerReader $binaryIntegerReader, + ReadBuffer $readBuffer, + BufferPayloadReaderFactory $payloadReaderFactory + ) { $this->binaryIntegerReader = $binaryIntegerReader; $this->readBuffer = $readBuffer; + $this->payloadReaderFactory = $payloadReaderFactory; } /** @@ -68,7 +80,12 @@ public function append(string $data): void public function readPayload(callable $reader): bool { try { - $reader($this, $this->packets[0][self::LENGTH], $this->packets[0][self::SEQUENCE]); + $reader( + $this->payloadReaderFactory->create($this->readBuffer, $this->remainingPacketLength), + $this->packets[0][self::LENGTH], + $this->packets[0][self::SEQUENCE] + ); + $this->advancePacketLength($this->readBuffer->flush()); } catch (IncompleteBufferException $exception) { return false; @@ -77,96 +94,6 @@ public function readPayload(callable $reader): bool return true; } - /** - * {@inheritDoc} - */ - public function readFixedInteger(int $bytes) - { - return $this->binaryIntegerReader->readFixed( - $this->readBuffer->read($bytes), - $bytes - ); - } - - /** - * Reads length encoded integer - * - * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_integers.html - */ - public function readLengthEncodedIntegerOrNull() - { - $value = $this->readFixedInteger(1); - - if ($value === 0xfb) { - return null; - } - - if ($value < 251) { - return $value; - } - - if (!isset(self::INTEGER_LENGTH_MARKER[$value])) { - throw new InvalidBinaryDataException(); - } - - return $this->readFixedInteger(self::INTEGER_LENGTH_MARKER[$value]); - } - - /** - * Reads string of specified length from buffer - * - * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html - */ - public function readFixedString(int $length): string - { - return $this->readBuffer->read($length); - } - - /** - * Reads string that is has length as the first part of the fragment - * - * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html - */ - public function readLengthEncodedStringOrNull(): ?string - { - $stringLength = $this->readLengthEncodedIntegerOrNull(); - - if ($stringLength === null) { - return null; - } - - return $this->readFixedString($stringLength); - } - - /** - * Reads string till x00 character - * - * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html - */ - public function readNullTerminatedString(): string - { - $nullPosition = $this->readBuffer->scan("\x00"); - - if ($nullPosition === -1) { - throw new IncompleteBufferException(); - } - - $string = $this->readBuffer->read($nullPosition - 1); - $this->readBuffer->read(1); - return $string; - } - - /** - * Reads string that is rest of payload - * - * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html - */ - public function readRestOfPacketString(): string - { - return $this->readBuffer->read( - $this->remainingPacketLengthToRead() - ); - } private function registerPacket(string $dataToParse): string { @@ -184,40 +111,27 @@ private function registerPacket(string $dataToParse): string $this->packets[] = [ self::LENGTH => $this->awaitedPacketLength, - self::SEQUENCE => $this->binaryIntegerReader->readFixed($dataToParse[3], 1), - self::UNREAD_LENGTH => $this->awaitedPacketLength + self::SEQUENCE => $this->binaryIntegerReader->readFixed($dataToParse[3], 1) ]; + $this->remainingPacketLength[] = $this->awaitedPacketLength; + return substr($dataToParse, 4); } - private function remainingPacketLengthToRead(): int - { - $currentBufferPosition = $this->readBuffer->currentPosition(); - - $currentPacketIndex = 0; - while ($this->packets[$currentPacketIndex][self::UNREAD_LENGTH] <= $currentBufferPosition) { - $currentBufferPosition -= $this->packets[$currentPacketIndex][self::UNREAD_LENGTH]; - $currentPacketIndex++; - } - - return $this->packets[$currentPacketIndex][self::UNREAD_LENGTH] - $currentBufferPosition; - } - private function advancePacketLength(int $readLength): void { - - while ($this->packets[0][self::UNREAD_LENGTH] <= $readLength) { - $readLength -= $this->packets[0][self::UNREAD_LENGTH]; + while ($this->remainingPacketLength[0] <= $readLength) { + $readLength -= $this->remainingPacketLength[0]; array_shift($this->packets); + array_shift($this->remainingPacketLength); if (!$this->packets) { return; } } - - $this->packets[0][self::UNREAD_LENGTH] -= $readLength; + $this->remainingPacketLength[0] -= $readLength; } } diff --git a/tests/BufferPayloadReaderTest.php b/tests/BufferPayloadReaderTest.php new file mode 100644 index 0000000..9d18d44 --- /dev/null +++ b/tests/BufferPayloadReaderTest.php @@ -0,0 +1,317 @@ +payloadReaderFactory = new BufferPayloadReaderFactory(); + $this->buffer = new ReadBuffer(); + } + + + /** @test */ + public function readsOneByteFixedInteger() + { + $payloadReader = $this->createPayloadReader("\x01\x02\x03"); + + $this->assertEquals( + [ + 1, + 2, + 3 + ], + [ + $payloadReader->readFixedInteger(1), + $payloadReader->readFixedInteger(1), + $payloadReader->readFixedInteger(1), + ] + ); + } + + /** @test */ + public function readsMultipleBytesOfFixedInteger() + { + $payloadReader = $this->createPayloadReader( + "\x00\x02\x02\x00\x00\x00\x00\x00\x00\x00\x00\xF0\x00" + ); + + $this->assertEquals( + [ + 512, + 2, + 67553994410557440 + ], + [ + $payloadReader->readFixedInteger(2), + $payloadReader->readFixedInteger(3), + $payloadReader->readFixedInteger(8), + ] + ); + } + + /** @test */ + public function readsOneByteLengthEncodedInteger() + { + $payloadReader = $this->createPayloadReader("\x00\xfa\xf9\xa0"); + + $this->assertEquals( + [ + 0, + 250, + 249, + 160 + ], + [ + $payloadReader->readLengthEncodedIntegerOrNull(), + $payloadReader->readLengthEncodedIntegerOrNull(), + $payloadReader->readLengthEncodedIntegerOrNull(), + $payloadReader->readLengthEncodedIntegerOrNull(), + ] + ); + } + + /** @test */ + public function readsTwoByteLengthEncodedInteger() + { + $payloadReader = $this->createPayloadReader("\xfc\xfb\x00\xfc\xfc\x00\xfc\xff\xf0"); + + $this->assertEquals( + [ + 251, + 252, + 61695 + ], + [ + $payloadReader->readLengthEncodedIntegerOrNull(), + $payloadReader->readLengthEncodedIntegerOrNull(), + $payloadReader->readLengthEncodedIntegerOrNull(), + ] + ); + } + + /** @test */ + public function readsThreeByteLengthEncodedInteger() + { + $payloadReader = $this->createPayloadReader("\xfd\xff\xf0\x00\xfd\xa9\xff\xf0"); + + $this->assertEquals( + [ + 61695, + 15794089 + ], + [ + $payloadReader->readLengthEncodedIntegerOrNull(), + $payloadReader->readLengthEncodedIntegerOrNull(), + ] + ); + } + + /** @test */ + public function readsEightByteLengthEncodedInteger() + { + $payloadReader = $this->createPayloadReader( + "\xfe\xa9\xff\xf0\x00\x00\x00\x00\x00\xfe\x09\xea\xca\xff\x0a\xff\xff\x0a" + ); + + $this->assertEquals( + [ + 15794089, + 792632482146740745 + ], + [ + $payloadReader->readLengthEncodedIntegerOrNull(), + $payloadReader->readLengthEncodedIntegerOrNull(), + ] + ); + } + + /** @test */ + public function readsNullValueFromLengthEncodedIntegerSpec() + { + $payloadReader = $this->createPayloadReader("\xfb"); + + $this->assertEquals( + null, + $payloadReader->readLengthEncodedIntegerOrNull() + ); + } + + /** @test */ + public function reportsIncorrectLengthEncodedIntegerGivenFirstByteIsOutOfBounds() + { + $payloadReader = $this->createPayloadReader("\xff"); + + $this->expectException(InvalidBinaryDataException::class); + $payloadReader->readLengthEncodedIntegerOrNull(); + } + + /** @test */ + public function readsFixedLengthString() + { + $payloadReader = $this->createPayloadReader('onetwothree'); + + $this->assertEquals( + [ + 'one', + 'two', + 'three' + ], + [ + $payloadReader->readFixedString(3), + $payloadReader->readFixedString(3), + $payloadReader->readFixedString(5), + ] + ); + } + + /** @test */ + public function readsLengthEncodedString() + { + $payloadReader = $this->createPayloadReader( + "\x01a\x03one\x05three" + ); + + $this->assertEquals( + [ + 'a', + 'one', + 'three' + ], + [ + $payloadReader->readLengthEncodedStringOrNull(), + $payloadReader->readLengthEncodedStringOrNull(), + $payloadReader->readLengthEncodedStringOrNull(), + ] + ); + } + + /** @test */ + public function readsLongLengthEncodedString() + { + $payloadReader = $this->createPayloadReader("\xfc\xe8\x03" . str_repeat('a', 1000)); + + $this->assertEquals( + str_repeat('a', 1000), + $payloadReader->readLengthEncodedStringOrNull() + ); + } + + + /** @test */ + public function readsNullStringValue() + { + $payloadReader = $this->createPayloadReader("\xfb"); + + $this->assertEquals( + null, + $payloadReader->readLengthEncodedStringOrNull() + ); + } + + /** @test */ + public function readsNullTerminatedString() + { + $payloadReader = $this->createPayloadReader("null_terminated_string\x00other_data"); + + $this->assertEquals('null_terminated_string', $payloadReader->readNullTerminatedString()); + } + + /** @test */ + public function readsMultipleNullTerminatedStrings() + { + $payloadReader = $this->createPayloadReader("null_terminated_string\x00other_null_terminated_string\x00"); + + $this->assertEquals( + [ + 'null_terminated_string', + 'other_null_terminated_string' + ], + [ + $payloadReader->readNullTerminatedString(), + $payloadReader->readNullTerminatedString(), + ] + ); + } + + /** @test */ + public function throwsIncompleteBufferExceptionWhenNullCharacterIsNotPresent() + { + $payloadReader = $this->createPayloadReader('some string without null character'); + + $this->expectException(IncompleteBufferException::class); + + $payloadReader->readNullTerminatedString(); + } + + /** @test */ + public function readsStringTillEndOfTheBuffer() + { + $payloadReader = $this->createPayloadReader('some string till end of buffer'); + + $this->assertEquals('some string till end of buffer', $payloadReader->readRestOfPacketString()); + } + + /** @test */ + public function readsMultipleStringsThatRepresentCompletePacket() + { + $payloadReader = $this->createPayloadReader( + 'packet1packet2packet3last packet', + 7, 7, 7, 11 + ); + + $this->assertEquals( + [ + 'packet1', + 'packet2', + 'packet3', + 'last packet' + ], + [ + $payloadReader->readRestOfPacketString(), + $payloadReader->readRestOfPacketString(), + $payloadReader->readRestOfPacketString(), + $payloadReader->readRestOfPacketString(), + ] + ); + } + + /** @test */ + public function readsRestOfPacketStringStartingFromCurrentBufferPosition() + { + $payloadReader = $this->createPayloadReader( + 'onerest of packetstring that should not be read', + 17, 30 + ); + + $payloadReader->readFixedString(3); + + $this->assertEquals( + 'rest of packet', + $payloadReader->readRestOfPacketString() + ); + } + + private function createPayloadReader(string $payload, int ...$packetLength): PayloadReader + { + $buffer = new ReadBuffer(); + $buffer->append($payload); + $packetLength = $packetLength ?: [strlen($payload)]; + return $this->payloadReaderFactory->create($buffer, $packetLength); + } +} diff --git a/tests/PacketReaderTest.php b/tests/PacketReaderTest.php index d6ec9f9..62d3202 100644 --- a/tests/PacketReaderTest.php +++ b/tests/PacketReaderTest.php @@ -28,7 +28,7 @@ public function allowsToReadSingleByteIntegerFromPayload() $this->reader->append("\x01\x00\x00\x00\xF1"); $data = []; - $this->reader->readPayload(function (PacketPayloadReader $fragment) use (&$data) { + $this->reader->readPayload(function (PayloadReader $fragment) use (&$data) { $data[] = $fragment->readFixedInteger(1); }); @@ -44,7 +44,7 @@ public function allowsToReadMultiplePacketsOfIntegers() $this->reader->append("\x01\x00\x00\x00\xF4"); $data = []; - $readOneByte = function (PacketPayloadReader $fragment) use (&$data) { + $readOneByte = function (PayloadReader $fragment) use (&$data) { $data[] = $fragment->readFixedInteger(1); }; @@ -63,7 +63,7 @@ public function reportsFragmentIsRead() $this->assertEquals( true, - $this->reader->readPayload(function (PacketPayloadReader $reader) { + $this->reader->readPayload(function (PayloadReader $reader) { $reader->readFixedInteger(1); }) ); @@ -76,7 +76,7 @@ public function reportsFragmentIsNotRead() $this->assertEquals( false, - $this->reader->readPayload(function (PacketPayloadReader $reader) { + $this->reader->readPayload(function (PayloadReader $reader) { $reader->readFixedInteger(1); $reader->readFixedInteger(1); }) @@ -88,7 +88,7 @@ public function allowsReadingVariousFixedIntegers() { $this->reader->append("\x0D\x00\x00\x00\x00\x02\x02\x00\x00\x00\x00\x00\x00\x00\x00\xF0\x00"); - $data = $this->readPayload(function (PacketPayloadReader $fragment) { + $data = $this->readPayload(function (PayloadReader $fragment) { return [ $fragment->readFixedInteger(2), // 512 $fragment->readFixedInteger(3), // 2 @@ -113,7 +113,7 @@ public function allowsReadingDifferentLengthEncodedIntegers() "\x12\x00\x00\x00\xf9\xfa\xfc\xfb\00\xfd\xff\xff\xf0\xfe\xff\xff\xff\xff\xff\xff\xff\xf0" ); - $data = $this->readPayload(function (PacketPayloadReader $fragment) { + $data = $this->readPayload(function (PayloadReader $fragment) { return [ $fragment->readLengthEncodedIntegerOrNull(), // 249 $fragment->readLengthEncodedIntegerOrNull(), // 250 @@ -145,7 +145,7 @@ public function throwsInvalidBinaryDataExceptionWhenLengthEncodedIntegerDoesNotM $this->expectException(InvalidBinaryDataException::class); - $this->reader->readPayload(function (PacketPayloadReader $fragment) { + $this->reader->readPayload(function (PayloadReader $fragment) { $fragment->readLengthEncodedIntegerOrNull(); }); } @@ -159,7 +159,7 @@ public function readsNullForLengthEncodedInteger() $this->assertEquals( null, - $this->readPayload(function (PacketPayloadReader $fragment) { + $this->readPayload(function (PayloadReader $fragment) { return $fragment->readLengthEncodedIntegerOrNull(); }) ); @@ -170,7 +170,7 @@ public function readsFixedLengthString() { $this->reader->append("\x18\x00\x00\x00helloworld!awesomestring"); - $data = $this->readPayload(function (PacketPayloadReader $fragment) { + $data = $this->readPayload(function (PayloadReader $fragment) { return [ $fragment->readFixedString(5), $fragment->readFixedString(6), @@ -196,7 +196,7 @@ public function readsLengthEncodedString() $this->reader->append("\x0c\x01\x00\x00\xfc\xff\x00$veryLongString\x05hello\xfb\x0202"); - $data = $this->readPayload(function (PacketPayloadReader $fragment) { + $data = $this->readPayload(function (PayloadReader $fragment) { return [ $fragment->readLengthEncodedStringOrNull(), $fragment->readLengthEncodedStringOrNull(), @@ -221,7 +221,7 @@ public function readsLengthEncodedString() public function readsMultipleNullTerminatedStrings() { $this->reader->append("\x31\x00\x00\x00first_string\x00second_string\x00third_string\x00"); - $data = $this->readPayload(function (PacketPayloadReader $fragmentReader) { + $data = $this->readPayload(function (PayloadReader $fragmentReader) { return [ $fragmentReader->readNullTerminatedString(), $fragmentReader->readNullTerminatedString(), @@ -244,7 +244,7 @@ public function readsMultipleNullTerminatedStrings() public function stopsReadingPayloadWhenNullCharacterIsNotFoundForAString() { $this->reader->append("\x31\x00\x00\x00first_string"); - $data = $this->readPayload(function (PacketPayloadReader $fragmentReader) { + $data = $this->readPayload(function (PayloadReader $fragmentReader) { return $fragmentReader->readNullTerminatedString(); }); @@ -256,7 +256,7 @@ public function reportIncompletePayloadReadWhenNullTerminatedStringIsNotComplete { $this->reader->append("\x31\x00\x00\x00first_string"); - $this->assertEquals(false, $this->reader->readPayload(function (PacketPayloadReader $fragmentReader) { + $this->assertEquals(false, $this->reader->readPayload(function (PayloadReader $fragmentReader) { $fragmentReader->readNullTerminatedString(); })); } @@ -266,7 +266,7 @@ public function readsStringThatRepresentsCompletePacket() { $this->reader->append("\x0a\x00\x00\x00This is 10\x00\x00\x00\x00"); - $this->assertEquals('This is 10', $this->readPayload(function (PacketPayloadReader $payloadReader) { + $this->assertEquals('This is 10', $this->readPayload(function (PayloadReader $payloadReader) { return $payloadReader->readRestOfPacketString(); })); } @@ -276,7 +276,7 @@ public function readsStringThatRepresentsRemainderOfThePacket() { $this->reader->append("\x0C\x00\x00\x00\x01\x02This is 10\x00\x00\x00\x00"); - $this->assertEquals('This is 10', $this->readPayload(function (PacketPayloadReader $payloadReader) { + $this->assertEquals('This is 10', $this->readPayload(function (PayloadReader $payloadReader) { $payloadReader->readFixedInteger(1); $payloadReader->readFixedInteger(1); return $payloadReader->readRestOfPacketString(); @@ -295,7 +295,7 @@ public function readMultiplePacketsPacketStringsAddedAsSingleNetworkPacketInSing 'three', 'four' ], - $this->readPayload(function (PacketPayloadReader $payloadReader) { + $this->readPayload(function (PayloadReader $payloadReader) { return [ $payloadReader->readRestOfPacketString(), $payloadReader->readRestOfPacketString(), @@ -312,7 +312,7 @@ public function readMultiplePacketsPacketStringsAddedAsSingleNetworkPacketInMult $this->reader->append("\x03\x00\x00\x00one\x03\x00\x00\x00two\x05\x00\x00\x00three\x04\x00\x00\x00four"); - $readString = function (PacketPayloadReader $payloadReader) { + $readString = function (PayloadReader $payloadReader) { return $payloadReader->readRestOfPacketString(); }; @@ -339,7 +339,7 @@ public function providesSequenceNumberAndPacketLengthDuringReadingOfPayload() ->append("\x08\x00\x00\x00one\x00two\x00\x06\x00\x00\x01three\x00\x05\x00\x00\x05four\x00"); - $readString = function (PacketPayloadReader $payloadReader, int $length, int $sequence) { + $readString = function (PayloadReader $payloadReader, int $length, int $sequence) { $payloadReader->readNullTerminatedString(); return [$length, $sequence]; From 6fac13f7b4bb230b8da2dde9ff380cb06b14450b Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Fri, 28 Jun 2019 16:12:42 +0200 Subject: [PATCH 20/23] Remove redundant code from packet reader --- src/UncompressedPacketReader.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/UncompressedPacketReader.php b/src/UncompressedPacketReader.php index 4e5a709..96342ac 100644 --- a/src/UncompressedPacketReader.php +++ b/src/UncompressedPacketReader.php @@ -11,13 +11,6 @@ class UncompressedPacketReader implements PacketReader { - private const INTEGER_LENGTH_MARKER = [ - 0xfc => 2, - 0xfd => 3, - 0xfe => 8 - ]; - - private const UNREAD_LENGTH = 2; private const LENGTH = 0; private const SEQUENCE = 1; From 1b039db0345e860a4a472efa6a9f83eee563eded Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Fri, 28 Jun 2019 16:13:39 +0200 Subject: [PATCH 21/23] Add @throws into PayloadReader interface --- src/PayloadReader.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/PayloadReader.php b/src/PayloadReader.php index 05bd7db..83ee3b4 100644 --- a/src/PayloadReader.php +++ b/src/PayloadReader.php @@ -19,6 +19,8 @@ interface PayloadReader * * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_integers.html * @return int|float + * + * @throws IncompleteBufferException */ public function readFixedInteger(int $bytes); @@ -27,6 +29,8 @@ public function readFixedInteger(int $bytes); * * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_integers.html * @return int|float + * + * @throws IncompleteBufferException */ public function readLengthEncodedIntegerOrNull(); @@ -34,6 +38,9 @@ public function readLengthEncodedIntegerOrNull(); * Reads string of specified length from buffer * * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html + * + * + * @throws IncompleteBufferException */ public function readFixedString(int $length): string; @@ -41,6 +48,8 @@ public function readFixedString(int $length): string; * Reads string that is has length as the first part of the fragment * * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html + * + * @throws IncompleteBufferException */ public function readLengthEncodedStringOrNull(): ?string; @@ -48,6 +57,8 @@ public function readLengthEncodedStringOrNull(): ?string; * Reads string till x00 character * * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html + * + * @throws IncompleteBufferException */ public function readNullTerminatedString(): string; @@ -56,6 +67,8 @@ public function readNullTerminatedString(): string; * Reads string that is rest of payload * * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html + * + * @throws IncompleteBufferException */ public function readRestOfPacketString(): string; } From 283e7c9e357d01aad30383d90cff43ee0c5a7d74 Mon Sep 17 00:00:00 2001 From: Ivan Chepurnyi Date: Fri, 28 Jun 2019 16:15:35 +0200 Subject: [PATCH 22/23] Add idea for frame parser and frame interfaces for FSM implementation --- src/Frame.php | 14 ++++++++++++++ src/FrameParser.php | 21 +++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/Frame.php create mode 100644 src/FrameParser.php diff --git a/src/Frame.php b/src/Frame.php new file mode 100644 index 0000000..0a365ad --- /dev/null +++ b/src/Frame.php @@ -0,0 +1,14 @@ + Date: Mon, 22 Jul 2019 17:47:46 +0200 Subject: [PATCH 23/23] Add buffer reader factory method for creating it from text --- src/BufferPayloadReaderFactory.php | 22 ++++++++++++++++++++-- src/UncompressedPacketReader.php | 2 +- tests/BufferPayloadReaderTest.php | 2 +- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/BufferPayloadReaderFactory.php b/src/BufferPayloadReaderFactory.php index 890e009..768306a 100644 --- a/src/BufferPayloadReaderFactory.php +++ b/src/BufferPayloadReaderFactory.php @@ -11,8 +11,26 @@ class BufferPayloadReaderFactory { - public function create(ReadBuffer $buffer, array $unreadPacketLength): BufferPayloadReader + /** + * @var BinaryIntegerReader + */ + private $binaryIntegerReader; + + public function __construct(BinaryIntegerReader $binaryIntegerReader = null) + { + $this->binaryIntegerReader = $binaryIntegerReader ?? new BinaryIntegerReader(); + } + + public function createFromBuffer(ReadBuffer $buffer, array $unreadPacketLength): BufferPayloadReader { - return new BufferPayloadReader($buffer, $unreadPacketLength, new BinaryIntegerReader()); + return new BufferPayloadReader($buffer, $unreadPacketLength, $this->binaryIntegerReader); + } + + public function createFromString(string $data): BufferPayloadReader + { + $buffer = new ReadBuffer(); + $buffer->append($data); + + return $this->createFromBuffer($buffer, [strlen($data)]); } } diff --git a/src/UncompressedPacketReader.php b/src/UncompressedPacketReader.php index 96342ac..1fe9d85 100644 --- a/src/UncompressedPacketReader.php +++ b/src/UncompressedPacketReader.php @@ -74,7 +74,7 @@ public function readPayload(callable $reader): bool { try { $reader( - $this->payloadReaderFactory->create($this->readBuffer, $this->remainingPacketLength), + $this->payloadReaderFactory->createFromBuffer($this->readBuffer, $this->remainingPacketLength), $this->packets[0][self::LENGTH], $this->packets[0][self::SEQUENCE] ); diff --git a/tests/BufferPayloadReaderTest.php b/tests/BufferPayloadReaderTest.php index 9d18d44..a2a4de8 100644 --- a/tests/BufferPayloadReaderTest.php +++ b/tests/BufferPayloadReaderTest.php @@ -312,6 +312,6 @@ private function createPayloadReader(string $payload, int ...$packetLength): Pay $buffer = new ReadBuffer(); $buffer->append($payload); $packetLength = $packetLength ?: [strlen($payload)]; - return $this->payloadReaderFactory->create($buffer, $packetLength); + return $this->payloadReaderFactory->createFromBuffer($buffer, $packetLength); } }