From 55981e8de97e52bcbe9b0741e7858259fca1d88c Mon Sep 17 00:00:00 2001
From: Ivan Chepurnyi <ivan.chepurnyi@ecomdev.org>
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/php-mysql-binary-protocol.iml" filepath="$PROJECT_DIR$/.idea/php-mysql-binary-protocol.iml" />
+    </modules>
+  </component>
+</project>
\ 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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="EcomDev\MySQLBinaryProtocol" />
+      <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="EcomDev\MySQLBinaryProtocol" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
\ 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 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+interface ReadBuffer
+{
+    /**
+     * Appends data into buffer
+     *
+     * @return void
+     */
+    public function append(string $data): void;
+
+    /**
+     * Executes callable to read a fragment of the buffer
+     *
+     * In case of IncompleteBufferException is thrown during reading,
+     * method returns false, otherwise buffer is advanced
+     *
+     * @param callable
+     * @return boolean
+     */
+    public function readFragment(callable $reader): bool;
+
+    /**
+     * Checks if complete packed has been added to the buffer.
+     */
+    public function isCompletePacket(): bool;
+}

From 043a11a22fcfb407ed51233beb3a92776576a175 Mon Sep 17 00:00:00 2001
From: Ivan Chepurnyi <ivan.chepurnyi@ecomdev.org>
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 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+
+class InMemoryReadBuffer implements ReadBuffer
+{
+    /** @var string */
+    private $buffer = '';
+
+    /** @var int */
+    private $bufferSize = 0;
+
+    /**
+     * {@inheritDoc}
+     */
+    public function append(string $data): void
+    {
+        $this->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 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+
+class IncompleteBufferException extends \LengthException
+{
+
+}
diff --git a/src/ReadBuffer.php b/src/ReadBuffer.php
index fe68142..a8faead 100644
--- a/src/ReadBuffer.php
+++ b/src/ReadBuffer.php
@@ -12,8 +12,6 @@ interface ReadBuffer
 {
     /**
      * Appends data into buffer
-     *
-     * @return void
      */
     public function append(string $data): void;
 
@@ -21,15 +19,20 @@ public function append(string $data): void;
      * Executes callable to read a fragment of the buffer
      *
      * In case of IncompleteBufferException is thrown during reading,
-     * method returns false, otherwise buffer is advanced
+     * method returns false and same data will be returned on the next read.
      *
-     * @param callable
-     * @return boolean
+     * The code of $reader MUST NOT catch this exception
      */
     public function readFragment(callable $reader): bool;
 
     /**
-     * Checks if complete packed has been added to the buffer.
+     * Checks if current packet readable till the end
+     */
+    public function isFullPacket(): bool;
+
+    /**
+     * Moves internal pointer to start reading next packet,
+     * ignoring any left data in current one
      */
-    public function isCompletePacket(): bool;
+    public function nextPacket(): void;
 }
diff --git a/src/ReadBufferFragment.php b/src/ReadBufferFragment.php
new file mode 100644
index 0000000..20ce1cd
--- /dev/null
+++ b/src/ReadBufferFragment.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+/**
+ * Reader for buffer fragments
+ *
+ */
+interface ReadBufferFragment
+{
+    /**
+     * Reads fixed value integer
+     *
+     * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_integers.html
+     */
+    public function readFixedInteger(int $bytes): int;
+
+    /**
+     * Reads length encoded integer
+     *
+     * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_integers.html
+     */
+    public function readLengthEncodedInteger(): int;
+
+    /**
+     * 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;
+
+    /**
+     * 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;
+
+    /**
+     * 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;
+
+
+    /**
+     * 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;
+}
diff --git a/tests/InMemoryReadBufferTest.php b/tests/InMemoryReadBufferTest.php
new file mode 100644
index 0000000..548a22e
--- /dev/null
+++ b/tests/InMemoryReadBufferTest.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+use PHPUnit\Framework\TestCase;
+
+class InMemoryReadBufferTest extends TestCase
+{
+    /**
+     * @var ReadBuffer
+     */
+    private $buffer;
+
+    protected function setUp(): void
+    {
+        $this->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 <ivan.chepurnyi@ecomdev.org>
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 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+/**
+ * @internal
+ */
+class BinaryIntegerReader
+{
+    public function readFixed(string $binary, int $size)
+    {
+        $result = ord($binary);
+
+        if ($size === 1) {
+            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))
+            );
+        }
+
+        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 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+use PHPUnit\Framework\TestCase;
+
+class BinaryIntegerReaderTest extends TestCase
+{
+    /**
+     * @var BinaryIntegerReader
+     */
+    private $reader;
+
+    protected function setUp(): void
+    {
+        $this->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 <ivan.chepurnyi@ecomdev.org>
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 <ivan.chepurnyi@ecomdev.org>
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 <ivan.chepurnyi@ecomdev.org>
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit
+        colors                      = "true"
+        convertErrorsToExceptions   = "true"
+        convertNoticesToExceptions  = "true"
+        convertWarningsToExceptions = "true"
+        bootstrap                   = "vendor/autoload.php">
+
+    <testsuites>
+        <testsuite name="Unit Tests">
+            <directory suffix="Test.php">tests/</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist>
+            <directory>src/</directory>
+        </whitelist>
+    </filter>
+
+</phpunit>
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 <ivan.chepurnyi@ecomdev.org>
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 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+
+class StringReadBufferFactory
+{
+    public function createWithDefaultSettings(): StringReadBuffer
+    {
+        return new StringReadBuffer(new BinaryIntegerReader());
+    }
+}
diff --git a/tests/StringReadBufferTest.php b/tests/StringReadBufferTest.php
index cea8540..19a4817 100644
--- a/tests/StringReadBufferTest.php
+++ b/tests/StringReadBufferTest.php
@@ -19,7 +19,7 @@ class StringReadBufferTest extends TestCase
 
     protected function setUp(): void
     {
-        $this->buffer = new StringReadBuffer();
+        $this->buffer = (new StringReadBufferFactory())->createWithDefaultSettings();
     }
 
     /**

From 0bf777b85c906dafc0972058860aab1a879207ee Mon Sep 17 00:00:00 2001
From: Ivan Chepurnyi <ivan.chepurnyi@ecomdev.org>
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 <ivan.chepurnyi@ecomdev.org>
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 <ivan.chepurnyi@ecomdev.org>
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 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+
+class DefaultPacketReaderFactory
+{
+    public function createWithDefaultSettings(): StringPacketReader
+    {
+        return new StringPacketReader(new BinaryIntegerReader(), new ReadBuffer());
+    }
+}
diff --git a/src/ReadBufferFragment.php b/src/PacketFragmentReader.php
similarity index 98%
rename from src/ReadBufferFragment.php
rename to src/PacketFragmentReader.php
index 20ce1cd..72971ef 100644
--- a/src/ReadBufferFragment.php
+++ b/src/PacketFragmentReader.php
@@ -12,7 +12,7 @@
  * Reader for buffer fragments
  *
  */
-interface ReadBufferFragment
+interface PacketFragmentReader
 {
     /**
      * Reads fixed value integer
diff --git a/src/PacketReader.php b/src/PacketReader.php
new file mode 100644
index 0000000..2f6d097
--- /dev/null
+++ b/src/PacketReader.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+interface PacketReader
+{
+    /**
+     * Appends data into buffer
+     */
+    public function append(string $data): void;
+
+    /**
+     * 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.
+     *
+     * 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;
+}
diff --git a/src/ReadBuffer.php b/src/ReadBuffer.php
index bf30b71..72b2188 100644
--- a/src/ReadBuffer.php
+++ b/src/ReadBuffer.php
@@ -8,32 +8,68 @@
 
 namespace EcomDev\MySQLBinaryProtocol;
 
-interface ReadBuffer
+use function strlen;
+use function substr;
+
+class ReadBuffer
 {
-    /**
-     * Appends data into buffer
-     */
-    public function append(string $data): void;
+    const ONE_MEGABYTE = 1024*1024;
 
     /**
-     * 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.
-     *
-     * The code of $reader MUST NOT catch this exception
+     * @var string
      */
-    public function readFragment(callable $reader): bool;
+    private $buffer = '';
 
-    /**
-     * Checks if current packet readable completely to the end
-     */
-    public function isFullPacket(): bool;
+    /** @var int */
+    private $currentPosition = 0;
+
+    /** @var int */
+    private $readPosition = 0;
 
     /**
-     * Moves internal pointer to start reading next packet
-     *
-     * Any data that is left unread in the packet will be discarded
+     * @var int
      */
-    public function nextPacket(): void;
+    private $bufferSize;
+
+    public function __construct(int $bufferSize = self::ONE_MEGABYTE)
+    {
+        $this->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 @@
-<?php
-/**
- * Copyright © EcomDev B.V. All rights reserved.
- * See LICENSE for license details.
- */
-
-declare(strict_types=1);
-
-namespace EcomDev\MySQLBinaryProtocol;
-
-
-class StringReadBufferFactory
-{
-    public function createWithDefaultSettings(): StringReadBuffer
-    {
-        return new StringReadBuffer(new BinaryIntegerReader());
-    }
-}
diff --git a/tests/PacketReaderTest.php b/tests/PacketReaderTest.php
new file mode 100644
index 0000000..87de1d4
--- /dev/null
+++ b/tests/PacketReaderTest.php
@@ -0,0 +1,115 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+use PHPUnit\Framework\TestCase;
+
+class PacketReaderTest extends TestCase
+{
+    /**
+     * @var PacketReader
+     */
+    private $reader;
+
+    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) {
+            $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 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+
+use PHPUnit\Framework\TestCase;
+
+class ReadBufferTest extends TestCase
+{
+    /** @var ReadBuffer */
+    private $readBuffer;
+
+    protected function setUp(): void
+    {
+        $this->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 @@
-<?php
-/**
- * Copyright © EcomDev B.V. All rights reserved.
- * See LICENSE for license details.
- */
-
-declare(strict_types=1);
-
-namespace EcomDev\MySQLBinaryProtocol;
-
-use PHPUnit\Framework\TestCase;
-
-class StringReadBufferTest extends TestCase
-{
-    /**
-     * @var ReadBuffer
-     */
-    private $buffer;
-
-    protected function setUp(): void
-    {
-        $this->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 <ivan.chepurnyi@ecomdev.org>
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 <ivan.chepurnyi@ecomdev.org>
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 <ivan.chepurnyi@ecomdev.org>
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 <ivan.chepurnyi@ecomdev.org>
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 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+class InvalidBinaryDataException extends \RuntimeException
+{
+
+}
diff --git a/src/PacketFragmentReader.php b/src/PacketFragmentReader.php
index 72971ef..7ba7a8a 100644
--- a/src/PacketFragmentReader.php
+++ b/src/PacketFragmentReader.php
@@ -18,15 +18,17 @@ interface PacketFragmentReader
      * Reads fixed value integer
      *
      * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_integers.html
+     * @return int|float
      */
-    public function readFixedInteger(int $bytes): int;
+    public function readFixedInteger(int $bytes);
 
     /**
      * Reads length encoded integer
      *
      * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_integers.html
+     * @return int|float
      */
-    public function readLengthEncodedInteger(): int;
+    public function readLengthEncodedIntegerOrNull();
 
     /**
      * Reads string of specified length from buffer
diff --git a/src/StringPacketReader.php b/src/StringPacketReader.php
index ffb0462..f816abf 100644
--- a/src/StringPacketReader.php
+++ b/src/StringPacketReader.php
@@ -11,6 +11,11 @@
 
 class StringPacketReader implements PacketReader, PacketFragmentReader
 {
+    const INTEGER_LENGTH_MARKER = [
+        0xfc => 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 <ivan.chepurnyi@ecomdev.org>
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 <ivan.chepurnyi@ecomdev.org>
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 <ivan.chepurnyi@ecomdev.org>
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 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+/**
+ * Collection of capability flags for MySQL communication
+ *
+ * @see https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags
+ */
+class CapabilityFlags
+{
+    public const CLIENT_LONG_PASSWORD = 0x01;
+    public const CLIENT_FOUND_ROWS = 0x02;
+    public const CLIENT_LONG_FLAG = 0x04;
+    public const CLIENT_CONNECT_WITH_DB = 0x08;
+    public const CLIENT_NO_SCHEMA = 0x10;
+    public const CLIENT_COMPRESS = 0x20;
+    public const CLIENT_IGNORE_SPACE = 0x0100;
+    public const CLIENT_PROTOCOL_41 = 0x0200;
+    public const CLIENT_INTERACTIVE = 0x0400;
+    public const CLIENT_SSL = 0x0800;
+    public const CLIENT_TRANSACTIONS = 0x2000;
+    public const CLIENT_SECURE_CONNECTION = 0x8000;
+    public const CLIENT_MULTI_STATEMENTS = 0x010000;
+    public const CLIENT_MULTI_RESULTS = 0x020000;
+    public const CLIENT_PS_MULTI_RESULTS = 0x040000;
+    public const CLIENT_PLUGIN_AUTH = 0x080000;
+    public const CLIENT_CONNECT_ATTRS = 0x100000;
+    public const CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA = 0x200000;
+    public const CLIENT_SESSION_TRACK = 0x800000;
+    public const CLIENT_DEPRECATE_EOF = 0x01000000;
+}
diff --git a/src/StatusFlags.php b/src/StatusFlags.php
new file mode 100644
index 0000000..4ac0217
--- /dev/null
+++ b/src/StatusFlags.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+/**
+ * Collection of server status flags sent in OK packet
+ *
+ * @see https://dev.mysql.com/doc/internals/en/status-flags.html
+ */
+class StatusFlags
+{
+    public const SERVER_STATUS_IN_TRANS = 0x01;
+    public const SERVER_STATUS_AUTOCOMMIT = 0x02;
+    public const SERVER_MORE_RESULTS_EXISTS = 0x08;
+    public const SERVER_STATUS_NO_GOOD_INDEX_USED = 0x10;
+    public const SERVER_STATUS_NO_INDEX_USED = 0x20;
+    public const SERVER_STATUS_CURSOR_EXISTS = 0x40;
+    public const SERVER_STATUS_LAST_ROW_SENT = 0x80;
+    public const SERVER_STATUS_DB_DROPPED = 0x0100;
+    public const SERVER_STATUS_NO_BACKSLASH_ESCAPES = 0x0200;
+    public const SERVER_STATUS_METADATA_CHANGED = 0x0400;
+    public const SERVER_QUERY_WAS_SLOW = 0x0800;
+    public const SERVER_PS_OUT_PARAMS = 0x1000;
+    public const SERVER_STATUS_IN_TRANS_READONLY = 0x2000;
+    public const SERVER_SESSION_STATE_CHANGED = 0x4000;
+}

From 304cfa9ec62b1b97c253e69eb4b26a0a3c6e492f Mon Sep 17 00:00:00 2001
From: Ivan Chepurnyi <ivan.chepurnyi@ecomdev.org>
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 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+
+class CharsetIdentifiers
+{
+    public const UTF8 = 33;
+    public const LATIN1 = 8;
+    public const UTF8MB4 = 255;
+}
diff --git a/src/Frame/HandshakeV10.php b/src/Frame/HandshakeV10.php
new file mode 100644
index 0000000..891d971
--- /dev/null
+++ b/src/Frame/HandshakeV10.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol\Frame;
+
+
+class HandshakeV10
+{
+    public $serverVersion;
+
+    public $connectionId;
+
+    public $authData;
+
+    public $capabilities;
+
+    public $charset;
+
+    public $status;
+
+    public $authPlugin;
+
+    public function __construct(
+        string $serverVersion,
+        int $connectionId,
+        string $authData,
+        int $capabilities,
+        int $charset = 0,
+        int $status = 0,
+        string $authPlugin = ''
+    )
+    {
+        $this->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 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol\Frame;
+
+
+class HandshakeV10Builder
+{
+
+    /**
+     * @var string
+     */
+    private $serverVersion;
+    /**
+     * @var int
+     */
+    private $clientId;
+
+    /**
+     * @var string
+     */
+    private $authData;
+
+    /**
+     * @var int
+     */
+    private $capabilities = 0;
+
+    /**
+     * @var int
+     */
+    private $charset = 0;
+
+    /**
+     * @var int
+     */
+    private $status = 0;
+
+    /**
+     * @var string
+     */
+    private $authPlugin = '';
+
+    public function withServerInfo(string $serverVersion, int $clientId): self
+    {
+        $builder = clone $this;
+        $builder->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 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+
+use EcomDev\MySQLBinaryProtocol\Frame\HandshakeV10Builder;
+
+class HandshakeParser
+{
+
+    /**
+     * @var HandshakeV10Builder
+     */
+    private $frameBuilder;
+    /**
+     * @var callable
+     */
+    private $frameReceiver;
+
+    public function __construct(HandshakeV10Builder $frameBuilder, callable $frameReceiver)
+    {
+
+        $this->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 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol\Frame;
+
+
+use EcomDev\MySQLBinaryProtocol\CapabilityFlags;
+use EcomDev\MySQLBinaryProtocol\CharsetIdentifiers;
+use EcomDev\MySQLBinaryProtocol\StatusFlags;
+use PHPUnit\Framework\TestCase;
+
+class HandshakeBuilderV10Test extends TestCase
+{
+    /** @var HandshakeV10Builder */
+    private $builder;
+
+    protected function setUp(): void
+    {
+        $this->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 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+
+use EcomDev\MySQLBinaryProtocol\Frame\HandshakeV10;
+use EcomDev\MySQLBinaryProtocol\Frame\HandshakeV10Builder;
+use PHPUnit\Framework\TestCase;
+
+class HandshakeParserTest extends TestCase
+{
+    /** @var HandshakeV10Builder */
+    private $frameBuilder;
+
+    /**
+     * @var PacketReader
+     */
+    private $packetReader;
+
+    /**
+     * @var HandshakeParser
+     */
+    private $parser;
+
+    /**
+     * @var HandshakeV10[]
+     */
+    private $frames = [];
+
+    protected function setUp(): void
+    {
+        $this->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 <ivan.chepurnyi@ecomdev.org>
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 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+
+class BufferPayloadReader implements PayloadReader
+{
+    private const LENGTH_MARKERS = [
+        0xfc => 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 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+
+class BufferPayloadReaderFactory
+{
+    public function create(ReadBuffer $buffer, array $unreadPacketLength): BufferPayloadReader
+    {
+        return new BufferPayloadReader($buffer, $unreadPacketLength, new BinaryIntegerReader());
+    }
+}
diff --git a/src/DefaultPacketReaderFactory.php b/src/DefaultPacketReaderFactory.php
index 67409c0..e14b91a 100644
--- a/src/DefaultPacketReaderFactory.php
+++ b/src/DefaultPacketReaderFactory.php
@@ -13,6 +13,10 @@ class DefaultPacketReaderFactory
 {
     public function createWithDefaultSettings(): UncompressedPacketReader
     {
-        return new UncompressedPacketReader(new BinaryIntegerReader(), new ReadBuffer());
+        return new UncompressedPacketReader(
+            new BinaryIntegerReader(),
+            new ReadBuffer(),
+            new BufferPayloadReaderFactory()
+        );
     }
 }
diff --git a/src/HandshakeParser.php b/src/HandshakeParser.php
index a75942f..a6b8813 100644
--- a/src/HandshakeParser.php
+++ b/src/HandshakeParser.php
@@ -30,7 +30,7 @@ public function __construct(HandshakeV10Builder $frameBuilder, callable $frameRe
         $this->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 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+use PHPUnit\Framework\TestCase;
+
+class BufferPayloadReaderTest extends TestCase
+{
+    /** @var BufferPayloadReaderFactory */
+    private $payloadReaderFactory;
+
+    /** @var ReadBuffer */
+    private $buffer;
+
+    protected function setUp(): void
+    {
+        $this->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 <ivan.chepurnyi@ecomdev.org>
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 <ivan.chepurnyi@ecomdev.org>
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 <ivan.chepurnyi@ecomdev.org>
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 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+interface Frame
+{
+
+}
diff --git a/src/FrameParser.php b/src/FrameParser.php
new file mode 100644
index 0000000..e124b07
--- /dev/null
+++ b/src/FrameParser.php
@@ -0,0 +1,21 @@
+<?php
+/**
+ * Copyright © EcomDev B.V. All rights reserved.
+ * See LICENSE for license details.
+ */
+
+declare(strict_types=1);
+
+namespace EcomDev\MySQLBinaryProtocol;
+
+
+interface FrameParser
+{
+    /**
+     * Parses frames from payload reader
+     *
+     * @throws InvalidBinaryDataException
+     * @throws IncompleteBufferException
+     */
+    public function parse(PayloadReader $payload, int $length, int $sequenceNumber): Frame;
+}

From f89f87b3367ed0f66b63a91e73d4575d425d0531 Mon Sep 17 00:00:00 2001
From: Ivan Chepurnyi <ivan.chepurnyi@ecomdev.org>
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);
     }
 }
