diff --git a/.github/workflows/label-issue.yml b/.github/workflows/label-issue.yml
new file mode 100644
index 0000000..47320bb
--- /dev/null
+++ b/.github/workflows/label-issue.yml
@@ -0,0 +1,22 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: Label issues
+on:
+  issues:
+    types:
+      - reopened
+      - opened
+jobs:
+  label_issues:
+    runs-on: ubuntu-latest
+    permissions:
+      issues: write
+    steps:
+      - name: Label issues
+        uses: andymckay/labeler@5c59dabdfd4dd5bd9c6e6d255b01b9d764af4414
+        with:
+          add-labels: "Status: Available"
+          repo-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/lint-python.yml b/.github/workflows/lint-python.yml
new file mode 100644
index 0000000..548038f
--- /dev/null
+++ b/.github/workflows/lint-python.yml
@@ -0,0 +1,21 @@
+name: lint_python
+on: [pull_request, push]
+jobs:
+  lint_python:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/setup-python@v2
+      - run: pip install bandit black codespell flake8 isort mypy pytest pyupgrade safety
+      - run: bandit --recursive --skip B101,B105,B106,B110,B303,B404,B603 .
+      - run: black --check . || true
+      - run: codespell || true  # --ignore-words-list="" --skip=""
+      - run: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
+      - run: flake8 . --count --exit-zero --max-complexity=29 --max-line-length=167 --show-source --statistics
+      - run: isort --check-only --profile black .
+      - run: pip install -e .
+      - run: mypy --ignore-missing-imports . || true
+      - run: mv setup.cfg setup.cfg.disabled
+      - run: pytest .
+      - run: shopt -s globstar && pyupgrade --py36-plus **/*.py || true
+      - run: safety check
diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml
new file mode 100644
index 0000000..37186bf
--- /dev/null
+++ b/.github/workflows/tox.yml
@@ -0,0 +1,24 @@
+name: tox
+on: [push, pull_request]
+jobs:
+  tox:
+    strategy:
+      fail-fast: false
+      max-parallel: 4
+      matrix:
+        python: [3.6, 3.7, 3.8, 3.9]
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python }}
+      - run: pip install tox
+      - if: matrix.python == '3.6'
+        run: TOXENV=py36 tox
+      - if: matrix.python == '3.7'
+        run: TOXENV=py37 tox
+      - if: matrix.python == '3.8'
+        run: TOXENV=py38 tox
+      - if: matrix.python == '3.9'
+        run: TOXENV=py39 tox
diff --git a/.gitignore b/.gitignore
index 4dbb7ea..84665a2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,6 +45,10 @@ nosetests.xml
 coverage.xml
 *,cover
 .hypothesis/
+test/ssl/demoCA
+test/ssl/rootCA
+test/ssl/signingCA
+*.csr
 
 # Translations
 *.mo
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 3a4c46b..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-language: python
-
-jobs:
-  include:
-    - python: 3.8
-      env: TOXENV=py38
-    - python: 3.7
-      env: TOXENV=py37
-    - python: 3.6
-      env: TOXENV=py36
-    - python: 3.5
-      env: TOXENV=py35
-    - python: 2.7
-      env: TOXENV=py27
-
-install:
-  - git clone https://github.com/eclipse/paho.mqtt.testing.git || true
-  - pip install tox
-
-script:
-  - tox
diff --git a/ChangeLog.txt b/ChangeLog.txt
index c6c5f19..ab57c1a 100644
--- a/ChangeLog.txt
+++ b/ChangeLog.txt
@@ -1,4 +1,54 @@
-v1.5.1 - 2020x-09-22
+v1.6.1 - 2021-10-21
+===================
+
+- Fix Python 2.7 compatilibity.
+
+
+v1.6.0 - 2021-10-20
+===================
+
+- Changed default TLS version to 1.2 instead of 1.0.
+- Fix incoming MQTT v5 messages with overall property length > 127 bytes being
+  incorrectly decoded. Closes #541.
+- MQTTMessageInfo.wait_for_publish() and MQTTMessageInfo.is_published() will
+  now raise exceptions if called when the publish call produced an error.
+  Closes #550.
+- Remove periodic retry checks for outgoing messages with QoS>0. This means
+  that outgoing messages will only be retried on the client reconnecting to
+  the server. They will *not* be retried when the client is still connected.
+- The `rc` parameter in the `on_disconnect` callback now has meaningful values
+  in the case of an error. Closes #441.
+- Callbacks can now be applied to client instances using decorators.
+- PUBACK messages are now sent to the broker only after the on_message
+  callback has returned.
+- Raise exceptions when attempting to set MQTT v5 properties to forbidden
+  values. Closes #586.
+- Callbacks can now be updated from within a callback.
+- Remove _out_packet_mutex and _current_out_packet_mutex and convert the
+  _out_packet queue use to thread safe.
+- Add basic MQTT v5 support to the subscribe and publish helper functions.
+  Closes #575.
+- Fix on_disconnect() sometimes calling the MQTT v3.x callback when it should
+  call the MQTT v5 callback. Closes #570.
+- Big performance improvement when receiving large payloads, particularly for
+  SSL. Closes #571,
+- Fix connecting with MQTT v5 to a broker that doesn't support MQTT v5.
+  Closes #566.
+- Removed ancient Mosquitto compatibility class.
+- Fix exception on calling Client(client_id="", clean_session=False).
+  Closes #520.
+- Experimental support for Websockets continuation frames. Closes #500.
+  Closes #89.
+- `Properties.json()` now converts Correlation Data bytes() objects to hex.
+  Closes #555.
+- Only use the internal sockpair wakeup when running with loop_start() or
+  loop(). This removes problems when running with an external event loop.
+- Drain all of sockpairR bytes to avoid unnecessary wakeups and possible
+  timeouts. Closes #563.
+- Add timeout to MQTTMessageInfo:wait_for_publish().
+
+
+v1.5.1 - 2020-09-22
 ===================
 
 - Exceptions that occur in callbacks are no longer suppressed by default. They
diff --git a/LICENSE.txt b/LICENSE.txt
index 5cc0225..7cb8ad9 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,3 +1,3 @@
-This project is dual licensed under the Eclipse Public License 1.0 and the
-Eclipse Distribution License 1.0 as described in the epl-v10 and edl-v10 files.
+This project is dual licensed under the Eclipse Public License 2.0 and the
+Eclipse Distribution License 1.0 as described in the epl-v20 and edl-v10 files.
 
diff --git a/Makefile b/Makefile
index 3e4f672..16cc917 100644
--- a/Makefile
+++ b/Makefile
@@ -1,13 +1,14 @@
 # Set DESTDIR if it isn't given
 DESTDIR?=/
+PYTHON?=python3
 
 .PHONY : all clean clean-build clean-pyc clean-test install test upload
 
 all :
-	python ./setup.py build
+	$(PYTHON) ./setup.py build
 
 install : all
-	python ./setup.py install --root=${DESTDIR}
+	$(PYTHON) ./setup.py install --root=${DESTDIR}
 
 clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
 
@@ -31,8 +32,8 @@ clean-test: ## remove test and coverage artifacts
 	rm -fr htmlcov/
 
 test :
-	python setup.py test
+	$(PYTHON) setup.py test
 	$(MAKE) -C test test
 
 upload : test
-	python ./setup.py sdist upload
+	$(PYTHON) ./setup.py sdist upload
diff --git a/README.rst b/README.rst
index 4cc37d0..e060cef 100644
--- a/README.rst
+++ b/README.rst
@@ -5,7 +5,7 @@ This document describes the source code for the `Eclipse Paho <http://eclipse.or
 
 This code provides a client class which enable applications to connect to an `MQTT <http://mqtt.org/>`_ broker to publish messages, and to subscribe to topics and receive published messages. It also provides some helper functions to make publishing one off messages to an MQTT server very straightforward.
 
-It supports Python 2.7.9+ or 3.5+.
+It supports Python 2.7.9+ or 3.6+.
 
 The MQTT protocol is a machine-to-machine (M2M)/"Internet of Things" connectivity protocol. Designed as an extremely lightweight publish/subscribe messaging transport, it is useful for connections with remote locations where a small code footprint is required and/or network bandwidth is at a premium.
 
@@ -135,7 +135,7 @@ Here is a very simple example that subscribes to the broker $SYS topic tree and
     client.on_connect = on_connect
     client.on_message = on_message
 
-    client.connect("mqtt.eclipse.org", 1883, 60)
+    client.connect("mqtt.eclipseprojects.io", 1883, 60)
 
     # Blocking call that processes network traffic, dispatches callbacks and
     # handles reconnecting.
@@ -191,7 +191,7 @@ userdata
 
 protocol
     the version of the MQTT protocol to use for this client. Can be either
-    ``MQTTv31`` or ``MQTTv311``
+    ``MQTTv31``, ``MQTTv311`` or ``MQTTv5``
 
 transport
     set to "websockets" to send MQTT over WebSockets. Leave at the default of
@@ -249,7 +249,7 @@ max_queued_messages_set()
 
 Set the maximum number of outgoing messages with QoS>0 that can be pending in the outgoing message queue.
 
-Defaults to 0. 0 means unlimited. When the queue is full, any further outgoing messages would be dropped.
+Defaults to 0. 0 means unlimited, but due to implementation currently limited to 65555 (65535 messages in queue + 20 in flight). When the queue is full, any further outgoing messages would be dropped.
 
 message_retry_set()
 '''''''''''''''''''
@@ -291,7 +291,7 @@ tls_set()
 Configure network encryption and authentication options. Enables SSL/TLS support.
 
 ca_certs
-    a string path to the Certificate Authority certificate files that are to be treated as trusted by this client. If this is the only option given then the client will operate in a similar manner to a web browser. That is to say it will require the broker to have a certificate signed by the Certificate Authorities in ``ca_certs`` and will communicate using TLS v1, but will not attempt any form of authentication. This provides basic network encryption but may not be sufficient depending on how the broker is configured. By default, on Python 2.7.9+ or 3.4+, the default certification authority of the system is used. On older Python version this parameter is mandatory.
+    a string path to the Certificate Authority certificate files that are to be treated as trusted by this client. If this is the only option given then the client will operate in a similar manner to a web browser. That is to say it will require the broker to have a certificate signed by the Certificate Authorities in ``ca_certs`` and will communicate using TLS v1.2, but will not attempt any form of authentication. This provides basic network encryption but may not be sufficient depending on how the broker is configured. By default, on Python 2.7.9+ or 3.4+, the default certification authority of the system is used. On older Python version this parameter is mandatory.
 
 certfile, keyfile
     strings pointing to the PEM encoded client certificate and private keys respectively. If these arguments are not ``None`` then they will be used as client information for TLS based authentication. Support for this feature is broker dependent. Note that if either of these files in encrypted and needs a password to decrypt it, Python will ask for the password at the command line. It is not currently possible to define a callback to provide the password.
@@ -300,7 +300,7 @@ cert_reqs
     defines the certificate requirements that the client imposes on the broker. By default this is ``ssl.CERT_REQUIRED``, which means that the broker must provide a certificate. See the ssl pydoc for more information on this parameter.
 
 tls_version
-    specifies the version of the SSL/TLS protocol to be used. By default (if the python version supports it) the highest TLS version is detected. If unavailable, TLS v1 is used. Previous versions (all versions beginning with SSL) are possible but not recommended due to possible security problems.
+    specifies the version of the SSL/TLS protocol to be used. By default (if the python version supports it) the highest TLS version is detected. If unavailable, TLS v1.2 is used. Previous versions (all versions beginning with SSL) are possible but not recommended due to possible security problems.
 
 ciphers
     a string specifying which encryption ciphers are allowable for this connection, or ``None`` to use the defaults. See the ssl pydoc for more information.
@@ -476,7 +476,7 @@ Connect Example
 
 .. code:: python
 
-    mqttc.connect("mqtt.eclipse.org")
+    mqttc.connect("mqtt.eclipseprojects.io")
 
 connect_async()
 '''''''''''''''
@@ -613,7 +613,7 @@ Loop Start/Stop Example
 
 .. code:: python
 
-    mqttc.connect("mqtt.eclipse.org")
+    mqttc.connect("mqtt.eclipseprojects.io")
     mqttc.loop_start()
 
     while True:
@@ -681,9 +681,13 @@ Returns a MQTTMessageInfo which expose the following attributes and methods:
   ``on_publish()`` callback if it is defined. ``wait_for_publish`` may be easier
   depending on your use-case.
 * ``wait_for_publish()`` will block until the message is published. It will
-  raise ValueError if the message is not queued (rc == ``MQTT_ERR_QUEUE_SIZE``).
+  raise ValueError if the message is not queued (rc ==
+  ``MQTT_ERR_QUEUE_SIZE``), or a RuntimeError if there was an error when
+  publishing, most likely due to the client not being connected.
 * ``is_published`` returns True if the message has been published. It will
-  raise ValueError if the message is not queued (rc == ``MQTT_ERR_QUEUE_SIZE``).
+  raise ValueError if the message is not queued (rc ==
+  ``MQTT_ERR_QUEUE_SIZE``), or a RuntimeError if there was an error when
+  publishing, most likely due to the client not being connected.
 
 A ``ValueError`` will be raised if topic is ``None``, has zero length or is
 invalid (contains a wildcard), if ``qos`` is not one of 0, 1 or 2, or if the
@@ -1173,6 +1177,9 @@ broker, then disconnect with nothing else required.
 
 The two functions provided are ``single()`` and ``multiple()``.
 
+Both functions include support for MQTT v5.0, but do not currently let you
+set any properties on connection or when sending messages.
+
 Single
 ``````
 
@@ -1245,7 +1252,8 @@ tls
     Defaults to None, which indicates that TLS should not be used.
 
 protocol
-    choose the version of the MQTT protocol to use. Use either ``MQTTv31`` or ``MQTTv311``.
+    choose the version of the MQTT protocol to use. Use either ``MQTTv31``,
+    ``MQTTv311``, or ``MQTTv5`.
 
 transport
     set to "websockets" to send MQTT over WebSockets. Leave at the default of
@@ -1258,13 +1266,16 @@ Publish Single Example
 
     import paho.mqtt.publish as publish
 
-    publish.single("paho/test/single", "payload", hostname="mqtt.eclipse.org")
+    publish.single("paho/test/single", "payload", hostname="mqtt.eclipseprojects.io")
 
 Multiple
 ````````
 
 Publish multiple messages to a broker, then disconnect cleanly.
 
+This function includes support for MQTT v5.0, but does not currently let you
+set any properties on connection or when sending messages.
+
 .. code:: python
 
     multiple(msgs, hostname="localhost", port=1883, client_id="", keepalive=60,
@@ -1299,7 +1310,7 @@ Publish Multiple Example
 
     msgs = [{'topic':"paho/test/multiple", 'payload':"multiple 1"},
         ("paho/test/multiple", "multiple 2", 0, False)]
-    publish.multiple(msgs, hostname="mqtt.eclipse.org")
+    publish.multiple(msgs, hostname="mqtt.eclipseprojects.io")
 
 
 Subscribe
@@ -1310,6 +1321,9 @@ and processing of messages.
 
 The two functions provided are ``simple()`` and ``callback()``.
 
+Both functions include support for MQTT v5.0, but do not currently let you
+set any properties on connection or when subscribing.
+
 Simple
 ``````
 
@@ -1388,7 +1402,8 @@ tls
     Defaults to None, which indicates that TLS should not be used.
 
 protocol
-    choose the version of the MQTT protocol to use. Use either ``MQTTv31`` or ``MQTTv311``.
+    choose the version of the MQTT protocol to use. Use either ``MQTTv31``,
+    ``MQTTv311``, or ``MQTTv5``.
 
 
 Simple Example
@@ -1398,7 +1413,7 @@ Simple Example
 
     import paho.mqtt.subscribe as subscribe
 
-    msg = subscribe.simple("paho/test/simple", hostname="mqtt.eclipse.org")
+    msg = subscribe.simple("paho/test/simple", hostname="mqtt.eclipseprojects.io")
     print("%s %s" % (msg.topic, msg.payload))
 
 Using Callback
@@ -1447,7 +1462,7 @@ Callback Example
     def on_message_print(client, userdata, message):
         print("%s %s" % (message.topic, message.payload))
 
-    subscribe.callback(on_message_print, "paho/test/callback", hostname="mqtt.eclipse.org")
+    subscribe.callback(on_message_print, "paho/test/callback", hostname="mqtt.eclipseprojects.io")
 
 
 Reporting bugs
diff --git a/about.html b/about.html
index b183f41..48d8e2f 100644
--- a/about.html
+++ b/about.html
@@ -6,14 +6,14 @@
 <body lang="EN-US">
 <h2>About This Content</h2>
  
-<p><em>December 9, 2013</em></p>	
+<p><em>December 9, 2013</em></p>
 <h3>License</h3>
 
 <p>The Eclipse Foundation makes available all content in this plug-in ("Content").  Unless otherwise 
 indicated below, the Content is provided to you under the terms and conditions of the
-Eclipse Public License Version 1.0 ("EPL") and Eclipse Distribution License Version 1.0 ("EDL").
+Eclipse Public License Version 2.0 ("EPL") and Eclipse Distribution License Version 1.0 ("EDL").
 A copy of the EPL is available at 
-<a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a> 
+<a href="http://www.eclipse.org/legal/epl-v20.html">http://www.eclipse.org/legal/epl-v20.html</a> 
 and a copy of the EDL is available at 
 <a href="http://www.eclipse.org/org/documents/edl-v10.php">http://www.eclipse.org/org/documents/edl-v10.php</a>. 
 For purposes of the EPL, "Program" will mean the Content.</p>
diff --git a/epl-v10 b/epl-v10
deleted file mode 100644
index 190869d..0000000
--- a/epl-v10
+++ /dev/null
@@ -1,221 +0,0 @@
-Eclipse Public License - v 1.0
-
-THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
-LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
-CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
-
-
-1. DEFINITIONS
-
-"Contribution" means:
-  a) in the case of the initial Contributor, the initial code and
-     documentation distributed under this Agreement, and
-
-  b) in the case of each subsequent Contributor:
-
-     i) changes to the Program, and
-     ii) additions to the Program; 
-
-where such changes and/or additions to the Program originate from and are
-distributed by that particular Contributor. A Contribution 'originates' from a
-Contributor if it was added to the Program by such Contributor itself or anyone
-acting on such Contributor's behalf. Contributions do not include additions to
-the Program which: (i) are separate modules of software distributed in
-conjunction with the Program under their own license agreement, and (ii) are
-not derivative works of the Program.
-
-"Contributor" means any person or entity that distributes the Program.
-
-"Licensed Patents " mean patent claims licensable by a Contributor which are
-necessarily infringed by the use or sale of its Contribution alone or when
-combined with the Program.
-
-"Program" means the Contributions distributed in accordance with this Agreement.
-
-"Recipient" means anyone who receives the Program under this Agreement,
-including all Contributors.
-
-
-2. GRANT OF RIGHTS
-
-  a) Subject to the terms of this Agreement, each Contributor hereby grants
-     Recipient a non-exclusive, worldwide, royalty-free copyright license to
-     reproduce, prepare derivative works of, publicly display, publicly
-     perform, distribute and sublicense the Contribution of such Contributor,
-     if any, and such derivative works, in source code and object code form.
-
-  b) Subject to the terms of this Agreement, each Contributor hereby grants
-     Recipient a non-exclusive, worldwide, royalty-free patent license under
-     Licensed Patents to make, use, sell, offer to sell, import and otherwise
-     transfer the Contribution of such Contributor, if any, in source code and
-     object code form. This patent license shall apply to the combination of the
-     Contribution and the Program if, at the time the Contribution is added by the
-     Contributor, such addition of the Contribution causes such combination to be
-     covered by the Licensed Patents. The patent license shall not apply to any
-     other combinations which include the Contribution. No hardware per se is
-     licensed hereunder.
-
-  c) Recipient understands that although each Contributor grants the licenses
-     to its Contributions set forth herein, no assurances are provided by any
-     Contributor that the Program does not infringe the patent or other
-     intellectual property rights of any other entity. Each Contributor disclaims
-     any liability to Recipient for claims brought by any other entity based on
-     infringement of intellectual property rights or otherwise. As a condition to
-     exercising the rights and licenses granted hereunder, each Recipient hereby
-     assumes sole responsibility to secure any other intellectual property rights
-     needed, if any. For example, if a third party patent license is required to
-     allow Recipient to distribute the Program, it is Recipient's responsibility
-     to acquire that license before distributing the Program.
-
-  d) Each Contributor represents that to its knowledge it has sufficient
-     copyright rights in its Contribution, if any, to grant the copyright license
-     set forth in this Agreement.
-
-
-3. REQUIREMENTS
-
-  A Contributor may choose to distribute the Program in object code form under
-  its own license agreement, provided that:
-
-  a) it complies with the terms and conditions of this Agreement; and
-
-  b) its license agreement:
-
-     i) effectively disclaims on behalf of all Contributors all warranties and
-        conditions, express and implied, including warranties or conditions of
-        title and non-infringement, and implied warranties or conditions of
-        merchantability and fitness for a particular purpose;
-
-    ii) effectively excludes on behalf of all Contributors all liability for
-        damages, including direct, indirect, special, incidental and consequential
-        damages, such as lost profits;
-
-   iii) states that any provisions which differ from this Agreement are offered
-        by that Contributor alone and not by any other party; and
-
-    iv) states that source code for the Program is available from such
-        Contributor, and informs licensees how to obtain it in a reasonable manner
-        on or through a medium customarily used for software exchange. 
-
-  When the Program is made available in source code form:
-
-    a) it must be made available under this Agreement; and
-
-    b) a copy of this Agreement must be included with each copy of the Program. 
-
-  Contributors may not remove or alter any copyright notices contained within
-  the Program.
-
-  Each Contributor must identify itself as the originator of its Contribution,
-  if any, in a manner that reasonably allows subsequent Recipients to identify
-  the originator of the Contribution.
-
-
-4. COMMERCIAL DISTRIBUTION
-
-  Commercial distributors of software may accept certain responsibilities with
-  respect to end users, business partners and the like. While this license is
-  intended to facilitate the commercial use of the Program, the Contributor who
-  includes the Program in a commercial product offering should do so in a
-  manner which does not create potential liability for other Contributors.
-  Therefore, if a Contributor includes the Program in a commercial product
-  offering, such Contributor ("Commercial Contributor") hereby agrees to defend
-  and indemnify every other Contributor ("Indemnified Contributor") against any
-  losses, damages and costs (collectively "Losses") arising from claims,
-  lawsuits and other legal actions brought by a third party against the
-  Indemnified Contributor to the extent caused by the acts or omissions of such
-  Commercial Contributor in connection with its distribution of the Program in
-  a commercial product offering. The obligations in this section do not apply
-  to any claims or Losses relating to any actual or alleged intellectual
-  property infringement. In order to qualify, an Indemnified Contributor must:
-  a) promptly notify the Commercial Contributor in writing of such claim, and
-  b) allow the Commercial Contributor to control, and cooperate with the
-  Commercial Contributor in, the defense and any related settlement
-  negotiations. The Indemnified Contributor may participate in any such claim
-  at its own expense.
-
-  For example, a Contributor might include the Program in a commercial product
-  offering, Product X. That Contributor is then a Commercial Contributor. If
-  that Commercial Contributor then makes performance claims, or offers
-  warranties related to Product X, those performance claims and warranties are
-  such Commercial Contributor's responsibility alone. Under this section, the
-  Commercial Contributor would have to defend claims against the other
-  Contributors related to those performance claims and warranties, and if a
-  court requires any other Contributor to pay any damages as a result, the
-  Commercial Contributor must pay those damages.
-
-
-5. NO WARRANTY
-
-  EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON
-  AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
-  EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR
-  CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A
-  PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the
-  appropriateness of using and distributing the Program and assumes all risks
-  associated with its exercise of rights under this Agreement , including but
-  not limited to the risks and costs of program errors, compliance with
-  applicable laws, damage to or loss of data, programs or equipment, and
-  unavailability or interruption of operations.
-
-
-6. DISCLAIMER OF LIABILITY
-
-  EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
-  CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
-  LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-  ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
-  EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
-  OF SUCH DAMAGES.
-
-
-7. GENERAL
-
-  If any provision of this Agreement is invalid or unenforceable under
-  applicable law, it shall not affect the validity or enforceability of the
-  remainder of the terms of this Agreement, and without further action by the
-  parties hereto, such provision shall be reformed to the minimum extent
-  necessary to make such provision valid and enforceable.
-
-  If Recipient institutes patent litigation against any entity (including a
-  cross-claim or counterclaim in a lawsuit) alleging that the Program itself
-  (excluding combinations of the Program with other software or hardware)
-  infringes such Recipient's patent(s), then such Recipient's rights granted
-  under Section 2(b) shall terminate as of the date such litigation is filed.
-
-  All Recipient's rights under this Agreement shall terminate if it fails to
-  comply with any of the material terms or conditions of this Agreement and
-  does not cure such failure in a reasonable period of time after becoming
-  aware of such noncompliance. If all Recipient's rights under this Agreement
-  terminate, Recipient agrees to cease use and distribution of the Program as
-  soon as reasonably practicable. However, Recipient's obligations under this
-  Agreement and any licenses granted by Recipient relating to the Program shall
-  continue and survive.
-
-  Everyone is permitted to copy and distribute copies of this Agreement, but in
-  order to avoid inconsistency the Agreement is copyrighted and may only be
-  modified in the following manner. The Agreement Steward reserves the right to
-  publish new versions (including revisions) of this Agreement from time to
-  time. No one other than the Agreement Steward has the right to modify this
-  Agreement. The Eclipse Foundation is the initial Agreement Steward. The
-  Eclipse Foundation may assign the responsibility to serve as the Agreement
-  Steward to a suitable separate entity. Each new version of the Agreement will
-  be given a distinguishing version number. The Program (including
-  Contributions) may always be distributed subject to the version of the
-  Agreement under which it was received. In addition, after a new version of
-  the Agreement is published, Contributor may elect to distribute the Program
-  (including its Contributions) under the new version. Except as expressly
-  stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
-  licenses to the intellectual property of any Contributor under this
-  Agreement, whether expressly, by implication, estoppel or otherwise. All
-  rights in the Program not expressly granted under this Agreement are
-  reserved.
-
-  This Agreement is governed by the laws of the State of New York and the
-  intellectual property laws of the United States of America. No party to this
-  Agreement will bring a legal action under this Agreement more than one year
-  after the cause of action arose. Each party waives its rights to a jury trial
-  in any resulting litigation.
-
diff --git a/epl-v20 b/epl-v20
new file mode 100644
index 0000000..e48e096
--- /dev/null
+++ b/epl-v20
@@ -0,0 +1,277 @@
+Eclipse Public License - v 2.0
+
+    THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+    PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION
+    OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+  a) in the case of the initial Contributor, the initial content
+     Distributed under this Agreement, and
+
+  b) in the case of each subsequent Contributor:
+     i) changes to the Program, and
+     ii) additions to the Program;
+  where such changes and/or additions to the Program originate from
+  and are Distributed by that particular Contributor. A Contribution
+  "originates" from a Contributor if it was added to the Program by
+  such Contributor itself or anyone acting on such Contributor's behalf.
+  Contributions do not include changes or additions to the Program that
+  are not Modified Works.
+
+"Contributor" means any person or entity that Distributes the Program.
+
+"Licensed Patents" mean patent claims licensable by a Contributor which
+are necessarily infringed by the use or sale of its Contribution alone
+or when combined with the Program.
+
+"Program" means the Contributions Distributed in accordance with this
+Agreement.
+
+"Recipient" means anyone who receives the Program under this Agreement
+or any Secondary License (as applicable), including Contributors.
+
+"Derivative Works" shall mean any work, whether in Source Code or other
+form, that is based on (or derived from) the Program and for which the
+editorial revisions, annotations, elaborations, or other modifications
+represent, as a whole, an original work of authorship.
+
+"Modified Works" shall mean any work in Source Code or other form that
+results from an addition to, deletion from, or modification of the
+contents of the Program, including, for purposes of clarity any new file
+in Source Code form that contains any contents of the Program. Modified
+Works shall not include works that contain only declarations,
+interfaces, types, classes, structures, or files of the Program solely
+in each case in order to link to, bind by name, or subclass the Program
+or Modified Works thereof.
+
+"Distribute" means the acts of a) distributing or b) making available
+in any manner that enables the transfer of a copy.
+
+"Source Code" means the form of a Program preferred for making
+modifications, including but not limited to software source code,
+documentation source, and configuration files.
+
+"Secondary License" means either the GNU General Public License,
+Version 2.0, or any later versions of that license, including any
+exceptions or additional permissions as identified by the initial
+Contributor.
+
+2. GRANT OF RIGHTS
+
+  a) Subject to the terms of this Agreement, each Contributor hereby
+  grants Recipient a non-exclusive, worldwide, royalty-free copyright
+  license to reproduce, prepare Derivative Works of, publicly display,
+  publicly perform, Distribute and sublicense the Contribution of such
+  Contributor, if any, and such Derivative Works.
+
+  b) Subject to the terms of this Agreement, each Contributor hereby
+  grants Recipient a non-exclusive, worldwide, royalty-free patent
+  license under Licensed Patents to make, use, sell, offer to sell,
+  import and otherwise transfer the Contribution of such Contributor,
+  if any, in Source Code or other form. This patent license shall
+  apply to the combination of the Contribution and the Program if, at
+  the time the Contribution is added by the Contributor, such addition
+  of the Contribution causes such combination to be covered by the
+  Licensed Patents. The patent license shall not apply to any other
+  combinations which include the Contribution. No hardware per se is
+  licensed hereunder.
+
+  c) Recipient understands that although each Contributor grants the
+  licenses to its Contributions set forth herein, no assurances are
+  provided by any Contributor that the Program does not infringe the
+  patent or other intellectual property rights of any other entity.
+  Each Contributor disclaims any liability to Recipient for claims
+  brought by any other entity based on infringement of intellectual
+  property rights or otherwise. As a condition to exercising the
+  rights and licenses granted hereunder, each Recipient hereby
+  assumes sole responsibility to secure any other intellectual
+  property rights needed, if any. For example, if a third party
+  patent license is required to allow Recipient to Distribute the
+  Program, it is Recipient's responsibility to acquire that license
+  before distributing the Program.
+
+  d) Each Contributor represents that to its knowledge it has
+  sufficient copyright rights in its Contribution, if any, to grant
+  the copyright license set forth in this Agreement.
+
+  e) Notwithstanding the terms of any Secondary License, no
+  Contributor makes additional grants to any Recipient (other than
+  those set forth in this Agreement) as a result of such Recipient's
+  receipt of the Program under the terms of a Secondary License
+  (if permitted under the terms of Section 3).
+
+3. REQUIREMENTS
+
+3.1 If a Contributor Distributes the Program in any form, then:
+
+  a) the Program must also be made available as Source Code, in
+  accordance with section 3.2, and the Contributor must accompany
+  the Program with a statement that the Source Code for the Program
+  is available under this Agreement, and informs Recipients how to
+  obtain it in a reasonable manner on or through a medium customarily
+  used for software exchange; and
+
+  b) the Contributor may Distribute the Program under a license
+  different than this Agreement, provided that such license:
+     i) effectively disclaims on behalf of all other Contributors all
+     warranties and conditions, express and implied, including
+     warranties or conditions of title and non-infringement, and
+     implied warranties or conditions of merchantability and fitness
+     for a particular purpose;
+
+     ii) effectively excludes on behalf of all other Contributors all
+     liability for damages, including direct, indirect, special,
+     incidental and consequential damages, such as lost profits;
+
+     iii) does not attempt to limit or alter the recipients' rights
+     in the Source Code under section 3.2; and
+
+     iv) requires any subsequent distribution of the Program by any
+     party to be under a license that satisfies the requirements
+     of this section 3.
+
+3.2 When the Program is Distributed as Source Code:
+
+  a) it must be made available under this Agreement, or if the
+  Program (i) is combined with other material in a separate file or
+  files made available under a Secondary License, and (ii) the initial
+  Contributor attached to the Source Code the notice described in
+  Exhibit A of this Agreement, then the Program may be made available
+  under the terms of such Secondary Licenses, and
+
+  b) a copy of this Agreement must be included with each copy of
+  the Program.
+
+3.3 Contributors may not remove or alter any copyright, patent,
+trademark, attribution notices, disclaimers of warranty, or limitations
+of liability ("notices") contained within the Program from any copy of
+the Program which they Distribute, provided that Contributors may add
+their own appropriate notices.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities
+with respect to end users, business partners and the like. While this
+license is intended to facilitate the commercial use of the Program,
+the Contributor who includes the Program in a commercial product
+offering should do so in a manner which does not create potential
+liability for other Contributors. Therefore, if a Contributor includes
+the Program in a commercial product offering, such Contributor
+("Commercial Contributor") hereby agrees to defend and indemnify every
+other Contributor ("Indemnified Contributor") against any losses,
+damages and costs (collectively "Losses") arising from claims, lawsuits
+and other legal actions brought by a third party against the Indemnified
+Contributor to the extent caused by the acts or omissions of such
+Commercial Contributor in connection with its distribution of the Program
+in a commercial product offering. The obligations in this section do not
+apply to any claims or Losses relating to any actual or alleged
+intellectual property infringement. In order to qualify, an Indemnified
+Contributor must: a) promptly notify the Commercial Contributor in
+writing of such claim, and b) allow the Commercial Contributor to control,
+and cooperate with the Commercial Contributor in, the defense and any
+related settlement negotiations. The Indemnified Contributor may
+participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program in a commercial
+product offering, Product X. That Contributor is then a Commercial
+Contributor. If that Commercial Contributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Contributor's responsibility
+alone. Under this section, the Commercial Contributor would have to
+defend claims against the other Contributors related to those performance
+claims and warranties, and if a court requires any other Contributor to
+pay any damages as a result, the Commercial Contributor must pay
+those damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
+PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS"
+BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
+IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF
+TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
+PURPOSE. Each Recipient is solely responsible for determining the
+appropriateness of using and distributing the Program and assumes all
+risks associated with its exercise of rights under this Agreement,
+including but not limited to the risks and costs of program errors,
+compliance with applicable laws, damage to or loss of data, programs
+or equipment, and unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
+PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS
+SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
+PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
+EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further
+action by the parties hereto, such provision shall be reformed to the
+minimum extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity
+(including a cross-claim or counterclaim in a lawsuit) alleging that the
+Program itself (excluding combinations of the Program with other software
+or hardware) infringes such Recipient's patent(s), then such Recipient's
+rights granted under Section 2(b) shall terminate as of the date such
+litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it
+fails to comply with any of the material terms or conditions of this
+Agreement and does not cure such failure in a reasonable period of
+time after becoming aware of such noncompliance. If all Recipient's
+rights under this Agreement terminate, Recipient agrees to cease use
+and distribution of the Program as soon as reasonably practicable.
+However, Recipient's obligations under this Agreement and any licenses
+granted by Recipient relating to the Program shall continue and survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement,
+but in order to avoid inconsistency the Agreement is copyrighted and
+may only be modified in the following manner. The Agreement Steward
+reserves the right to publish new versions (including revisions) of
+this Agreement from time to time. No one other than the Agreement
+Steward has the right to modify this Agreement. The Eclipse Foundation
+is the initial Agreement Steward. The Eclipse Foundation may assign the
+responsibility to serve as the Agreement Steward to a suitable separate
+entity. Each new version of the Agreement will be given a distinguishing
+version number. The Program (including Contributions) may always be
+Distributed subject to the version of the Agreement under which it was
+received. In addition, after a new version of the Agreement is published,
+Contributor may elect to Distribute the Program (including its
+Contributions) under the new version.
+
+Except as expressly stated in Sections 2(a) and 2(b) above, Recipient
+receives no rights or licenses to the intellectual property of any
+Contributor under this Agreement, whether expressly, by implication,
+estoppel or otherwise. All rights in the Program not expressly granted
+under this Agreement are reserved. Nothing in this Agreement is intended
+to be enforceable by any entity that is not a Contributor or Recipient.
+No third-party beneficiary rights are created under this Agreement.
+
+Exhibit A - Form of Secondary Licenses Notice
+
+"This Source Code may also be made available under the following
+Secondary Licenses when the conditions for such availability set forth
+in the Eclipse Public License, v. 2.0 are satisfied: {name license(s),
+version(s), and exceptions or additional permissions here}."
+
+  Simply including a copy of this Agreement, including this Exhibit A
+  is not sufficient to license the Source Code under Secondary Licenses.
+
+  If it is not possible or desirable to put the notice in a particular
+  file, then You may include the notice in a location (such as a LICENSE
+  file in a relevant directory) where a recipient would be likely to
+  look for such a notice.
+
+  You may add additional accurate notices of copyright ownership.
diff --git a/examples/aws_iot.py b/examples/aws_iot.py
old mode 100644
new mode 100755
diff --git a/examples/client_logger.py b/examples/client_logger.py
old mode 100644
new mode 100755
index abe4788..7cbb87f
--- a/examples/client_logger.py
+++ b/examples/client_logger.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright (c) 2016 James Myatt <james@jamesmyatt.co.uk>
@@ -15,10 +15,12 @@
 
 # This shows a simple example of standard logging with an MQTT subscriber client.
 
+import logging
+
 import context  # Ensures paho is in PYTHONPATH
+
 import paho.mqtt.client as mqtt
 
-import logging
 logging.basicConfig(level=logging.DEBUG)
 
 # If you want to use a specific client id, use
@@ -30,7 +32,7 @@ mqttc = mqtt.Client()
 logger = logging.getLogger(__name__)
 mqttc.enable_logger(logger)
 
-mqttc.connect("mqtt.eclipse.org", 1883, 60)
+mqttc.connect("mqtt.eclipseprojects.io", 1883, 60)
 mqttc.subscribe("$SYS/#", 0)
 
 mqttc.loop_forever()
diff --git a/examples/client_mqtt_clear_retain.py b/examples/client_mqtt_clear_retain.py
old mode 100644
new mode 100755
index 3d64416..62f7c88
--- a/examples/client_mqtt_clear_retain.py
+++ b/examples/client_mqtt_clear_retain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright (c) 2013 Roger Light <roger@atchoo.org>
@@ -17,10 +17,11 @@
 
 # This shows an example of an MQTT client that clears all of the retained messages it receives.
 
-import sys
 import getopt
+import sys
 
 import context  # Ensures paho is in PYTHONPATH
+
 import paho.mqtt.client as mqtt
 
 final_mid = 0
@@ -59,7 +60,7 @@ def print_usage():
 
 def main(argv):
     debug = False
-    host = "mqtt.eclipse.org"
+    host = "mqtt.eclipseprojects.io"
     client_id = None
     keepalive = 60
     port = 1883
diff --git a/examples/client_pub-wait.py b/examples/client_pub-wait.py
old mode 100644
new mode 100755
index 5598672..caeb1a5
--- a/examples/client_pub-wait.py
+++ b/examples/client_pub-wait.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright (c) 2010-2013 Roger Light <roger@atchoo.org>
@@ -18,6 +18,7 @@
 # This shows a simple example of waiting for a message to be published.
 
 import context  # Ensures paho is in PYTHONPATH
+
 import paho.mqtt.client as mqtt
 
 
@@ -53,7 +54,7 @@ mqttc.on_publish = on_publish
 mqttc.on_subscribe = on_subscribe
 # Uncomment to enable debug messages
 # mqttc.on_log = on_log
-mqttc.connect("mqtt.eclipse.org", 1883, 60)
+mqttc.connect("mqtt.eclipseprojects.io", 1883, 60)
 
 mqttc.loop_start()
 
diff --git a/examples/client_pub_opts.py b/examples/client_pub_opts.py
old mode 100644
new mode 100755
index 0f196ae..a850288
--- a/examples/client_pub_opts.py
+++ b/examples/client_pub_opts.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright (c) 2017 Jon Levell <levell@uk.ibm.com>
@@ -15,15 +15,16 @@
 # This shows a example of an MQTT publisher with the ability to use
 # user name, password CA certificates based on command line arguments
 
-import paho.mqtt.client as mqtt
+import argparse
 import os
 import ssl
-import argparse
 import time
 
+import paho.mqtt.client as mqtt
+
 parser = argparse.ArgumentParser()
 
-parser.add_argument('-H', '--host', required=False, default="mqtt.eclipse.org")
+parser.add_argument('-H', '--host', required=False, default="mqtt.eclipseprojects.io")
 parser.add_argument('-t', '--topic', required=False, default="paho/test/opts")
 parser.add_argument('-q', '--qos', required=False, type=int,default=0)
 parser.add_argument('-c', '--clientid', required=False, default=None)
diff --git a/examples/client_rpc_math.py b/examples/client_rpc_math.py
old mode 100644
new mode 100755
index fe738e4..af534fc
--- a/examples/client_rpc_math.py
+++ b/examples/client_rpc_math.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright (c) 2020 Frank Pagliughi <fpagliughi@mindspring.com>
@@ -17,10 +17,12 @@
 
 # This shows an example of an MQTTv5 Remote Procedure Call (RPC) client.
 
-import context  # Ensures paho is in PYTHONPATH
+import json
 import sys
 import time
-import json
+
+import context  # Ensures paho is in PYTHONPATH
+
 import paho.mqtt.client as mqtt
 from paho.mqtt.packettypes import PacketTypes
 
diff --git a/examples/client_session_present.py b/examples/client_session_present.py
old mode 100644
new mode 100755
index b651ceb..806d1af
--- a/examples/client_session_present.py
+++ b/examples/client_session_present.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright (c) 2014 Roger Light <roger@atchoo.org>
@@ -18,6 +18,7 @@
 # This demonstrates the session present flag when connecting.
 
 import context  # Ensures paho is in PYTHONPATH
+
 import paho.mqtt.client as mqtt
 
 
@@ -49,7 +50,7 @@ mqttc.on_disconnect = on_disconnect
 # Uncomment to enable debug messages
 # mqttc.on_log = on_log
 mqttc.user_data_set(0)
-mqttc.connect("mqtt.eclipse.org", 1883, 60)
+mqttc.connect("mqtt.eclipseprojects.io", 1883, 60)
 
 mqttc.loop_forever()
 
@@ -57,5 +58,5 @@ mqttc.loop_forever()
 mqttc = mqtt.Client(client_id="asdfj", clean_session=True)
 mqttc.on_connect = on_connect
 mqttc.user_data_set(2)
-mqttc.connect("mqtt.eclipse.org", 1883, 60)
+mqttc.connect("mqtt.eclipseprojects.io", 1883, 60)
 mqttc.loop_forever()
diff --git a/examples/client_sub-class.py b/examples/client_sub-class.py
old mode 100644
new mode 100755
index 638eb77..d577625
--- a/examples/client_sub-class.py
+++ b/examples/client_sub-class.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright (c) 2013 Roger Light <roger@atchoo.org>
@@ -16,13 +16,18 @@
 # This example shows how you can use the MQTT client in a class.
 
 import context  # Ensures paho is in PYTHONPATH
+
 import paho.mqtt.client as mqtt
 
+
 class MyMQTTClass(mqtt.Client):
 
     def on_connect(self, mqttc, obj, flags, rc):
         print("rc: "+str(rc))
 
+    def on_connect_fail(self, mqttc, obj):
+        print("Connect failed")
+
     def on_message(self, mqttc, obj, msg):
         print(msg.topic+" "+str(msg.qos)+" "+str(msg.payload))
 
@@ -36,7 +41,7 @@ class MyMQTTClass(mqtt.Client):
         print(string)
 
     def run(self):
-        self.connect("mqtt.eclipse.org", 1883, 60)
+        self.connect("mqtt.eclipseprojects.io", 1883, 60)
         self.subscribe("$SYS/#", 0)
 
         rc = 0
diff --git a/examples/client_sub-multiple-callback.py b/examples/client_sub-multiple-callback.py
old mode 100644
new mode 100755
index 4e60c7e..d8126c7
--- a/examples/client_sub-multiple-callback.py
+++ b/examples/client_sub-multiple-callback.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright (c) 2014 Roger Light <roger@atchoo.org>
@@ -17,6 +17,7 @@
 # This shows a simple example of an MQTT subscriber using a per-subscription message handler.
 
 import context  # Ensures paho is in PYTHONPATH
+
 import paho.mqtt.client as mqtt
 
 
@@ -46,7 +47,7 @@ mqttc = mqtt.Client()
 mqttc.message_callback_add("$SYS/broker/messages/#", on_message_msgs)
 mqttc.message_callback_add("$SYS/broker/bytes/#", on_message_bytes)
 mqttc.on_message = on_message
-mqttc.connect("mqtt.eclipse.org", 1883, 60)
+mqttc.connect("mqtt.eclipseprojects.io", 1883, 60)
 mqttc.subscribe("$SYS/#", 0)
 
 mqttc.loop_forever()
diff --git a/examples/client_sub-srv.py b/examples/client_sub-srv.py
old mode 100644
new mode 100755
index bf20008..f3ebd38
--- a/examples/client_sub-srv.py
+++ b/examples/client_sub-srv.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright (c) 2010-2013 Roger Light <roger@atchoo.org>
@@ -18,8 +18,10 @@
 # This shows a simple example of an MQTT subscriber using connect_srv method.
 
 import context  # Ensures paho is in PYTHONPATH
+
 import paho.mqtt.client as mqtt
 
+
 def on_connect(mqttc, obj, flags, rc):
     print("Connected to %s:%s" % (mqttc._host, mqttc._port))
 
diff --git a/examples/client_sub-ws.py b/examples/client_sub-ws.py
old mode 100644
new mode 100755
index 10b6979..085d485
--- a/examples/client_sub-ws.py
+++ b/examples/client_sub-ws.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright (c) 2010-2013 Roger Light <roger@atchoo.org>
@@ -18,8 +18,10 @@
 # This shows a simple example of an MQTT subscriber.
 
 import context  # Ensures paho is in PYTHONPATH
+
 import paho.mqtt.client as mqtt
 
+
 def on_connect(mqttc, obj, flags, rc):
     print("rc: "+str(rc))
 
diff --git a/examples/client_sub.py b/examples/client_sub.py
old mode 100644
new mode 100755
index 91415c4..9cd54e2
--- a/examples/client_sub.py
+++ b/examples/client_sub.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright (c) 2010-2013 Roger Light <roger@atchoo.org>
@@ -18,6 +18,7 @@
 # This shows a simple example of an MQTT subscriber.
 
 import context  # Ensures paho is in PYTHONPATH
+
 import paho.mqtt.client as mqtt
 
 
@@ -52,7 +53,7 @@ mqttc.on_publish = on_publish
 mqttc.on_subscribe = on_subscribe
 # Uncomment to enable debug messages
 # mqttc.on_log = on_log
-mqttc.connect("mqtt.eclipse.org", 1883, 60)
+mqttc.connect("mqtt.eclipseprojects.io", 1883, 60)
 mqttc.subscribe("$SYS/#", 0)
 
 mqttc.loop_forever()
diff --git a/examples/client_sub_opts.py b/examples/client_sub_opts.py
old mode 100644
new mode 100755
index 0e5bc5a..4aa6664
--- a/examples/client_sub_opts.py
+++ b/examples/client_sub_opts.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright (c) 2017 Jon Levell <levell@uk.ibm.com>
@@ -15,14 +15,15 @@
 # This shows a example of an MQTT subscriber with the ability to use
 # user name, password CA certificates based on command line arguments
 
-import paho.mqtt.client as mqtt
+import argparse
 import os
 import ssl
-import argparse
+
+import paho.mqtt.client as mqtt
 
 parser = argparse.ArgumentParser()
 
-parser.add_argument('-H', '--host', required=False, default="mqtt.eclipse.org")
+parser.add_argument('-H', '--host', required=False, default="mqtt.eclipseprojects.io")
 parser.add_argument('-t', '--topic', required=False, default="$SYS/#")
 parser.add_argument('-q', '--qos', required=False, type=int, default=0)
 parser.add_argument('-c', '--clientid', required=False, default=None)
diff --git a/examples/context.py b/examples/context.py
old mode 100644
new mode 100755
index e42e26e..faef26a
--- a/examples/context.py
+++ b/examples/context.py
@@ -7,9 +7,9 @@ try:
 except ImportError:
     # This part is only required to run the examples from within the examples
     # directory when the module itself is not installed.
-    import sys
-    import os
     import inspect
+    import os
+    import sys
 
     cmd_subfolder = os.path.realpath(
         os.path.abspath(
diff --git a/examples/loop_asyncio.py b/examples/loop_asyncio.py
index b017501..f4f22c7 100755
--- a/examples/loop_asyncio.py
+++ b/examples/loop_asyncio.py
@@ -1,9 +1,12 @@
 #!/usr/bin/env python3
 
+import asyncio
 import socket
 import uuid
+
+import context  # Ensures paho is in PYTHONPATH
+
 import paho.mqtt.client as mqtt
-import asyncio
 
 client_id = 'paho-mqtt-python/issue72/' + str(uuid.uuid4())
 topic = client_id
@@ -85,7 +88,7 @@ class AsyncMqttExample:
 
         aioh = AsyncioHelper(self.loop, self.client)
 
-        self.client.connect('mqtt.eclipse.org', 1883, 60)
+        self.client.connect('mqtt.eclipseprojects.io', 1883, 60)
         self.client.socket().setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 2048)
 
         for c in range(3):
diff --git a/examples/loop_select.py b/examples/loop_select.py
index 68bb541..328b103 100755
--- a/examples/loop_select.py
+++ b/examples/loop_select.py
@@ -2,10 +2,11 @@
 
 import socket
 import uuid
-import paho.mqtt.client as mqtt
 from select import select
 from time import time
 
+import paho.mqtt.client as mqtt
+
 client_id = 'paho-mqtt-python/issue72/' + str(uuid.uuid4())
 topic = client_id
 print("Using client_id / topic: " + client_id)
@@ -64,7 +65,7 @@ class SelectMqttExample:
         self.client.on_message = self.on_message
         self.client.on_disconnect = self.on_disconnect
 
-        self.client.connect('mqtt.eclipse.org', 1883, 60)
+        self.client.connect('mqtt.eclipseprojects.io', 1883, 60)
         print("Socket opened")
         self.client.socket().setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 2048)
 
diff --git a/examples/loop_trio.py b/examples/loop_trio.py
new file mode 100755
index 0000000..0567d13
--- /dev/null
+++ b/examples/loop_trio.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+
+import socket
+import uuid
+
+import trio
+
+import paho.mqtt.client as mqtt
+
+client_id = 'paho-mqtt-python/issue72/' + str(uuid.uuid4())
+topic = client_id
+print("Using client_id / topic: " + client_id)
+
+
+class TrioAsyncHelper:
+    def __init__(self, client):
+        self.client = client
+        self.sock = None
+        self._event_large_write = trio.Event()
+
+        self.client.on_socket_open = self.on_socket_open
+        self.client.on_socket_register_write = self.on_socket_register_write
+        self.client.on_socket_unregister_write = self.on_socket_unregister_write
+
+    async def read_loop(self):
+        while True:
+            await trio.hazmat.wait_readable(self.sock)
+            self.client.loop_read()
+
+    async def write_loop(self):
+        while True:
+            await self._event_large_write.wait()
+            await trio.hazmat.wait_writable(self.sock)
+            self.client.loop_write()
+
+    async def misc_loop(self):
+        print("misc_loop started")
+        while self.client.loop_misc() == mqtt.MQTT_ERR_SUCCESS:
+            await trio.sleep(1)
+        print("misc_loop finished")
+
+    def on_socket_open(self, client, userdata, sock):
+        print("Socket opened")
+        self.sock = sock
+        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 2048)
+
+    def on_socket_register_write(self, client, userdata, sock):
+        print('large write request')
+        self._event_large_write.set()
+
+    def on_socket_unregister_write(self, client, userdata, sock):
+        print("finished large write")
+        self._event_large_write = trio.Event()
+
+
+class TrioAsyncMqttExample:
+    def on_connect(self, client, userdata, flags, rc):
+        print("Subscribing")
+        client.subscribe(topic)
+
+    def on_message(self, client, userdata, msg):
+        print("Got response with {} bytes".format(len(msg.payload)))
+
+    def on_disconnect(self, client, userdata, rc):
+        print('Disconnect result {}'.format(rc))
+
+    async def test_write(self, cancel_scope: trio.CancelScope):
+        for c in range(3):
+            await trio.sleep(5)
+            print("Publishing")
+            self.client.publish(topic, b'Hello' * 40000, qos=1)
+        cancel_scope.cancel()
+
+    async def main(self):
+        self.client = mqtt.Client(client_id=client_id)
+        self.client.on_connect = self.on_connect
+        self.client.on_message = self.on_message
+        self.client.on_disconnect = self.on_disconnect
+
+        trio_helper = TrioAsyncHelper(self.client)
+
+        self.client.connect('mqtt.eclipseprojects.io', 1883, 60)
+
+        async with trio.open_nursery() as nursery:
+            nursery.start_soon(trio_helper.read_loop)
+            nursery.start_soon(trio_helper.write_loop)
+            nursery.start_soon(trio_helper.misc_loop)
+            nursery.start_soon(self.test_write, nursery.cancel_scope)
+
+        self.client.disconnect()
+        print("Disconnected")
+
+
+print("Starting")
+trio.run(TrioAsyncMqttExample().main)
+print("Finished")
diff --git a/examples/publish_multiple.py b/examples/publish_multiple.py
old mode 100644
new mode 100755
index 5ef56d2..7c19a13
--- a/examples/publish_multiple.py
+++ b/examples/publish_multiple.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright (c) 2014 Roger Light <roger@atchoo.org>
@@ -16,7 +16,8 @@
 # This shows an example of using the publish.multiple helper function.
 
 import context  # Ensures paho is in PYTHONPATH
+
 import paho.mqtt.publish as publish
 
 msgs = [{'topic': "paho/test/multiple", 'payload': "multiple 1"}, ("paho/test/multiple", "multiple 2", 0, False)]
-publish.multiple(msgs, hostname="mqtt.eclipse.org")
+publish.multiple(msgs, hostname="mqtt.eclipseprojects.io")
diff --git a/examples/publish_single.py b/examples/publish_single.py
old mode 100644
new mode 100755
index 628a7e0..9e48279
--- a/examples/publish_single.py
+++ b/examples/publish_single.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright (c) 2014 Roger Light <roger@atchoo.org>
@@ -16,6 +16,7 @@
 # This shows an example of using the publish.single helper function.
 
 import context  # Ensures paho is in PYTHONPATH
+
 import paho.mqtt.publish as publish
 
-publish.single("paho/test/single", "boo", hostname="mqtt.eclipse.org")
+publish.single("paho/test/single", "boo", hostname="mqtt.eclipseprojects.io")
diff --git a/examples/publish_utf8-27.py b/examples/publish_utf8-27.py
old mode 100644
new mode 100755
index 73372b1..74dee2b
--- a/examples/publish_utf8-27.py
+++ b/examples/publish_utf8-27.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright (c) 2014 Roger Light <roger@atchoo.org>
@@ -16,8 +16,9 @@
 # This shows an example of using the publish.single helper function with unicode topic and payload.
 
 import context  # Ensures paho is in PYTHONPATH
+
 import paho.mqtt.publish as publish
 
 topic = u"paho/test/single/ô"
 payload = u"bôô"
-publish.single(topic, payload, hostname="mqtt.eclipse.org")
+publish.single(topic, payload, hostname="mqtt.eclipseprojects.io")
diff --git a/examples/publish_utf8-3.py b/examples/publish_utf8-3.py
new file mode 100755
index 0000000..76c11b4
--- /dev/null
+++ b/examples/publish_utf8-3.py
@@ -0,0 +1,24 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2014 Roger Light <roger@atchoo.org>
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Distribution License v1.0
+# which accompanies this distribution.
+#
+# The Eclipse Distribution License is available at
+#   http://www.eclipse.org/org/documents/edl-v10.php.
+#
+# Contributors:
+#    Roger Light - initial implementation
+
+# This shows an example of using the publish.single helper function with unicode topic and payload.
+
+import context  # Ensures paho is in PYTHONPATH
+
+import paho.mqtt.publish as publish
+
+topic = u"paho/test/single/ô"
+payload = u'German umlauts like "ä" ü"ö" are not supported'
+publish.single(topic, payload, hostname="test.mosquitto.org")
diff --git a/examples/server_rpc_math.py b/examples/server_rpc_math.py
old mode 100644
new mode 100755
index 5d71166..5f3613e
--- a/examples/server_rpc_math.py
+++ b/examples/server_rpc_math.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright (c) 2020 Frank Pagliughi <fpagliughi@mindspring.com>
@@ -17,8 +17,10 @@
 
 # This shows an example of an MQTTv5 Remote Procedure Call (RPC) server.
 
-import context  # Ensures paho is in PYTHONPATH
 import json
+
+import context  # Ensures paho is in PYTHONPATH
+
 import paho.mqtt.client as mqtt
 from paho.mqtt.packettypes import PacketTypes
 
@@ -90,6 +92,6 @@ mqttc.on_connect = on_connect
 # Uncomment to enable debug messages
 #mqttc.on_log = on_log
 
-#mqttc.connect("mqtt.eclipse.org", 1883, 60)
+#mqttc.connect("mqtt.eclipseprojects.io", 1883, 60)
 mqttc.connect(host="localhost", clean_start=False)
 mqttc.loop_forever()
diff --git a/examples/subscribe_callback.py b/examples/subscribe_callback.py
old mode 100644
new mode 100755
index 7feaef2..f12bccc
--- a/examples/subscribe_callback.py
+++ b/examples/subscribe_callback.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright (c) 2016 Roger Light <roger@atchoo.org>
@@ -16,9 +16,11 @@
 # This shows an example of using the subscribe.callback helper function.
 
 import context  # Ensures paho is in PYTHONPATH
+
 import paho.mqtt.subscribe as subscribe
 
+
 def print_msg(client, userdata, message):
     print("%s : %s" % (message.topic, message.payload))
 
-subscribe.callback(print_msg, "#", hostname="mqtt.eclipse.org")
+subscribe.callback(print_msg, "#", hostname="mqtt.eclipseprojects.io")
diff --git a/examples/subscribe_simple.py b/examples/subscribe_simple.py
old mode 100644
new mode 100755
index 2603b5b..87adb9f
--- a/examples/subscribe_simple.py
+++ b/examples/subscribe_simple.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright (c) 2016 Roger Light <roger@atchoo.org>
@@ -16,11 +16,12 @@
 # This shows an example of using the subscribe.simple helper function.
 
 import context  # Ensures paho is in PYTHONPATH
+
 import paho.mqtt.subscribe as subscribe
 
 topics = ['#']
 
-m = subscribe.simple(topics, hostname="mqtt.eclipse.org", retained=False, msg_count=2)
+m = subscribe.simple(topics, hostname="mqtt.eclipseprojects.io", retained=False, msg_count=2)
 for a in m:
     print(a.topic)
     print(a.payload)
diff --git a/notice.html b/notice.html
index f19c483..f26d640 100644
--- a/notice.html
+++ b/notice.html
@@ -21,7 +21,7 @@
 
 <h3>Applicable Licenses</h3>
 
-<p>Unless otherwise indicated, all Content made available by the Eclipse Foundation is provided to you under the terms and conditions of the Eclipse Public License Version 1.0
+<p>Unless otherwise indicated, all Content made available by the Eclipse Foundation is provided to you under the terms and conditions of the Eclipse Public License Version 2.0
    (&quot;EPL&quot;).  A copy of the EPL is provided with this Content and is also available at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
    For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
 
diff --git a/setup.cfg b/setup.cfg
index 84551cd..71ce170 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,8 +1,7 @@
 [aliases]
 test=pytest
 [tool:pytest]
-addopts=-r xs --pylama
-strict=True
+addopts=-r xs
 testpaths=tests src
 [pylama]
 linters=pyflakes
diff --git a/setup.py b/setup.py
index f5d0742..038c1e1 100644
--- a/setup.py
+++ b/setup.py
@@ -1,8 +1,9 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
 import sys
-from setuptools import setup, find_packages
+
+from setuptools import find_packages, setup
 
 sys.path.insert(0, 'src')
 from paho.mqtt import __version__
@@ -31,7 +32,7 @@ setup(
     package_dir={'': 'src'},
     include_package_data=True,
     install_requires=requirements,
-    license='Eclipse Public License v1.0 / Eclipse Distribution License v1.0',
+    license='Eclipse Public License v2.0 / Eclipse Distribution License v1.0',
     zip_safe=False,
     keywords='paho',
     classifiers=[
@@ -46,10 +47,11 @@ setup(
         'Programming Language :: Python :: 2',
         'Programming Language :: Python :: 2.7',
         'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: 3.8',
+        'Programming Language :: Python :: 3.9',
+        'Programming Language :: Python :: 3.10',
         'Topic :: Communications',
         'Topic :: Internet',
     ],
diff --git a/src/paho/mqtt/__init__.py b/src/paho/mqtt/__init__.py
index d16f17f..0d349fc 100644
--- a/src/paho/mqtt/__init__.py
+++ b/src/paho/mqtt/__init__.py
@@ -1,4 +1,4 @@
-__version__ = "1.5.1"
+__version__ = "1.6.1"
 
 
 class MQTTException(Exception):
diff --git a/src/paho/mqtt/client.py b/src/paho/mqtt/client.py
index 1085970..1c0236e 100644
--- a/src/paho/mqtt/client.py
+++ b/src/paho/mqtt/client.py
@@ -1,7 +1,7 @@
 # Copyright (c) 2012-2019 Roger Light and others
 #
 # All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Eclipse Public License v1.0
+# are made available under the terms of the Eclipse Public License v2.0
 # and Eclipse Distribution License v1.0 which accompany this distribution.
 #
 # The Eclipse Public License is available at
@@ -13,19 +13,21 @@
 #    Roger Light - initial API and implementation
 #    Ian Craggs - MQTT V5 support
 
-from .subscribeoptions import SubscribeOptions
-from .reasoncodes import ReasonCodes
-from .properties import Properties
-from .matcher import MQTTMatcher
-import logging
+import base64
 import hashlib
+import logging
 import string
-import base64
-import uuid
-import time
-import threading
-import sys
 import struct
+import sys
+import threading
+import time
+import uuid
+
+from .matcher import MQTTMatcher
+from .properties import Properties
+from .reasoncodes import ReasonCodes
+from .subscribeoptions import SubscribeOptions
+
 """
 This is an MQTT client module. MQTT is a lightweight pub/sub messaging
 protocol that is easy to implement and suitable for low powered devices.
@@ -51,11 +53,12 @@ except ImportError:
 
 try:
     # Python 3
-    from urllib import request as urllib_dot_request
     from urllib import parse as urllib_dot_parse
+    from urllib import request as urllib_dot_request
 except ImportError:
     # Python 2
     import urllib as urllib_dot_request
+
     import urlparse as urllib_dot_parse
 
 
@@ -78,6 +81,12 @@ if platform.system() == 'Windows':
 else:
     EAGAIN = errno.EAGAIN
 
+# Python 2.7 does not have BlockingIOError.  Fall back to IOError
+try:
+    BlockingIOError
+except NameError:
+    BlockingIOError  = IOError
+
 MQTTv31 = 3
 MQTTv311 = 4
 MQTTv5 = 5
@@ -162,6 +171,7 @@ MQTT_ERR_ACL_DENIED = 12
 MQTT_ERR_UNKNOWN = 13
 MQTT_ERR_ERRNO = 14
 MQTT_ERR_QUEUE_SIZE = 15
+MQTT_ERR_KEEPALIVE = 16
 
 MQTT_CLIENT = 0
 MQTT_BRIDGE = 1
@@ -176,10 +186,6 @@ class WebsocketConnectionError(ValueError):
     pass
 
 
-class WouldBlockError(Exception):
-    pass
-
-
 def error_string(mqtt_errno):
     """Return the error string associated with an mqtt error number."""
     if mqtt_errno == MQTT_ERR_SUCCESS:
@@ -214,6 +220,8 @@ def error_string(mqtt_errno):
         return "Error defined by errno."
     elif mqtt_errno == MQTT_ERR_QUEUE_SIZE:
         return "Message queue full."
+    elif mqtt_errno == MQTT_ERR_KEEPALIVE:
+        return "Client or broker did not communicate in the keepalive interval."
     else:
         return "Unknown error."
 
@@ -278,9 +286,8 @@ def _socketpair_compat():
     sock1.setblocking(0)
     try:
         sock1.connect(("127.0.0.1", port))
-    except socket.error as err:
-        if err.errno != errno.EINPROGRESS and err.errno != errno.EWOULDBLOCK and err.errno != EAGAIN:
-            raise
+    except BlockingIOError:
+        pass
     sock2, address = listensock.accept()
     sock2.setblocking(0)
     listensock.close()
@@ -335,19 +342,44 @@ class MQTTMessageInfo(object):
             self._published = True
             self._condition.notify()
 
-    def wait_for_publish(self):
-        """Block until the message associated with this object is published."""
+    def wait_for_publish(self, timeout=None):
+        """Block until the message associated with this object is published, or
+        until the timeout occurs. If timeout is None, this will never time out.
+        Set timeout to a positive number of seconds, e.g. 1.2, to enable the
+        timeout.
+
+        Raises ValueError if the message was not queued due to the outgoing
+        queue being full.
+
+        Raises RuntimeError if the message was not published for another
+        reason.
+        """
         if self.rc == MQTT_ERR_QUEUE_SIZE:
             raise ValueError('Message is not queued due to ERR_QUEUE_SIZE')
+        elif self.rc == MQTT_ERR_AGAIN:
+            pass
+        elif self.rc > 0:
+            raise RuntimeError('Message publish failed: %s' % (error_string(self.rc)))
+
+        timeout_time = None if timeout is None else time.time() + timeout
+        timeout_tenth = None if timeout is None else timeout / 10.
+        def timed_out():
+            return False if timeout is None else time.time() > timeout_time
+
         with self._condition:
-            while not self._published:
-                self._condition.wait()
+            while not self._published and not timed_out():
+                self._condition.wait(timeout_tenth)
 
     def is_published(self):
         """Returns True if the message associated with this object has been
         published, else returns False."""
         if self.rc == MQTT_ERR_QUEUE_SIZE:
             raise ValueError('Message is not queued due to ERR_QUEUE_SIZE')
+        elif self.rc == MQTT_ERR_AGAIN:
+            pass
+        elif self.rc > 0:
+            raise RuntimeError('Message publish failed: %s' % (error_string(self.rc)))
+
         with self._condition:
             return self._published
 
@@ -358,14 +390,12 @@ class MQTTMessage(object):
 
     Members:
 
-    topic : String/bytes. topic that the message was published on.
-    payload : String/bytes the message payload.
+    topic : String. topic that the message was published on.
+    payload : Bytes/Byte array. the message payload.
     qos : Integer. The message Quality of Service 0, 1 or 2.
     retain : Boolean. If true, the message is a retained message and not fresh.
     mid : Integer. The message id.
     properties: Properties class. In MQTT v5.0, the properties associated with the message.
-
-    On Python 3, topic must be bytes.
     """
 
     __slots__ = 'timestamp', 'state', 'dup', 'mid', '_topic', 'payload', 'qos', 'retain', 'info', 'properties'
@@ -426,11 +456,24 @@ class Client(object):
     broker. To use a callback, define a function and then assign it to the
     client:
 
-    def on_connect(client, userdata, flags, rc, properties=None):
+    def on_connect(client, userdata, flags, rc):
         print("Connection returned " + str(rc))
 
     client.on_connect = on_connect
 
+    Callbacks can also be attached using decorators:
+
+    client = paho.mqtt.Client()
+
+    @client.connect_callback()
+    def on_connect(client, userdata, flags, rc):
+        print("Connection returned " + str(rc))
+
+
+    **IMPORTANT** the required function signature for a callback can differ
+    depending on whether you are using MQTT v5 or MQTT v3.1.1/v3.1. See the
+    documentation for each callback.
+
     All of the callbacks as described below have a "client" and an "userdata"
     argument. "client" is the Client instance that is calling the callback.
     "userdata" is user data of any type and can be set when creating a new client
@@ -439,81 +482,16 @@ class Client(object):
     If you wish to suppress exceptions within a callback, you should set
     `client.suppress_exceptions = True`
 
-    The callbacks:
-
-    on_connect(client, userdata, flags, rc, properties=None): called when the broker responds to our connection
-      request.
-      flags is a dict that contains response flags from the broker:
-        flags['session present'] - this flag is useful for clients that are
-            using clean session set to 0 only. If a client with clean
-            session=0, that reconnects to a broker that it has previously
-            connected to, this flag indicates whether the broker still has the
-            session information for the client. If 1, the session still exists.
-      The value of rc determines success or not:
-        0: Connection successful
-        1: Connection refused - incorrect protocol version
-        2: Connection refused - invalid client identifier
-        3: Connection refused - server unavailable
-        4: Connection refused - bad username or password
-        5: Connection refused - not authorised
-        6-255: Currently unused.
-
-    on_disconnect(client, userdata, rc): called when the client disconnects from the broker.
-      The rc parameter indicates the disconnection state. If MQTT_ERR_SUCCESS
-      (0), the callback was called in response to a disconnect() call. If any
-      other value the disconnection was unexpected, such as might be caused by
-      a network error.
-
-    on_disconnect(client, userdata, rc, properties): called when the MQTT V5 client disconnects from the broker.
-      When using MQTT V5, the broker can send a disconnect message to the client.  The
-      message can contain a reason code and MQTT V5 properties.  The properties parameter could be
-      None if they do not exist in the disconnect message.
-
-    on_message(client, userdata, message): called when a message has been received on a
-      topic that the client subscribes to. The message variable is a
-      MQTTMessage that describes all of the message parameters.
-
-    on_publish(client, userdata, mid): called when a message that was to be sent using the
-      publish() call has completed transmission to the broker. For messages
-      with QoS levels 1 and 2, this means that the appropriate handshakes have
-      completed. For QoS 0, this simply means that the message has left the
-      client. The mid variable matches the mid variable returned from the
-      corresponding publish() call, to allow outgoing messages to be tracked.
-      This callback is important because even if the publish() call returns
-      success, it does not always mean that the message has been sent.
-
-    on_subscribe(client, userdata, mid, granted_qos, properties=None): called when the broker responds to a
-      subscribe request. The mid variable matches the mid variable returned
-      from the corresponding subscribe() call. The granted_qos variable is a
-      list of integers that give the QoS level the broker has granted for each
-      of the different subscription requests.
-
-    on_unsubscribe(client, userdata, mid): called when the broker responds to an unsubscribe
-      request. The mid variable matches the mid variable returned from the
-      corresponding unsubscribe() call.
-
-    on_log(client, userdata, level, buf): called when the client has log information. Define
-      to allow debugging. The level variable gives the severity of the message
-      and will be one of MQTT_LOG_INFO, MQTT_LOG_NOTICE, MQTT_LOG_WARNING,
-      MQTT_LOG_ERR, and MQTT_LOG_DEBUG. The message itself is in buf.
-
-    on_socket_open(client, userdata, sock): Called when the socket has been opened. Use this
-      to register the socket with an external event loop for reading.
-
-    on_socket_close(client, userdata, sock): Called when the socket is about to be closed.
-      Use this to unregister a socket from an external event loop for reading.
-
-    on_socket_register_write(client, userdata, sock): Called when a write operation to the
-      socket failed because it would have blocked, e.g. output buffer full. Use this to
-      register the socket with an external event loop for writing.
-
-    on_socket_unregister_write(client, userdata, sock): Called when a write operation to the
-      socket succeeded after it had previously failed. Use this to unregister the socket
-      from an external event loop for writing.
+    The callbacks are listed below, documentation for each of them can be found
+    at the same function name:
+
+    on_connect, on_connect_fail, on_disconnect, on_message, on_publish,
+    on_subscribe, on_unsubscribe, on_log, on_socket_open, on_socket_close,
+    on_socket_register_write, on_socket_unregister_write
     """
 
     def __init__(self, client_id="", clean_session=None, userdata=None,
-                 protocol=MQTTv311, transport="tcp"):
+                 protocol=MQTTv311, transport="tcp", reconnect_on_failure=True):
         """client_id is the unique client id string used when connecting to the
         broker. If client_id is zero length or None, then the behaviour is
         defined by which protocol version is in use. If using MQTT v3.1.1, then
@@ -540,24 +518,13 @@ class Client(object):
 
         The protocol argument allows explicit setting of the MQTT version to
         use for this client. Can be paho.mqtt.client.MQTTv311 (v3.1.1),
-        paho.mqtt.client.MQTTv31 (v3.1) or paho.mqtt.client.MQTTv5 (v5.0), 
+        paho.mqtt.client.MQTTv31 (v3.1) or paho.mqtt.client.MQTTv5 (v5.0),
         with the default being v3.1.1.
 
         Set transport to "websockets" to use WebSockets as the transport
         mechanism. Set to "tcp" to use raw TCP, which is the default.
         """
 
-        if protocol == MQTTv5:
-            if clean_session != None:
-                raise ValueError('Clean session is not used for MQTT 5.0')
-        else:
-            if clean_session == None:
-                clean_session = True
-            if not clean_session and (client_id == "" or client_id is None):
-                raise ValueError(
-                    'A client id must be provided if clean session is False.')
-            self._clean_session = clean_session
-
         if transport.lower() not in ('websockets', 'tcp'):
             raise ValueError(
                 'transport must be "websockets" or "tcp", not %s' % transport)
@@ -566,11 +533,21 @@ class Client(object):
         self._userdata = userdata
         self._sock = None
         self._sockpairR, self._sockpairW = (None, None,)
-        self._sockpairR, self._sockpairW = _socketpair_compat()
         self._keepalive = 60
-        self._message_retry = 20
-        self._last_retry_check = 0
+        self._connect_timeout = 5.0
         self._client_mode = MQTT_CLIENT
+
+        if protocol == MQTTv5:
+            if clean_session is not None:
+                raise ValueError('Clean session is not used for MQTT 5.0')
+        else:
+            if clean_session is None:
+                clean_session = True
+            if not clean_session and (client_id == "" or client_id is None):
+                raise ValueError(
+                    'A client id must be provided if clean session is False.')
+            self._clean_session = clean_session
+
         # [MQTT-3.1.3-4] Client Id must be UTF-8 encoded string.
         if client_id == "" or client_id is None:
             if protocol == MQTTv31:
@@ -590,16 +567,16 @@ class Client(object):
             "remaining_count": [],
             "remaining_mult": 1,
             "remaining_length": 0,
-            "packet": b"",
+            "packet": bytearray(b""),
             "to_process": 0,
             "pos": 0}
         self._out_packet = collections.deque()
-        self._current_out_packet = None
         self._last_msg_in = time_func()
         self._last_msg_out = time_func()
         self._reconnect_min_delay = 1
         self._reconnect_max_delay = 120
         self._reconnect_delay = None
+        self._reconnect_on_failure = reconnect_on_failure
         self._ping_t = 0
         self._last_mid = 0
         self._state = mqtt_cs_new
@@ -623,8 +600,6 @@ class Client(object):
         self._proxy = {}
         self._in_callback_mutex = threading.Lock()
         self._callback_mutex = threading.RLock()
-        self._out_packet_mutex = threading.Lock()
-        self._current_out_packet_mutex = threading.RLock()
         self._msgtime_mutex = threading.Lock()
         self._out_message_mutex = threading.RLock()
         self._in_message_mutex = threading.Lock()
@@ -641,6 +616,7 @@ class Client(object):
         # No default callbacks
         self._on_log = None
         self._on_connect = None
+        self._on_connect_fail = None
         self._on_subscribe = None
         self._on_message = None
         self._on_publish = None
@@ -662,29 +638,23 @@ class Client(object):
     def _sock_recv(self, bufsize):
         try:
             return self._sock.recv(bufsize)
-        except socket.error as err:
-            if self._ssl and err.errno == ssl.SSL_ERROR_WANT_READ:
-                raise WouldBlockError()
-            if self._ssl and err.errno == ssl.SSL_ERROR_WANT_WRITE:
-                self._call_socket_register_write()
-                raise WouldBlockError()
-            if err.errno == EAGAIN:
-                raise WouldBlockError()
-            raise
+        except ssl.SSLWantReadError:
+            raise BlockingIOError
+        except ssl.SSLWantWriteError:
+            self._call_socket_register_write()
+            raise BlockingIOError
 
     def _sock_send(self, buf):
         try:
             return self._sock.send(buf)
-        except socket.error as err:
-            if self._ssl and err.errno == ssl.SSL_ERROR_WANT_READ:
-                raise WouldBlockError()
-            if self._ssl and err.errno == ssl.SSL_ERROR_WANT_WRITE:
-                self._call_socket_register_write()
-                raise WouldBlockError()
-            if err.errno == EAGAIN:
-                self._call_socket_register_write()
-                raise WouldBlockError()
-            raise
+        except ssl.SSLWantReadError:
+            raise BlockingIOError
+        except ssl.SSLWantWriteError:
+            self._call_socket_register_write()
+            raise BlockingIOError
+        except BlockingIOError:
+            self._call_socket_register_write()
+            raise BlockingIOError
 
     def _sock_close(self):
         """Close the connection to the server."""
@@ -700,8 +670,9 @@ class Client(object):
             # In case a callback fails, still close the socket to avoid leaking the file descriptor.
             sock.close()
 
-    def _reset_sockets(self):
-        self._sock_close()
+    def _reset_sockets(self, sockpair_only=False):
+        if sockpair_only == False:
+            self._sock_close()
 
         if self._sockpairR:
             self._sockpairR.close()
@@ -761,7 +732,7 @@ class Client(object):
         if hasattr(context, 'check_hostname'):
             self._tls_insecure = not context.check_hostname
 
-    def tls_set(self, ca_certs=None, certfile=None, keyfile=None, cert_reqs=None, tls_version=None, ciphers=None):
+    def tls_set(self, ca_certs=None, certfile=None, keyfile=None, cert_reqs=None, tls_version=None, ciphers=None, keyfile_password=None):
         """Configure network encryption and authentication options. Enables SSL/TLS support.
 
         ca_certs : a string path to the Certificate Authority certificate files
@@ -769,7 +740,7 @@ class Client(object):
         option given then the client will operate in a similar manner to a web
         browser. That is to say it will require the broker to have a
         certificate signed by the Certificate Authorities in ca_certs and will
-        communicate using TLS v1, but will not attempt any form of
+        communicate using TLS v1,2, but will not attempt any form of
         authentication. This provides basic network encryption but may not be
         sufficient depending on how the broker is configured.
         By default, on Python 2.7.9+ or 3.4+, the default certification
@@ -781,8 +752,11 @@ class Client(object):
         None then they will be used as client information for TLS based
         authentication.  Support for this feature is broker dependent. Note
         that if either of these files in encrypted and needs a password to
-        decrypt it, Python will ask for the password at the command line. It is
-        not currently possible to define a callback to provide the password.
+        decrypt it, then this can be passed using the keyfile_password
+        argument - you should take precautions to ensure that your password is
+        not hard coded into your program by loading the password from a file
+        for example. If you do not provide keyfile_password, the password will
+        be requested to be typed in at a terminal window.
 
         cert_reqs allows the certificate requirements that the client imposes
         on the broker to be changed. By default this is ssl.CERT_REQUIRED,
@@ -790,9 +764,8 @@ class Client(object):
         pydoc for more information on this parameter.
 
         tls_version allows the version of the SSL/TLS protocol used to be
-        specified. By default TLS v1 is used. Previous versions (all versions
-        beginning with SSL) are possible but not recommended due to possible
-        security problems.
+        specified. By default TLS v1.2 is used. Previous versions are allowed
+        but not recommended due to possible security problems.
 
         ciphers is a string specifying which encryption ciphers are allowable
         for this connection, or None to use the defaults. See the ssl pydoc for
@@ -812,7 +785,7 @@ class Client(object):
 
         # Create SSLContext object
         if tls_version is None:
-            tls_version = ssl.PROTOCOL_TLSv1
+            tls_version = ssl.PROTOCOL_TLSv1_2
             # If the python version supports it, use highest TLS version automatically
             if hasattr(ssl, "PROTOCOL_TLS"):
                 tls_version = ssl.PROTOCOL_TLS
@@ -820,7 +793,7 @@ class Client(object):
 
         # Configure context
         if certfile is not None:
-            context.load_cert_chain(certfile, keyfile)
+            context.load_cert_chain(certfile, keyfile, keyfile_password)
 
         if cert_reqs == ssl.CERT_NONE and hasattr(context, 'check_hostname'):
             context.check_hostname = False
@@ -920,7 +893,7 @@ class Client(object):
         keepalive: Maximum period in seconds between communications with the
         broker. If no other messages are being exchanged, this controls the
         rate at which the client will send ping messages to the broker.
-        clean_start: (MQTT v5.0 only) True, False or MQTT_CLEAN_START_FIRST_ONLY.  
+        clean_start: (MQTT v5.0 only) True, False or MQTT_CLEAN_START_FIRST_ONLY.
         Sets the MQTT v5.0 clean_start flag always, never or on the first successful connect only,
         respectively.  MQTT session data (such as outstanding messages and subscriptions)
         is cleared on successful connect when the clean_start flag is set.
@@ -994,7 +967,7 @@ class Client(object):
         keepalive: Maximum period in seconds between communications with the
         broker. If no other messages are being exchanged, this controls the
         rate at which the client will send ping messages to the broker.
-        clean_start: (MQTT v5.0 only) True, False or MQTT_CLEAN_START_FIRST_ONLY.  
+        clean_start: (MQTT v5.0 only) True, False or MQTT_CLEAN_START_FIRST_ONLY.
         Sets the MQTT v5.0 clean_start flag always, never or on the first successful connect only,
         respectively.  MQTT session data (such as outstanding messages and subscriptions)
         is cleared on successful connect when the clean_start flag is set.
@@ -1021,7 +994,7 @@ class Client(object):
         self._clean_start = clean_start
         self._connect_properties = properties
         self._state = mqtt_cs_connect_async
-        
+
 
     def reconnect_delay_set(self, min_delay=1, max_delay=120):
         """ Configure the exponential reconnect delay
@@ -1050,15 +1023,11 @@ class Client(object):
             "remaining_count": [],
             "remaining_mult": 1,
             "remaining_length": 0,
-            "packet": b"",
+            "packet": bytearray(b""),
             "to_process": 0,
             "pos": 0}
 
-        with self._out_packet_mutex:
-            self._out_packet = collections.deque()
-
-        with self._current_out_packet_mutex:
-            self._current_out_packet = None
+        self._out_packet = collections.deque()
 
         with self._msgtime_mutex:
             self._last_msg_in = time_func()
@@ -1121,6 +1090,11 @@ class Client(object):
     def loop(self, timeout=1.0, max_packets=1):
         """Process network events.
 
+        It is strongly recommended that you use loop_start(), or
+        loop_forever(), or if you are using an external event loop using
+        loop_read(), loop_write(), and loop_misc(). Using loop() on it's own is
+        no longer recommended.
+
         This function must be called regularly to ensure communication with the
         broker is carried out. It calls select() on the network socket to wait
         for network events. If incoming data is present it will then be
@@ -1138,18 +1112,23 @@ class Client(object):
         Returns >0 on error.
 
         A ValueError will be raised if timeout < 0"""
+
+        if self._sockpairR is None or self._sockpairW is None:
+            self._reset_sockets(sockpair_only=True)
+            self._sockpairR, self._sockpairW = _socketpair_compat()
+
+        return self._loop(timeout)
+
+    def _loop(self, timeout=1.0):
         if timeout < 0.0:
             raise ValueError('Invalid timeout.')
 
-        with self._current_out_packet_mutex:
-            with self._out_packet_mutex:
-                if self._current_out_packet is None and len(self._out_packet) > 0:
-                    self._current_out_packet = self._out_packet.popleft()
-
-                if self._current_out_packet:
-                    wlist = [self._sock]
-                else:
-                    wlist = []
+        try:
+            packet = self._out_packet.popleft()
+            self._out_packet.appendleft(packet)
+            wlist = [self._sock]
+        except IndexError:
+            wlist = []
 
         # used to check if there are any bytes left in the (SSL) socket
         pending_bytes = 0
@@ -1162,7 +1141,11 @@ class Client(object):
 
         # sockpairR is used to break out of select() before the timeout, on a
         # call to publish() etc.
-        rlist = [self._sock, self._sockpairR]
+        if self._sockpairR is None:
+            rlist = [self._sock]
+        else:
+            rlist = [self._sock, self._sockpairR]
+
         try:
             socklist = select.select(rlist, wlist, [], timeout)
         except TypeError:
@@ -1178,23 +1161,24 @@ class Client(object):
             return MQTT_ERR_UNKNOWN
 
         if self._sock in socklist[0] or pending_bytes > 0:
-            rc = self.loop_read(max_packets)
+            rc = self.loop_read()
             if rc or self._sock is None:
                 return rc
 
-        if self._sockpairR in socklist[0]:
+        if self._sockpairR and self._sockpairR in socklist[0]:
             # Stimulate output write even though we didn't ask for it, because
             # at that point the publish or other command wasn't present.
             socklist[1].insert(0, self._sock)
             # Clear sockpairR - only ever a single byte written.
             try:
-                self._sockpairR.recv(1)
-            except socket.error as err:
-                if err.errno != EAGAIN:
-                    raise
+                # Read many bytes at once - this allows up to 10000 calls to
+                # publish() inbetween calls to loop().
+                self._sockpairR.recv(10000)
+            except BlockingIOError:
+                pass
 
         if self._sock in socklist[1]:
-            rc = self.loop_write(max_packets)
+            rc = self.loop_write()
             if rc or self._sock is None:
                 return rc
 
@@ -1234,7 +1218,7 @@ class Client(object):
         is defined.
 
         A ValueError will be raised if topic is None, has zero length or is
-        invalid (contains a wildcard), except if the MQTT version used is v5.0.  
+        invalid (contains a wildcard), except if the MQTT version used is v5.0.
         For v5.0, a zero length topic can be used when a Topic Alias has been set.
 
         A ValueError will be raised if qos is not one of 0, 1 or 2, or if
@@ -1321,10 +1305,10 @@ class Client(object):
         Must be called before connect() to have any effect.
         Requires a broker that supports MQTT v3.1.
 
-        username: The username to authenticate with. Need have no relationship to the client id. Must be unicode    
+        username: The username to authenticate with. Need have no relationship to the client id. Must be unicode
             [MQTT-3.1.3-11].
             Set to None to reset client back to not using username/password for broker authentication.
-        password: The password to authenticate with. Optional, set to None if not required. If it is unicode, then it 
+        password: The password to authenticate with. Optional, set to None if not required. If it is unicode, then it
             will be encoded as UTF-8.
         """
 
@@ -1361,7 +1345,7 @@ class Client(object):
     def disconnect(self, reasoncode=None, properties=None):
         """Disconnect a connected client from the broker.
         reasoncode: (MQTT v5.0 only) a ReasonCodes instance setting the MQTT v5.0
-        reasoncode to be sent with the disconnect.  It is optional, the receiver 
+        reasoncode to be sent with the disconnect.  It is optional, the receiver
         then assuming that 0 (success) is the value.
         properties: (MQTT v5.0 only) a Properties instance setting the MQTT v5.0 properties
         to be included. Optional - if not set, no properties are sent.
@@ -1378,7 +1362,7 @@ class Client(object):
 
         This function may be called in three different ways (and a further three for MQTT v5.0):
 
-        Simple string and integer 
+        Simple string and integer
         -------------------------
         e.g. subscribe("my/topic", 2)
 
@@ -1468,7 +1452,7 @@ class Client(object):
             if qos < 0 or qos > 2:
                 raise ValueError('Invalid QoS level.')
             if self._protocol == MQTTv5:
-                if options == None:
+                if options is None:
                     # if no options are provided, use the QoS passed instead
                     options = SubscribeOptions(qos=qos)
                 elif qos != 0:
@@ -1589,18 +1573,14 @@ class Client(object):
         if self._sock is None:
             return MQTT_ERR_NO_CONN
 
-        max_packets = len(self._out_packet) + 1
-        if max_packets < 1:
-            max_packets = 1
-
         try:
-            for _ in range(0, max_packets):
-                rc = self._packet_write()
-                if rc > 0:
-                    return self._loop_rc_handle(rc)
-                elif rc == MQTT_ERR_AGAIN:
-                    return MQTT_ERR_SUCCESS
-            return MQTT_ERR_SUCCESS
+            rc = self._packet_write()
+            if rc == MQTT_ERR_AGAIN:
+                return MQTT_ERR_SUCCESS
+            elif rc > 0:
+                return self._loop_rc_handle(rc)
+            else:
+                return MQTT_ERR_SUCCESS
         finally:
             if self.want_write():
                 self._call_socket_register_write()
@@ -1611,9 +1591,11 @@ class Client(object):
         """Call to determine if there is network data waiting to be written.
         Useful if you are calling select() yourself rather than using loop().
         """
-        if self._current_out_packet or len(self._out_packet) > 0:
+        try:
+            packet = self._out_packet.popleft()
+            self._out_packet.appendleft(packet)
             return True
-        else:
+        except IndexError:
             return False
 
     def loop_misc(self):
@@ -1626,10 +1608,6 @@ class Client(object):
 
         now = time_func()
         self._check_keepalive()
-        if self._last_retry_check + 1 < now:
-            # Only check once a second at most
-            self._message_retry_check()
-            self._last_retry_check = now
 
         if self._ping_t > 0 and now - self._ping_t >= self._keepalive:
             # client->ping_t != 0 means we are waiting for a pingresp.
@@ -1639,7 +1617,7 @@ class Client(object):
             if self._state == mqtt_cs_disconnecting:
                 rc = MQTT_ERR_SUCCESS
             else:
-                rc = 1
+                rc = MQTT_ERR_KEEPALIVE
 
             self._do_on_disconnect(rc)
 
@@ -1665,12 +1643,8 @@ class Client(object):
         return self
 
     def message_retry_set(self, retry):
-        """Set the timeout in seconds before a message with QoS>0 is retried.
-        20 seconds by default."""
-        if retry < 0:
-            raise ValueError('Invalid retry.')
-
-        self._message_retry = retry
+        """No longer used, remove in version 2.0"""
+        pass
 
     def user_data_set(self, userdata):
         """Set the user data variable passed to callbacks. May be any data type."""
@@ -1739,20 +1713,22 @@ class Client(object):
         return self._sock
 
     def loop_forever(self, timeout=1.0, max_packets=1, retry_first_connection=False):
-        """This function call loop() for you in an infinite blocking loop. It
-        is useful for the case where you only want to run the MQTT client loop
-        in your program.
+        """This function calls the network loop functions for you in an
+        infinite blocking loop. It is useful for the case where you only want
+        to run the MQTT client loop in your program.
 
-        loop_forever() will handle reconnecting for you. If you call
-        disconnect() in a callback it will return.
+        loop_forever() will handle reconnecting for you if reconnect_on_failure is
+        true (this is the default behavior). If you call disconnect() in a callback
+        it will return.
 
 
         timeout: The time in seconds to wait for incoming/outgoing network
           traffic before timing out and returning.
         max_packets: Not currently used.
         retry_first_connection: Should the first connection attempt be retried on failure.
+          This is independent of the reconnect_on_failure setting.
 
-        Raises socket.error on first connection failures unless retry_first_connection=True
+        Raises OSError/WebsocketConnectionError on first connection failures unless retry_first_connection=True
         """
 
         run = True
@@ -1764,7 +1740,8 @@ class Client(object):
             if self._state == mqtt_cs_connect_async:
                 try:
                     self.reconnect()
-                except (socket.error, OSError, WebsocketConnectionError):
+                except (OSError, WebsocketConnectionError):
+                    self._handle_on_connect_fail()
                     if not retry_first_connection:
                         raise
                     self._easy_log(
@@ -1776,14 +1753,12 @@ class Client(object):
         while run:
             rc = MQTT_ERR_SUCCESS
             while rc == MQTT_ERR_SUCCESS:
-                rc = self.loop(timeout, max_packets)
+                rc = self._loop(timeout)
                 # We don't need to worry about locking here, because we've
                 # either called loop_forever() when in single threaded mode, or
                 # in multi threaded mode when loop_stop() has been called and
-                # so no other threads can access _current_out_packet,
-                # _out_packet or _messages.
+                # so no other threads can access _out_packet or _messages.
                 if (self._thread_terminate is True
-                    and self._current_out_packet is None
                     and len(self._out_packet) == 0
                         and len(self._out_messages) == 0):
                     rc = 1
@@ -1792,7 +1767,7 @@ class Client(object):
             def should_exit():
                 return self._state == mqtt_cs_disconnecting or run is False or self._thread_terminate is True
 
-            if should_exit():
+            if should_exit() or not self._reconnect_on_failure:
                 run = False
             else:
                 self._reconnect_wait()
@@ -1802,7 +1777,8 @@ class Client(object):
                 else:
                     try:
                         self.reconnect()
-                    except (socket.error, OSError, WebsocketConnectionError):
+                    except (OSError, WebsocketConnectionError):
+                        self._handle_on_connect_fail()
                         self._easy_log(
                             MQTT_LOG_DEBUG, "Connection failed, retrying")
 
@@ -1816,6 +1792,7 @@ class Client(object):
         if self._thread is not None:
             return MQTT_ERR_INVAL
 
+        self._sockpairR, self._sockpairW = _socketpair_compat()
         self._thread_terminate = False
         self._thread = threading.Thread(target=self._thread_main)
         self._thread.daemon = True
@@ -1855,9 +1832,18 @@ class Client(object):
                     MQTT_LOG_INFO, MQTT_LOG_NOTICE, MQTT_LOG_WARNING,
                     MQTT_LOG_ERR, and MQTT_LOG_DEBUG.
         buf:        the message itself
+
+        Decorator: @client.log_callback() (```client``` is the name of the
+            instance which this callback is being attached to)
         """
         self._on_log = func
 
+    def log_callback(self):
+        def decorator(func):
+            self.on_log = func
+            return func
+        return decorator
+
     @property
     def on_connect(self):
         """If implemented, called when the broker responds to our connection
@@ -1869,7 +1855,7 @@ class Client(object):
         """ Define the connect callback implementation.
 
         Expected signature for MQTT v3.1 and v3.1.1 is:
-            connect_callback(client, userdata, flags, rc, properties=None)
+            connect_callback(client, userdata, flags, rc)
 
         and for MQTT v5.0:
             connect_callback(client, userdata, flags, reasonCode, properties)
@@ -1879,11 +1865,11 @@ class Client(object):
         flags:      response flags sent by the broker
         rc:         the connection result
         reasonCode: the MQTT v5.0 reason code: an instance of the ReasonCode class.
-                    ReasonCode may be compared to interger.
+                    ReasonCode may be compared to integer.
         properties: the MQTT v5.0 properties returned from the broker.  An instance
                     of the Properties class.
                     For MQTT v3.1 and v3.1.1 properties is not provided but for compatibility
-                    with MQTT v5.0, we recommand adding properties=None.
+                    with MQTT v5.0, we recommend adding properties=None.
 
         flags is a dict that contains response flags from the broker:
             flags['session present'] - this flag is useful for clients that are
@@ -1900,10 +1886,49 @@ class Client(object):
             4: Connection refused - bad username or password
             5: Connection refused - not authorised
             6-255: Currently unused.
+
+        Decorator: @client.connect_callback() (```client``` is the name of the
+            instance which this callback is being attached to)
+
         """
         with self._callback_mutex:
             self._on_connect = func
 
+    def connect_callback(self):
+        def decorator(func):
+            self.on_connect = func
+            return func
+        return decorator
+
+    @property
+    def on_connect_fail(self):
+        """If implemented, called when the client failed to connect
+        to the broker."""
+        return self._on_connect_fail
+
+    @on_connect_fail.setter
+    def on_connect_fail(self, func):
+        """ Define the connection failure callback implementation
+
+        Expected signature is:
+            on_connect_fail(client, userdata)
+
+        client:     the client instance for this callback
+        userdata:   the private user data as set in Client() or userdata_set()
+
+        Decorator: @client.connect_fail_callback() (```client``` is the name of the
+            instance which this callback is being attached to)
+
+        """
+        with self._callback_mutex:
+            self._on_connect_fail = func
+
+    def connect_fail_callback(self):
+        def decorator(func):
+            self.on_connect_fail = func
+            return func
+        return decorator
+
     @property
     def on_subscribe(self):
         """If implemented, called when the broker responds to a subscribe
@@ -1912,10 +1937,10 @@ class Client(object):
 
     @on_subscribe.setter
     def on_subscribe(self, func):
-        """ Define the suscribe callback implementation.
+        """ Define the subscribe callback implementation.
 
         Expected signature for MQTT v3.1.1 and v3.1 is:
-            subscribe_callback(client, userdata, mid, granted_qos, properties=None)
+            subscribe_callback(client, userdata, mid, granted_qos)
 
         and for MQTT v5.0:
             subscribe_callback(client, userdata, mid, reasonCodes, properties)
@@ -1930,10 +1955,19 @@ class Client(object):
                         subscription.  A list of ReasonCodes instances.
         properties:     the MQTT v5.0 properties received from the broker.  A
                         list of Properties class instances.
+
+        Decorator: @client.subscribe_callback() (```client``` is the name of the
+            instance which this callback is being attached to)
         """
         with self._callback_mutex:
             self._on_subscribe = func
 
+    def subscribe_callback(self):
+        def decorator(func):
+            self.on_subscribe = func
+            return func
+        return decorator
+
     @property
     def on_message(self):
         """If implemented, called when a message has been received on a topic
@@ -1955,10 +1989,20 @@ class Client(object):
         userdata:   the private user data as set in Client() or userdata_set()
         message:    an instance of MQTTMessage.
                     This is a class with members topic, payload, qos, retain.
+
+        Decorator: @client.message_callback() (```client``` is the name of the
+            instance which this callback is being attached to)
+
         """
         with self._callback_mutex:
             self._on_message = func
 
+    def message_callback(self):
+        def decorator(func):
+            self.on_message = func
+            return func
+        return decorator
+
     @property
     def on_publish(self):
         """If implemented, called when a message that was to be sent using the
@@ -1982,10 +2026,20 @@ class Client(object):
         userdata:   the private user data as set in Client() or userdata_set()
         mid:        matches the mid variable returned from the corresponding
                     publish() call, to allow outgoing messages to be tracked.
+
+        Decorator: @client.publish_callback() (```client``` is the name of the
+            instance which this callback is being attached to)
+
         """
         with self._callback_mutex:
             self._on_publish = func
 
+    def publish_callback(self):
+        def decorator(func):
+            self.on_publish = func
+            return func
+        return decorator
+
     @property
     def on_unsubscribe(self):
         """If implemented, called when the broker responds to an unsubscribe
@@ -2010,10 +2064,19 @@ class Client(object):
                         list of Properties class instances.
         reasonCodes:    the MQTT v5.0 reason codes received from the broker for each
                         unsubscribe topic.  A list of ReasonCodes instances
+
+        Decorator: @client.unsubscribe_callback() (```client``` is the name of the
+            instance which this callback is being attached to)
         """
         with self._callback_mutex:
             self._on_unsubscribe = func
 
+    def unsubscribe_callback(self):
+        def decorator(func):
+            self.on_unsubscribe = func
+            return func
+        return decorator
+
     @property
     def on_disconnect(self):
         """If implemented, called when the client disconnects from the broker.
@@ -2037,10 +2100,20 @@ class Client(object):
                     MQTT_ERR_SUCCESS (0), the callback was called in response to
                     a disconnect() call. If any other value the disconnection
                     was unexpected, such as might be caused by a network error.
+
+        Decorator: @client.disconnect_callback() (```client``` is the name of the
+            instance which this callback is being attached to)
+
         """
         with self._callback_mutex:
             self._on_disconnect = func
 
+    def disconnect_callback(self):
+        def decorator(func):
+            self.on_disconnect = func
+            return func
+        return decorator
+
     @property
     def on_socket_open(self):
         """If implemented, called just after the socket was opend."""
@@ -2058,22 +2131,33 @@ class Client(object):
         client:     the client instance for this callback
         userdata:   the private user data as set in Client() or userdata_set()
         sock:       the socket which was just opened.
+
+        Decorator: @client.socket_open_callback() (```client``` is the name of the
+            instance which this callback is being attached to)
         """
         with self._callback_mutex:
             self._on_socket_open = func
 
+    def socket_open_callback(self):
+        def decorator(func):
+            self.on_socket_open = func
+            return func
+        return decorator
+
     def _call_socket_open(self):
         """Call the socket_open callback with the just-opened socket"""
         with self._callback_mutex:
-            if self.on_socket_open:
-                with self._in_callback_mutex:
-                    try:
-                        self.on_socket_open(self, self._userdata, self._sock)
-                    except Exception as err:
-                        self._easy_log(
-                            MQTT_LOG_ERR, 'Caught exception in on_socket_open: %s', err)
-                        if not self.suppress_exceptions:
-                            raise
+            on_socket_open = self.on_socket_open
+
+        if on_socket_open:
+            with self._in_callback_mutex:
+                try:
+                    on_socket_open(self, self._userdata, self._sock)
+                except Exception as err:
+                    self._easy_log(
+                        MQTT_LOG_ERR, 'Caught exception in on_socket_open: %s', err)
+                    if not self.suppress_exceptions:
+                        raise
 
     @property
     def on_socket_close(self):
@@ -2092,22 +2176,33 @@ class Client(object):
         client:     the client instance for this callback
         userdata:   the private user data as set in Client() or userdata_set()
         sock:       the socket which is about to be closed.
+
+        Decorator: @client.socket_close_callback() (```client``` is the name of the
+            instance which this callback is being attached to)
         """
         with self._callback_mutex:
             self._on_socket_close = func
 
+    def socket_close_callback(self):
+        def decorator(func):
+            self.on_socket_close = func
+            return func
+        return decorator
+
     def _call_socket_close(self, sock):
         """Call the socket_close callback with the about-to-be-closed socket"""
         with self._callback_mutex:
-            if self.on_socket_close:
-                with self._in_callback_mutex:
-                    try:
-                        self.on_socket_close(self, self._userdata, sock)
-                    except Exception as err:
-                        self._easy_log(
-                            MQTT_LOG_ERR, 'Caught exception in on_socket_close: %s', err)
-                        if not self.suppress_exceptions:
-                            raise
+            on_socket_close = self.on_socket_close
+
+        if on_socket_close:
+            with self._in_callback_mutex:
+                try:
+                    on_socket_close(self, self._userdata, sock)
+                except Exception as err:
+                    self._easy_log(
+                        MQTT_LOG_ERR, 'Caught exception in on_socket_close: %s', err)
+                    if not self.suppress_exceptions:
+                        raise
 
     @property
     def on_socket_register_write(self):
@@ -2126,25 +2221,36 @@ class Client(object):
         client:     the client instance for this callback
         userdata:   the private user data as set in Client() or userdata_set()
         sock:       the socket which should be registered for writing
+
+        Decorator: @client.socket_register_write_callback() (```client``` is the name of the
+            instance which this callback is being attached to)
         """
         with self._callback_mutex:
             self._on_socket_register_write = func
 
+    def socket_register_write_callback(self):
+        def decorator(func):
+            self._on_socket_register_write = func
+            return func
+        return decorator
+
     def _call_socket_register_write(self):
         """Call the socket_register_write callback with the unwritable socket"""
         if not self._sock or self._registered_write:
             return
         self._registered_write = True
         with self._callback_mutex:
-            if self.on_socket_register_write:
-                try:
-                    self.on_socket_register_write(
-                        self, self._userdata, self._sock)
-                except Exception as err:
-                    self._easy_log(
-                        MQTT_LOG_ERR, 'Caught exception in on_socket_register_write: %s', err)
-                    if not self.suppress_exceptions:
-                        raise
+            on_socket_register_write = self.on_socket_register_write
+
+        if on_socket_register_write:
+            try:
+                on_socket_register_write(
+                    self, self._userdata, self._sock)
+            except Exception as err:
+                self._easy_log(
+                    MQTT_LOG_ERR, 'Caught exception in on_socket_register_write: %s', err)
+                if not self.suppress_exceptions:
+                    raise
 
     @property
     def on_socket_unregister_write(self):
@@ -2163,10 +2269,19 @@ class Client(object):
         client:     the client instance for this callback
         userdata:   the private user data as set in Client() or userdata_set()
         sock:       the socket which should be unregistered for writing
+
+        Decorator: @client.socket_unregister_write_callback() (```client``` is the name of the
+            instance which this callback is being attached to)
         """
         with self._callback_mutex:
             self._on_socket_unregister_write = func
 
+    def socket_unregister_write_callback(self):
+        def decorator(func):
+            self._on_socket_unregister_write = func
+            return func
+        return decorator
+
     def _call_socket_unregister_write(self, sock=None):
         """Call the socket_unregister_write callback with the writable socket"""
         sock = sock or self._sock
@@ -2175,14 +2290,16 @@ class Client(object):
         self._registered_write = False
 
         with self._callback_mutex:
-            if self.on_socket_unregister_write:
-                try:
-                    self.on_socket_unregister_write(self, self._userdata, sock)
-                except Exception as err:
-                    self._easy_log(
-                        MQTT_LOG_ERR, 'Caught exception in on_socket_unregister_write: %s', err)
-                    if not self.suppress_exceptions:
-                        raise
+            on_socket_unregister_write = self.on_socket_unregister_write
+
+        if on_socket_unregister_write:
+            try:
+                on_socket_unregister_write(self, self._userdata, sock)
+            except Exception as err:
+                self._easy_log(
+                    MQTT_LOG_ERR, 'Caught exception in on_socket_unregister_write: %s', err)
+                if not self.suppress_exceptions:
+                    raise
 
     def message_callback_add(self, sub, callback):
         """Register a message callback for a specific topic.
@@ -2201,6 +2318,12 @@ class Client(object):
         with self._callback_mutex:
             self._on_message_filtered[sub] = callback
 
+    def topic_callback(self, sub):
+        def decorator(func):
+            self.message_callback_add(sub, func)
+            return func
+        return decorator
+
     def message_callback_remove(self, sub):
         """Remove a message callback previously registered with
         message_callback_add()."""
@@ -2245,15 +2368,15 @@ class Client(object):
         if self._in_packet['command'] == 0:
             try:
                 command = self._sock_recv(1)
-            except WouldBlockError:
+            except BlockingIOError:
                 return MQTT_ERR_AGAIN
-            except socket.error as err:
+            except ConnectionError as err:
                 self._easy_log(
                     MQTT_LOG_ERR, 'failed to receive on socket: %s', err)
-                return 1
+                return MQTT_ERR_CONN_LOST
             else:
                 if len(command) == 0:
-                    return 1
+                    return MQTT_ERR_CONN_LOST
                 command, = struct.unpack("!B", command)
                 self._in_packet['command'] = command
 
@@ -2264,15 +2387,15 @@ class Client(object):
             while True:
                 try:
                     byte = self._sock_recv(1)
-                except WouldBlockError:
+                except BlockingIOError:
                     return MQTT_ERR_AGAIN
-                except socket.error as err:
+                except ConnectionError as err:
                     self._easy_log(
                         MQTT_LOG_ERR, 'failed to receive on socket: %s', err)
-                    return 1
+                    return MQTT_ERR_CONN_LOST
                 else:
                     if len(byte) == 0:
-                        return 1
+                        return MQTT_ERR_CONN_LOST
                     byte, = struct.unpack("!B", byte)
                     self._in_packet['remaining_count'].append(byte)
                     # Max 4 bytes length for remaining length as defined by protocol.
@@ -2290,20 +2413,26 @@ class Client(object):
             self._in_packet['have_remaining'] = 1
             self._in_packet['to_process'] = self._in_packet['remaining_length']
 
+        count = 100 # Don't get stuck in this loop if we have a huge message.
         while self._in_packet['to_process'] > 0:
             try:
                 data = self._sock_recv(self._in_packet['to_process'])
-            except WouldBlockError:
+            except BlockingIOError:
                 return MQTT_ERR_AGAIN
-            except socket.error as err:
+            except ConnectionError as err:
                 self._easy_log(
                     MQTT_LOG_ERR, 'failed to receive on socket: %s', err)
-                return 1
+                return MQTT_ERR_CONN_LOST
             else:
                 if len(data) == 0:
-                    return 1
+                    return MQTT_ERR_CONN_LOST
                 self._in_packet['to_process'] -= len(data)
                 self._in_packet['packet'] += data
+            count -= 1
+            if count == 0:
+                with self._msgtime_mutex:
+                    self._last_msg_in = time_func()
+                return MQTT_ERR_AGAIN
 
         # All data for this packet is read.
         self._in_packet['pos'] = 0
@@ -2316,7 +2445,7 @@ class Client(object):
             'remaining_count': [],
             'remaining_mult': 1,
             'remaining_length': 0,
-            'packet': b"",
+            'packet': bytearray(b""),
             'to_process': 0,
             'pos': 0}
 
@@ -2325,25 +2454,26 @@ class Client(object):
         return rc
 
     def _packet_write(self):
-        self._current_out_packet_mutex.acquire()
-
-        while self._current_out_packet:
-            packet = self._current_out_packet
+        while True:
+            try:
+                packet = self._out_packet.popleft()
+            except IndexError:
+                return MQTT_ERR_SUCCESS
 
             try:
                 write_length = self._sock_send(
                     packet['packet'][packet['pos']:])
             except (AttributeError, ValueError):
-                self._current_out_packet_mutex.release()
+                self._out_packet.appendleft(packet)
                 return MQTT_ERR_SUCCESS
-            except WouldBlockError:
-                self._current_out_packet_mutex.release()
+            except BlockingIOError:
+                self._out_packet.appendleft(packet)
                 return MQTT_ERR_AGAIN
-            except socket.error as err:
-                self._current_out_packet_mutex.release()
+            except ConnectionError as err:
+                self._out_packet.appendleft(packet)
                 self._easy_log(
                     MQTT_LOG_ERR, 'failed to receive on socket: %s', err)
-                return 1
+                return MQTT_ERR_CONN_LOST
 
             if write_length > 0:
                 packet['to_process'] -= write_length
@@ -2352,40 +2482,35 @@ class Client(object):
                 if packet['to_process'] == 0:
                     if (packet['command'] & 0xF0) == PUBLISH and packet['qos'] == 0:
                         with self._callback_mutex:
-                            if self.on_publish:
-                                with self._in_callback_mutex:
-                                    try:
-                                        self.on_publish(
-                                            self, self._userdata, packet['mid'])
-                                    except Exception as err:
-                                        self._easy_log(
-                                            MQTT_LOG_ERR, 'Caught exception in on_publish: %s', err)
-                                        if not self.suppress_exceptions:
-                                            raise
+                            on_publish = self.on_publish
+
+                        if on_publish:
+                            with self._in_callback_mutex:
+                                try:
+                                    on_publish(
+                                        self, self._userdata, packet['mid'])
+                                except Exception as err:
+                                    self._easy_log(
+                                        MQTT_LOG_ERR, 'Caught exception in on_publish: %s', err)
+                                    if not self.suppress_exceptions:
+                                        raise
 
                         packet['info']._set_as_published()
 
                     if (packet['command'] & 0xF0) == DISCONNECT:
-                        self._current_out_packet_mutex.release()
-
                         with self._msgtime_mutex:
                             self._last_msg_out = time_func()
 
-                        self._do_on_disconnect(0)
-
+                        self._do_on_disconnect(MQTT_ERR_SUCCESS)
                         self._sock_close()
                         return MQTT_ERR_SUCCESS
 
-                    with self._out_packet_mutex:
-                        if len(self._out_packet) > 0:
-                            self._current_out_packet = self._out_packet.popleft()
-                        else:
-                            self._current_out_packet = None
+                else:
+                    # We haven't finished with this packet
+                    self._out_packet.appendleft(packet)
             else:
                 break
 
-        self._current_out_packet_mutex.release()
-
         with self._msgtime_mutex:
             self._last_msg_out = time_func()
 
@@ -2415,17 +2540,22 @@ class Client(object):
 
         if self._sock is not None and (now - last_msg_out >= self._keepalive or now - last_msg_in >= self._keepalive):
             if self._state == mqtt_cs_connected and self._ping_t == 0:
-                self._send_pingreq()
-                with self._msgtime_mutex:
-                    self._last_msg_out = now
-                    self._last_msg_in = now
+                try:
+                    self._send_pingreq()
+                except Exception:
+                    self._sock_close()
+                    self._do_on_disconnect(MQTT_ERR_CONN_LOST)
+                else:
+                    with self._msgtime_mutex:
+                        self._last_msg_out = now
+                        self._last_msg_in = now
             else:
                 self._sock_close()
 
                 if self._state == mqtt_cs_disconnecting:
                     rc = MQTT_ERR_SUCCESS
                 else:
-                    rc = 1
+                    rc = MQTT_ERR_KEEPALIVE
 
                 self._do_on_disconnect(rc)
 
@@ -2542,7 +2672,7 @@ class Client(object):
             remaining_length += 2
 
         if self._protocol == MQTTv5:
-            if properties == None:
+            if properties is None:
                 packed_properties = b'\x00'
             else:
                 packed_properties = properties.pack()
@@ -2616,13 +2746,13 @@ class Client(object):
                 remaining_length += 2 + len(self._password)
 
         if self._protocol == MQTTv5:
-            if self._connect_properties == None:
+            if self._connect_properties is None:
                 packed_connect_properties = b'\x00'
             else:
                 packed_connect_properties = self._connect_properties.pack()
             remaining_length += len(packed_connect_properties)
             if self._will:
-                if self._will_properties == None:
+                if self._will_properties is None:
                     packed_will_properties = b'\x00'
                 else:
                     packed_will_properties = self._will_properties.pack()
@@ -2704,11 +2834,11 @@ class Client(object):
         packet.append(command)
 
         if self._protocol == MQTTv5:
-            if properties != None or reasoncode != None:
-                if reasoncode == None:
+            if properties is not None or reasoncode is not None:
+                if reasoncode is None:
                     reasoncode = ReasonCodes(DISCONNECT >> 4, identifier=0)
                 remaining_length += 1
-                if properties != None:
+                if properties is not None:
                     packed_props = properties.pack()
                     remaining_length += len(packed_props)
 
@@ -2725,7 +2855,7 @@ class Client(object):
     def _send_subscribe(self, dup, topics, properties=None):
         remaining_length = 2
         if self._protocol == MQTTv5:
-            if properties == None:
+            if properties is None:
                 packed_subscribe_properties = b'\x00'
             else:
                 packed_subscribe_properties = properties.pack()
@@ -2762,7 +2892,7 @@ class Client(object):
     def _send_unsubscribe(self, dup, topics, properties=None):
         remaining_length = 2
         if self._protocol == MQTTv5:
-            if properties == None:
+            if properties is None:
                 packed_unsubscribe_properties = b'\x00'
             else:
                 packed_unsubscribe_properties = properties.pack()
@@ -2803,36 +2933,6 @@ class Client(object):
             )
         return (self._packet_queue(command, packet, local_mid, 1), local_mid)
 
-    def _message_retry_check_actual(self, messages, mutex):
-        with mutex:
-            now = time_func()
-            for m in messages.values():
-                if m.timestamp + self._message_retry < now:
-                    if m.state == mqtt_ms_wait_for_puback or m.state == mqtt_ms_wait_for_pubrec:
-                        m.timestamp = now
-                        m.dup = True
-                        self._send_publish(
-                            m.mid,
-                            m.topic.encode('utf-8'),
-                            m.payload,
-                            m.qos,
-                            m.retain,
-                            m.dup,
-                            properties=m.properties,
-                        )
-                    elif m.state == mqtt_ms_wait_for_pubrel:
-                        m.timestamp = now
-                        self._send_pubrec(m.mid)
-                    elif m.state == mqtt_ms_wait_for_pubcomp:
-                        m.timestamp = now
-                        self._send_pubrel(m.mid)
-
-    def _message_retry_check(self):
-        self._message_retry_check_actual(
-            self._out_messages, self._out_message_mutex)
-        self._message_retry_check_actual(
-            self._in_messages, self._in_message_mutex)
-
     def _check_clean_session(self):
         if self._protocol == MQTTv5:
             if self._clean_start == MQTT_CLEAN_START_FIRST_ONLY:
@@ -2898,22 +2998,19 @@ class Client(object):
             'packet': packet,
             'info': info}
 
-        with self._out_packet_mutex:
-            self._out_packet.append(mpkt)
-            if self._current_out_packet_mutex.acquire(False):
-                if self._current_out_packet is None and len(self._out_packet) > 0:
-                    self._current_out_packet = self._out_packet.popleft()
-                self._current_out_packet_mutex.release()
+        self._out_packet.append(mpkt)
 
         # Write a single byte to sockpairW (connected to sockpairR) to break
         # out of select() if in threaded mode.
-        try:
-            self._sockpairW.send(sockpair_data)
-        except socket.error as err:
-            if err.errno != EAGAIN:
-                raise
+        if self._sockpairW is not None:
+            try:
+                self._sockpairW.send(sockpair_data)
+            except BlockingIOError:
+                pass
 
-        if self._thread is None:
+        # If we have an external event loop registered, use that instead
+        # of calling loop_write() directly.
+        if self._thread is None and self._on_socket_register_write is None:
             if self._in_callback_mutex.acquire(False):
                 self._in_callback_mutex.release()
                 return self.loop_write()
@@ -2977,13 +3074,21 @@ class Client(object):
         if self._protocol == MQTTv5:
             (flags, result) = struct.unpack(
                 "!BB", self._in_packet['packet'][:2])
-            reason = ReasonCodes(CONNACK >> 4, identifier=result)
-            properties = Properties(CONNACK >> 4)
-            properties.unpack(self._in_packet['packet'][2:])
+            if result == 1:
+                # This is probably a failure from a broker that doesn't support
+                # MQTT v5.
+                reason = 132 # Unsupported protocol version
+                properties = None
+            else:
+                reason = ReasonCodes(CONNACK >> 4, identifier=result)
+                properties = Properties(CONNACK >> 4)
+                properties.unpack(self._in_packet['packet'][2:])
         else:
             (flags, result) = struct.unpack("!BB", self._in_packet['packet'])
         if self._protocol == MQTTv311:
             if result == CONNACK_REFUSED_PROTOCOL_VERSION:
+                if not self._reconnect_on_failure:
+                    return MQTT_ERR_PROTOCOL
                 self._easy_log(
                     MQTT_LOG_DEBUG,
                     "Received CONNACK (%s, %s), attempting downgrade to MQTT v3.1.",
@@ -2994,6 +3099,8 @@ class Client(object):
                 return self.reconnect()
             elif (result == CONNACK_REFUSED_IDENTIFIER_REJECTED
                     and self._client_id == b''):
+                if not self._reconnect_on_failure:
+                    return MQTT_ERR_PROTOCOL
                 self._easy_log(
                     MQTT_LOG_DEBUG,
                     "Received CONNACK (%s, %s), attempting to use non-empty CID",
@@ -3017,22 +3124,24 @@ class Client(object):
         self._mqttv5_first_connect = False
 
         with self._callback_mutex:
-            if self.on_connect:
-                flags_dict = {}
-                flags_dict['session present'] = flags & 0x01
-                with self._in_callback_mutex:
-                    try:
-                        if self._protocol == MQTTv5:
-                            self.on_connect(self, self._userdata,
-                                            flags_dict, reason, properties)
-                        else:
-                            self.on_connect(
-                                self, self._userdata, flags_dict, result)
-                    except Exception as err:
-                        self._easy_log(
-                            MQTT_LOG_ERR, 'Caught exception in on_connect: %s', err)
-                        if not self.suppress_exceptions:
-                            raise
+            on_connect = self.on_connect
+
+        if on_connect:
+            flags_dict = {}
+            flags_dict['session present'] = flags & 0x01
+            with self._in_callback_mutex:
+                try:
+                    if self._protocol == MQTTv5:
+                        on_connect(self, self._userdata,
+                                        flags_dict, reason, properties)
+                    else:
+                        on_connect(
+                            self, self._userdata, flags_dict, result)
+                except Exception as err:
+                    self._easy_log(
+                        MQTT_LOG_ERR, 'Caught exception in on_connect: %s', err)
+                    if not self.suppress_exceptions:
+                        raise
 
         if result == 0:
             rc = 0
@@ -3140,20 +3249,22 @@ class Client(object):
             granted_qos = struct.unpack(pack_format, packet)
 
         with self._callback_mutex:
-            if self.on_subscribe:
-                with self._in_callback_mutex:  # Don't call loop_write after _send_publish()
-                    try:
-                        if self._protocol == MQTTv5:
-                            self.on_subscribe(
-                                self, self._userdata, mid, reasoncodes, properties)
-                        else:
-                            self.on_subscribe(
-                                self, self._userdata, mid, granted_qos)
-                    except Exception as err:
-                        self._easy_log(
-                            MQTT_LOG_ERR, 'Caught exception in on_subscribe: %s', err)
-                        if not self.suppress_exceptions:
-                            raise
+            on_subscribe = self.on_subscribe
+
+        if on_subscribe:
+            with self._in_callback_mutex:  # Don't call loop_write after _send_publish()
+                try:
+                    if self._protocol == MQTTv5:
+                        on_subscribe(
+                            self, self._userdata, mid, reasoncodes, properties)
+                    else:
+                        on_subscribe(
+                            self, self._userdata, mid, granted_qos)
+                except Exception as err:
+                    self._easy_log(
+                        MQTT_LOG_ERR, 'Caught exception in on_subscribe: %s', err)
+                    if not self.suppress_exceptions:
+                        raise
 
         return MQTT_ERR_SUCCESS
 
@@ -3216,9 +3327,8 @@ class Client(object):
             self._handle_on_message(message)
             return MQTT_ERR_SUCCESS
         elif message.qos == 1:
-            rc = self._send_puback(message.mid)
             self._handle_on_message(message)
-            return rc
+            return self._send_puback(message.mid)
         elif message.qos == 2:
             rc = self._send_pubrec(message.mid)
             message.state = mqtt_ms_wait_for_pubrel
@@ -3333,48 +3443,55 @@ class Client(object):
 
         self._easy_log(MQTT_LOG_DEBUG, "Received UNSUBACK (Mid: %d)", mid)
         with self._callback_mutex:
-            if self.on_unsubscribe:
-                with self._in_callback_mutex:
-                    try:
-                        if self._protocol == MQTTv5:
-                            self.on_unsubscribe(
-                                self, self._userdata, mid, properties, reasoncodes)
-                        else:
-                            self.on_unsubscribe(self, self._userdata, mid)
-                    except Exception as err:
-                        self._easy_log(
-                            MQTT_LOG_ERR, 'Caught exception in on_unsubscribe: %s', err)
-                        if not self.suppress_exceptions:
-                            raise
+            on_unsubscribe = self.on_unsubscribe
+
+        if on_unsubscribe:
+            with self._in_callback_mutex:
+                try:
+                    if self._protocol == MQTTv5:
+                        on_unsubscribe(
+                            self, self._userdata, mid, properties, reasoncodes)
+                    else:
+                        on_unsubscribe(self, self._userdata, mid)
+                except Exception as err:
+                    self._easy_log(
+                        MQTT_LOG_ERR, 'Caught exception in on_unsubscribe: %s', err)
+                    if not self.suppress_exceptions:
+                        raise
+
         return MQTT_ERR_SUCCESS
 
     def _do_on_disconnect(self, rc, properties=None):
         with self._callback_mutex:
-            if self.on_disconnect:
-                with self._in_callback_mutex:
-                    try:
-                        if properties:
-                            self.on_disconnect(
-                                self, self._userdata, rc, properties)
-                        else:
-                            self.on_disconnect(self, self._userdata, rc)
-                    except Exception as err:
-                        self._easy_log(
-                            MQTT_LOG_ERR, 'Caught exception in on_disconnect: %s', err)
-                        if not self.suppress_exceptions:
-                            raise
+            on_disconnect = self.on_disconnect
+
+        if on_disconnect:
+            with self._in_callback_mutex:
+                try:
+                    if self._protocol == MQTTv5:
+                        on_disconnect(
+                            self, self._userdata, rc, properties)
+                    else:
+                        on_disconnect(self, self._userdata, rc)
+                except Exception as err:
+                    self._easy_log(
+                        MQTT_LOG_ERR, 'Caught exception in on_disconnect: %s', err)
+                    if not self.suppress_exceptions:
+                        raise
 
     def _do_on_publish(self, mid):
         with self._callback_mutex:
-            if self.on_publish:
-                with self._in_callback_mutex:
-                    try:
-                        self.on_publish(self, self._userdata, mid)
-                    except Exception as err:
-                        self._easy_log(
-                            MQTT_LOG_ERR, 'Caught exception in on_publish: %s', err)
-                        if not self.suppress_exceptions:
-                            raise
+            on_publish = self.on_publish
+
+        if on_publish:
+            with self._in_callback_mutex:
+                try:
+                    on_publish(self, self._userdata, mid)
+                except Exception as err:
+                    self._easy_log(
+                        MQTT_LOG_ERR, 'Caught exception in on_publish: %s', err)
+                    if not self.suppress_exceptions:
+                        raise
 
         msg = self._out_messages.pop(mid)
         msg.info._set_as_published()
@@ -3416,37 +3533,59 @@ class Client(object):
 
     def _handle_on_message(self, message):
         matched = False
-        with self._callback_mutex:
-            try:
-                topic = message.topic
-            except UnicodeDecodeError:
-                topic = None
 
+        try:
+            topic = message.topic
+        except UnicodeDecodeError:
+            topic = None
+
+        on_message_callbacks = []
+        with self._callback_mutex:
             if topic is not None:
                 for callback in self._on_message_filtered.iter_match(message.topic):
-                    with self._in_callback_mutex:
-                        try:
-                            callback(self, self._userdata, message)
-                        except Exception as err:
-                            self._easy_log(
-                                MQTT_LOG_ERR,
-                                'Caught exception in user defined callback function %s: %s',
-                                callback.__name__,
-                                err
-                            )
-                            if not self.suppress_exceptions:
-                                raise
-                    matched = True
+                    on_message_callbacks.append(callback)
 
-            if matched == False and self.on_message:
-                with self._in_callback_mutex:
-                    try:
-                        self.on_message(self, self._userdata, message)
-                    except Exception as err:
-                        self._easy_log(
-                            MQTT_LOG_ERR, 'Caught exception in on_message: %s', err)
-                        if not self.suppress_exceptions:
-                            raise
+            if len(on_message_callbacks) == 0:
+                on_message = self.on_message
+            else:
+                on_message = None
+
+        for callback in on_message_callbacks:
+            with self._in_callback_mutex:
+                try:
+                    callback(self, self._userdata, message)
+                except Exception as err:
+                    self._easy_log(
+                        MQTT_LOG_ERR,
+                        'Caught exception in user defined callback function %s: %s',
+                        callback.__name__,
+                        err
+                    )
+                    if not self.suppress_exceptions:
+                        raise
+
+        if on_message:
+            with self._in_callback_mutex:
+                try:
+                    on_message(self, self._userdata, message)
+                except Exception as err:
+                    self._easy_log(
+                        MQTT_LOG_ERR, 'Caught exception in on_message: %s', err)
+                    if not self.suppress_exceptions:
+                        raise
+
+
+    def _handle_on_connect_fail(self):
+        with self._callback_mutex:
+            on_connect_fail = self.on_connect_fail
+
+        if on_connect_fail:
+            with self._in_callback_mutex:
+                try:
+                    on_connect_fail(self, self._userdata)
+                except Exception as err:
+                    self._easy_log(
+                        MQTT_LOG_ERR, 'Caught exception in on_connect_fail: %s', err)
 
     def _thread_main(self):
         self.loop_forever(retry_first_connection=True)
@@ -3538,18 +3677,12 @@ class Client(object):
         if sys.version_info < (2, 7) or (3, 0) < sys.version_info < (3, 2):
             # Have to short-circuit here because of unsupported source_address
             # param in earlier Python versions.
-            return socket.create_connection(addr, timeout=self._keepalive)
+            return socket.create_connection(addr, timeout=self._connect_timeout)
 
         if proxy:
-            return socks.create_connection(addr, source_address=source, timeout=self._keepalive, **proxy)
+            return socks.create_connection(addr, timeout=self._connect_timeout, source_address=source, **proxy)
         else:
-            return socket.create_connection(addr, source_address=source, timeout=self._keepalive)
-
-
-# Compatibility class for easy porting from mosquitto.py.
-class Mosquitto(Client):
-    def __init__(self, client_id="", clean_session=True, userdata=None):
-        super(Mosquitto, self).__init__(client_id, clean_session, userdata)
+            return socket.create_connection(addr, timeout=self._connect_timeout, source_address=source)
 
 
 class WebsocketWrapper(object):
@@ -3703,19 +3836,19 @@ class WebsocketWrapper(object):
 
     def _buffered_read(self, length):
 
-        # try to recv and strore needed bytes
+        # try to recv and store needed bytes
         wanted_bytes = length - (len(self._readbuffer) - self._readbuffer_head)
         if wanted_bytes > 0:
 
             data = self._socket.recv(wanted_bytes)
 
             if not data:
-                raise socket.error(errno.ECONNABORTED, 0)
+                raise ConnectionAbortedError
             else:
                 self._readbuffer.extend(data)
 
             if len(data) < wanted_bytes:
-                raise socket.error(EAGAIN, 0)
+                raise BlockingIOError
 
         self._readbuffer_head += length
         return self._readbuffer[self._readbuffer_head - length:self._readbuffer_head]
@@ -3791,19 +3924,17 @@ class WebsocketWrapper(object):
                         WebsocketWrapper.OPCODE_PONG, payload, 0)
                     self._socket.send(frame)
 
-            if opcode == WebsocketWrapper.OPCODE_BINARY and payload_length > 0:
+            # This isn't *proper* handling of continuation frames, but given
+            # that we only support binary frames, it is *probably* good enough.
+            if (opcode == WebsocketWrapper.OPCODE_BINARY or opcode == WebsocketWrapper.OPCODE_CONTINUATION) \
+                    and payload_length > 0:
                 return result
             else:
-                raise socket.error(EAGAIN, 0)
-
-        except socket.error as err:
+                raise BlockingIOError
 
-            if err.errno == errno.ECONNABORTED:
-                self.connected = False
-                return b''
-            else:
-                # no more data
-                raise
+        except ConnectionError:
+            self.connected = False
+            return b''
 
     def _send_impl(self, data):
 
diff --git a/src/paho/mqtt/matcher.py b/src/paho/mqtt/matcher.py
index 7fc966a..01ce295 100644
--- a/src/paho/mqtt/matcher.py
+++ b/src/paho/mqtt/matcher.py
@@ -1,9 +1,9 @@
 class MQTTMatcher(object):
     """Intended to manage topic filters including wildcards.
 
-    Internally, MQTTMatcher use a prefix tree (trie) to store 
-    values associated with filters, and has an iter_match() 
-    method to iterate efficiently over all filters that match 
+    Internally, MQTTMatcher use a prefix tree (trie) to store
+    values associated with filters, and has an iter_match()
+    method to iterate efficiently over all filters that match
     some topic name."""
 
     class Node(object):
@@ -55,7 +55,7 @@ class MQTTMatcher(object):
                 del parent._children[k]
 
     def iter_match(self, topic):
-        """Return an iterator on all values associated with filters 
+        """Return an iterator on all values associated with filters
         that match the :topic"""
         lst = topic.split('/')
         normal = not topic.startswith('$')
diff --git a/src/paho/mqtt/packettypes.py b/src/paho/mqtt/packettypes.py
index 7eb4069..2fd6a1b 100644
--- a/src/paho/mqtt/packettypes.py
+++ b/src/paho/mqtt/packettypes.py
@@ -3,7 +3,7 @@
   Copyright (c) 2017, 2019 IBM Corp.
 
   All rights reserved. This program and the accompanying materials
-  are made available under the terms of the Eclipse Public License v1.0
+  are made available under the terms of the Eclipse Public License v2.0
   and Eclipse Distribution License v1.0 which accompany this distribution.
 
   The Eclipse Public License is available at
diff --git a/src/paho/mqtt/properties.py b/src/paho/mqtt/properties.py
index 99f654a..dbcf543 100644
--- a/src/paho/mqtt/properties.py
+++ b/src/paho/mqtt/properties.py
@@ -3,7 +3,7 @@
   Copyright (c) 2017, 2019 IBM Corp.
 
   All rights reserved. This program and the accompanying materials
-  are made available under the terms of the Eclipse Public License v1.0
+  are made available under the terms of the Eclipse Public License v2.0
   and Eclipse Distribution License v1.0 which accompany this distribution.
 
   The Eclipse Public License is available at
@@ -16,7 +16,8 @@
 *******************************************************************
 """
 
-import sys, struct
+import struct
+import sys
 
 from .packettypes import PacketTypes
 
@@ -268,6 +269,30 @@ class Properties(object):
             if self.packetType not in self.properties[self.getIdentFromName(name)][1]:
                 raise MQTTException("Property %s does not apply to packet type %s"
                                     % (name, PacketTypes.Names[self.packetType]))
+
+            # Check for forbidden values
+            if type(value) != type([]):
+                if name in ["ReceiveMaximum", "TopicAlias"] \
+                        and (value < 1 or value > 65535):
+
+                    raise MQTTException(
+                        "%s property value must be in the range 1-65535" % (name))
+                elif name in ["TopicAliasMaximum"] \
+                        and (value < 0 or value > 65535):
+
+                    raise MQTTException(
+                        "%s property value must be in the range 0-65535" % (name))
+                elif name in ["MaximumPacketSize", "SubscriptionIdentifier"] \
+                        and (value < 1 or value > 268435455):
+
+                    raise MQTTException(
+                        "%s property value must be in the range 1-268435455" % (name))
+                elif name in ["RequestResponseInformation", "RequestProblemInformation", "PayloadFormatIndicator"] \
+                        and (value != 0 and value != 1):
+
+                    raise MQTTException(
+                        "%s property value must be 0 or 1" % (name))
+
             if self.allowsMultiple(name):
                 if type(value) != type([]):
                     value = [value]
@@ -294,7 +319,11 @@ class Properties(object):
         for name in self.names.keys():
             compressedName = name.replace(' ', '')
             if hasattr(self, compressedName):
-                data[compressedName] = getattr(self, compressedName)
+                val = getattr(self, compressedName)
+                if compressedName == 'CorrelationData' and isinstance(val, bytes):
+                    data[compressedName] = val.hex()
+                else:
+                    data[compressedName] = val
         return data
 
     def isEmpty(self):
@@ -391,10 +420,10 @@ class Properties(object):
         buffer = buffer[VBIlen:]  # strip the bytes used by the VBI
         propslenleft = propslen
         while propslenleft > 0:  # properties length is 0 if there are none
-            identifier, VBIlen = VariableByteIntegers.decode(
+            identifier, VBIlen2 = VariableByteIntegers.decode(
                 buffer)  # property identifier
-            buffer = buffer[VBIlen:]  # strip the bytes used by the VBI
-            propslenleft -= VBIlen
+            buffer = buffer[VBIlen2:]  # strip the bytes used by the VBI
+            propslenleft -= VBIlen2
             attr_type = self.properties[identifier][0]
             value, valuelen = self.readProperty(
                 buffer, attr_type, propslenleft)
diff --git a/src/paho/mqtt/publish.py b/src/paho/mqtt/publish.py
index dcb34ff..6d1589a 100644
--- a/src/paho/mqtt/publish.py
+++ b/src/paho/mqtt/publish.py
@@ -1,7 +1,7 @@
 # Copyright (c) 2014 Roger Light <roger@atchoo.org>
 #
 # All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Eclipse Public License v1.0
+# are made available under the terms of the Eclipse Public License v2.0
 # and Eclipse Distribution License v1.0 which accompany this distribution.
 #
 # The Eclipse Public License is available at
@@ -21,13 +21,15 @@ broker, then disconnect and nothing else is required.
 from __future__ import absolute_import
 
 import collections
+
 try:
     from collections.abc import Iterable
 except ImportError:
     from collections import Iterable
 
-from . import client as paho
 from .. import mqtt
+from . import client as paho
+
 
 def _do_publish(client):
     """Internal function"""
@@ -52,6 +54,9 @@ def _on_connect(client, userdata, flags, rc):
     else:
         raise mqtt.MQTTException(paho.connack_string(rc))
 
+def _on_connect_v5(client, userdata, flags, rc, properties):
+    """Internal v5 callback"""
+    _on_connect(client, userdata, flags, rc)
 
 def _on_publish(client, userdata, mid):
     """Internal callback"""
@@ -131,11 +136,15 @@ def multiple(msgs, hostname="localhost", port=1883, client_id="", keepalive=60,
     if not isinstance(msgs, Iterable):
         raise TypeError('msgs must be an iterable')
 
+
     client = paho.Client(client_id=client_id, userdata=collections.deque(msgs),
                          protocol=protocol, transport=transport)
 
     client.on_publish = _on_publish
-    client.on_connect = _on_connect
+    if protocol == mqtt.client.MQTTv5:
+        client.on_connect = _on_connect_v5
+    else:
+        client.on_connect = _on_connect
 
     if proxy_args is not None:
         client.proxy_set(**proxy_args)
diff --git a/src/paho/mqtt/reasoncodes.py b/src/paho/mqtt/reasoncodes.py
index 12325bc..c42e5ba 100644
--- a/src/paho/mqtt/reasoncodes.py
+++ b/src/paho/mqtt/reasoncodes.py
@@ -3,7 +3,7 @@
   Copyright (c) 2017, 2019 IBM Corp.
 
   All rights reserved. This program and the accompanying materials
-  are made available under the terms of the Eclipse Public License v1.0
+  are made available under the terms of the Eclipse Public License v2.0
   and Eclipse Distribution License v1.0 which accompany this distribution.
 
   The Eclipse Public License is available at
@@ -17,13 +17,14 @@
 """
 
 import sys
+
 from .packettypes import PacketTypes
 
 
 class ReasonCodes:
     """MQTT version 5.0 reason codes class.
 
-    See ReasonCodes.names for a list of possible numeric values along with their 
+    See ReasonCodes.names for a list of possible numeric values along with their
     names and the packets to which they apply.
 
     """
@@ -37,7 +38,7 @@ class ReasonCodes:
         aName: the String name of the reason code to be created.  Ignored
             if the identifier is set.
 
-        identifier: an integer value of the reason code to be created.  
+        identifier: an integer value of the reason code to be created.
 
         """
 
@@ -120,7 +121,7 @@ class ReasonCodes:
         }
         if identifier == -1:
             if packetType == PacketTypes.DISCONNECT and aName == "Success":
-                  aName = "Normal disconnection"
+                aName = "Normal disconnection"
             self.set(aName)
         else:
             self.value = identifier
@@ -153,7 +154,7 @@ class ReasonCodes:
                 if self.packetType in self.names[code][name]:
                     identifier = code
                 break
-        assert identifier != None, name
+        assert identifier is not None, name
         return identifier
 
     def set(self, name):
@@ -188,4 +189,4 @@ class ReasonCodes:
         return self.getName()
 
     def pack(self):
-        return bytearray([self.value])
\ No newline at end of file
+        return bytearray([self.value])
diff --git a/src/paho/mqtt/subscribe.py b/src/paho/mqtt/subscribe.py
index 8900a6b..643df9c 100644
--- a/src/paho/mqtt/subscribe.py
+++ b/src/paho/mqtt/subscribe.py
@@ -1,7 +1,7 @@
 # Copyright (c) 2016 Roger Light <roger@atchoo.org>
 #
 # All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Eclipse Public License v1.0
+# are made available under the terms of the Eclipse Public License v2.0
 # and Eclipse Distribution License v1.0 which accompany this distribution.
 #
 # The Eclipse Public License is available at
@@ -20,10 +20,11 @@ you to pass a callback for processing of messages.
 """
 from __future__ import absolute_import
 
-from . import client as paho
 from .. import mqtt
+from . import client as paho
 
-def _on_connect(client, userdata, flags, rc):
+
+def _on_connect_v5(client, userdata, flags, rc, properties):
     """Internal callback"""
     if rc != 0:
         raise mqtt.MQTTException(paho.connack_string(rc))
@@ -34,6 +35,10 @@ def _on_connect(client, userdata, flags, rc):
     else:
         client.subscribe(userdata['topics'], userdata['qos'])
 
+def _on_connect(client, userdata, flags, rc):
+    """Internal v5 callback"""
+    _on_connect_v5(client, userdata, flags, rc, None)
+
 
 def _on_message_callback(client, userdata, message):
     """Internal callback"""
@@ -142,7 +147,10 @@ def callback(callback, topics, qos=0, userdata=None, hostname="localhost",
                          protocol=protocol, transport=transport,
                          clean_session=clean_session)
     client.on_message = _on_message_callback
-    client.on_connect = _on_connect
+    if protocol == mqtt.client.MQTTv5:
+        client.on_connect = _on_connect_v5
+    else:
+        client.on_connect = _on_connect
 
     if proxy_args is not None:
         client.proxy_set(**proxy_args)
diff --git a/src/paho/mqtt/subscribeoptions.py b/src/paho/mqtt/subscribeoptions.py
index f55e90a..5b4f073 100644
--- a/src/paho/mqtt/subscribeoptions.py
+++ b/src/paho/mqtt/subscribeoptions.py
@@ -3,7 +3,7 @@
   Copyright (c) 2017, 2019 IBM Corp.
 
   All rights reserved. This program and the accompanying materials
-  are made available under the terms of the Eclipse Public License v1.0
+  are made available under the terms of the Eclipse Public License v2.0
   and Eclipse Distribution License v1.0 which accompany this distribution.
 
   The Eclipse Public License is available at
diff --git a/test/lib/01-asyncio.py b/test/lib/01-asyncio.py
new file mode 100755
index 0000000..f10a0b3
--- /dev/null
+++ b/test/lib/01-asyncio.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+
+# Test whether asyncio works
+
+import context
+import paho_test
+
+rc = 1
+keepalive = 60
+connect_packet = paho_test.gen_connect("asyncio-test", keepalive=keepalive)
+connack_packet = paho_test.gen_connack(rc=0)
+
+subscribe_packet = paho_test.gen_subscribe(mid=1, topic=u"sub-test", qos=1)
+suback_packet = paho_test.gen_suback(mid=1, qos=1)
+
+unsubscribe_packet = paho_test.gen_unsubscribe(mid=2, topic=u"unsub-test")
+unsuback_packet = paho_test.gen_unsuback(mid=2)
+
+publish_packet = paho_test.gen_publish(u"b2c", qos=0, payload="msg")
+
+publish_packet_in = paho_test.gen_publish(u"asyncio", qos=1, mid=3, payload="message")
+puback_packet_in = paho_test.gen_puback(mid=3)
+
+disconnect_packet = paho_test.gen_disconnect()
+
+sock = paho_test.create_server_socket()
+
+client = context.start_client()
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
+
+    paho_test.expect_packet(conn, "subscribe", subscribe_packet)
+    conn.send(suback_packet)
+
+    paho_test.expect_packet(conn, "unsubscribe", unsubscribe_packet)
+    conn.send(unsuback_packet)
+    conn.send(publish_packet)
+
+    paho_test.expect_packet(conn, "publish", publish_packet_in)
+    conn.send(puback_packet_in)
+
+    paho_test.expect_packet(conn, "disconnect", disconnect_packet)
+    rc = 0
+
+    conn.close()
+finally:
+    client.terminate()
+    client.wait()
+    sock.close()
+
+exit(rc)
diff --git a/test/lib/01-decorators.py b/test/lib/01-decorators.py
new file mode 100755
index 0000000..0360a53
--- /dev/null
+++ b/test/lib/01-decorators.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+
+# Test whether callback decorators work
+
+import context
+import paho_test
+
+rc = 1
+keepalive = 60
+connect_packet = paho_test.gen_connect("decorators-test", keepalive=keepalive)
+connack_packet = paho_test.gen_connack(rc=0)
+
+subscribe_packet = paho_test.gen_subscribe(mid=1, topic=u"sub-test", qos=1)
+suback_packet = paho_test.gen_suback(mid=1, qos=1)
+
+unsubscribe_packet = paho_test.gen_unsubscribe(mid=2, topic=u"unsub-test")
+unsuback_packet = paho_test.gen_unsuback(mid=2)
+
+publish_packet = paho_test.gen_publish(u"b2c", qos=0, payload="msg")
+
+publish_packet_in = paho_test.gen_publish(u"decorators", qos=1, mid=3, payload="message")
+puback_packet_in = paho_test.gen_puback(mid=3)
+
+disconnect_packet = paho_test.gen_disconnect()
+
+sock = paho_test.create_server_socket()
+
+client = context.start_client()
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
+
+    paho_test.expect_packet(conn, "subscribe", subscribe_packet)
+    conn.send(suback_packet)
+
+    paho_test.expect_packet(conn, "unsubscribe", unsubscribe_packet)
+    conn.send(unsuback_packet)
+    conn.send(publish_packet)
+
+    paho_test.expect_packet(conn, "publish", publish_packet_in)
+    conn.send(puback_packet_in)
+
+    paho_test.expect_packet(conn, "disconnect", disconnect_packet)
+    rc = 0
+
+    conn.close()
+finally:
+    client.terminate()
+    client.wait()
+    sock.close()
+
+exit(rc)
diff --git a/test/lib/01-keepalive-pingreq.py b/test/lib/01-keepalive-pingreq.py
index a166b12..0fb149c 100755
--- a/test/lib/01-keepalive-pingreq.py
+++ b/test/lib/01-keepalive-pingreq.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client sends a pingreq after the keepalive time
 
@@ -28,15 +28,15 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(keepalive+10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        conn.send(connack_packet)
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
 
-        if paho_test.expect_packet(conn, "pingreq", pingreq_packet):
-            time.sleep(1.0)
-            conn.send(pingresp_packet)
+    paho_test.expect_packet(conn, "pingreq", pingreq_packet)
+    time.sleep(1.0)
+    conn.send(pingresp_packet)
 
-            if paho_test.expect_packet(conn, "pingreq", pingreq_packet):
-                rc = 0
+    paho_test.expect_packet(conn, "pingreq", pingreq_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/01-no-clean-session.py b/test/lib/01-no-clean-session.py
index 15c9f06..b231233 100755
--- a/test/lib/01-no-clean-session.py
+++ b/test/lib/01-no-clean-session.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client produces a correct connect with clean session not set.
 
@@ -20,8 +20,8 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        rc = 0
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/01-reconnect-on-failure.py b/test/lib/01-reconnect-on-failure.py
new file mode 100755
index 0000000..d1fb9a7
--- /dev/null
+++ b/test/lib/01-reconnect-on-failure.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+
+# Test the reconnect_on_failure = False mode
+
+import context
+import paho_test
+
+rc = 1
+keepalive = 60
+connect_packet = paho_test.gen_connect("01-reconnect-on-failure", keepalive=keepalive)
+connack_packet_ok = paho_test.gen_connack(rc=0)
+connack_packet_failure = paho_test.gen_connack(rc=1) # CONNACK_REFUSED_PROTOCOL_VERSION
+
+publish_packet = paho_test.gen_publish(
+    u"reconnect/test", qos=0, payload="message")
+
+sock = paho_test.create_server_socket()
+
+client = context.start_client()
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet_ok)
+
+    # Connection is a success, so we expect a publish
+    paho_test.expect_packet(conn, "publish", publish_packet)
+    conn.close()
+    # Expect the client to quit here due to socket being closed
+    client.wait(1)
+    if client.returncode == 42:
+        # Repeat the test, but with a bad connack code
+        client = context.start_client()
+        (conn, address) = sock.accept()
+        conn.settimeout(10)
+
+        paho_test.expect_packet(conn, "connect", connect_packet)
+        conn.send(connack_packet_failure)
+        # Expect the client to quit here due to socket being closed
+        client.wait(1)
+        if client.returncode == 42:
+            rc = 0
+
+    conn.close()
+finally:
+    client.terminate()
+    client.wait()
+    sock.close()
+
+exit(rc)
+
diff --git a/test/lib/01-unpwd-empty-password-set.py b/test/lib/01-unpwd-empty-password-set.py
index f57db08..856a92e 100755
--- a/test/lib/01-unpwd-empty-password-set.py
+++ b/test/lib/01-unpwd-empty-password-set.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client produces a correct connect with a username and password.
 
@@ -23,8 +23,8 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        rc = 0
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/01-unpwd-empty-set.py b/test/lib/01-unpwd-empty-set.py
index c29e77f..2552395 100755
--- a/test/lib/01-unpwd-empty-set.py
+++ b/test/lib/01-unpwd-empty-set.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client produces a correct connect with a username and password.
 
@@ -23,8 +23,8 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        rc = 0
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/01-unpwd-set.py b/test/lib/01-unpwd-set.py
index a705e62..46e0907 100755
--- a/test/lib/01-unpwd-set.py
+++ b/test/lib/01-unpwd-set.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client produces a correct connect with a username and password.
 
@@ -23,8 +23,8 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        rc = 0
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/01-unpwd-unicode-set.py b/test/lib/01-unpwd-unicode-set.py
old mode 100644
new mode 100755
index 02c8bc2..487b29e
--- a/test/lib/01-unpwd-unicode-set.py
+++ b/test/lib/01-unpwd-unicode-set.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client produces a correct connect with a unicode username and password.
 
@@ -25,8 +25,8 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        rc = 0
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/01-will-set.py b/test/lib/01-will-set.py
index 96f8da2..49ad0af 100755
--- a/test/lib/01-will-set.py
+++ b/test/lib/01-will-set.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client produces a correct connect with a will.
 # Will QoS=1, will retain=1.
@@ -14,7 +14,7 @@ rc = 1
 keepalive = 60
 connect_packet = paho_test.gen_connect(
     "01-will-set", keepalive=keepalive, will_topic="topic/on/unexpected/disconnect",
-    will_qos=1, will_retain=True, will_payload="will message".encode('utf-8'))
+    will_qos=1, will_retain=True, will_payload="will message")
 
 sock = paho_test.create_server_socket()
 
@@ -24,8 +24,8 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        rc = 0
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/01-will-unpwd-set.py b/test/lib/01-will-unpwd-set.py
index 42814bd..67af1cb 100755
--- a/test/lib/01-will-unpwd-set.py
+++ b/test/lib/01-will-unpwd-set.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client produces a correct connect with a will, username and password.
 
@@ -14,7 +14,7 @@ rc = 1
 keepalive = 60
 connect_packet = paho_test.gen_connect("01-will-unpwd-set",
         keepalive=keepalive, username="oibvvwqw", password="#'^2hg9a&nm38*us",
-        will_topic="will-topic", will_qos=2, will_payload="will message".encode('utf-8'))
+        will_topic="will-topic", will_qos=2, will_payload="will message")
 
 sock = paho_test.create_server_socket()
 
@@ -24,8 +24,8 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        rc = 0
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/01-zero-length-clientid.py b/test/lib/01-zero-length-clientid.py
index 22cb494..587ed60 100755
--- a/test/lib/01-zero-length-clientid.py
+++ b/test/lib/01-zero-length-clientid.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client connects correctly with a zero length clientid.
 
@@ -7,7 +7,7 @@ import paho_test
 
 rc = 1
 keepalive = 60
-connect_packet = paho_test.gen_connect("", keepalive=keepalive, proto_name="MQTT", proto_ver=4)
+connect_packet = paho_test.gen_connect("", keepalive=keepalive, proto_ver=4)
 connack_packet = paho_test.gen_connack(rc=0)
 
 disconnect_packet = paho_test.gen_disconnect()
@@ -20,11 +20,11 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        conn.send(connack_packet)
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
 
-        if paho_test.expect_packet(conn, "disconnect", disconnect_packet):
-            rc = 0
+    paho_test.expect_packet(conn, "disconnect", disconnect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/02-subscribe-qos0.py b/test/lib/02-subscribe-qos0.py
index 09b34f5..c97783d 100755
--- a/test/lib/02-subscribe-qos0.py
+++ b/test/lib/02-subscribe-qos0.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client sends a correct SUBSCRIBE to a topic with QoS 0.
 
@@ -34,14 +34,14 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        conn.send(connack_packet)
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
 
-        if paho_test.expect_packet(conn, "subscribe", subscribe_packet):
-            conn.send(suback_packet)
+    paho_test.expect_packet(conn, "subscribe", subscribe_packet)
+    conn.send(suback_packet)
 
-            if paho_test.expect_packet(conn, "disconnect", disconnect_packet):
-                rc = 0
+    paho_test.expect_packet(conn, "disconnect", disconnect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/02-subscribe-qos1.py b/test/lib/02-subscribe-qos1.py
index 93988a9..1f98dd9 100755
--- a/test/lib/02-subscribe-qos1.py
+++ b/test/lib/02-subscribe-qos1.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client sends a correct SUBSCRIBE to a topic with QoS 1.
 
@@ -34,14 +34,14 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        conn.send(connack_packet)
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
 
-        if paho_test.expect_packet(conn, "subscribe", subscribe_packet):
-            conn.send(suback_packet)
+    paho_test.expect_packet(conn, "subscribe", subscribe_packet)
+    conn.send(suback_packet)
 
-            if paho_test.expect_packet(conn, "disconnect", disconnect_packet):
-                rc = 0
+    paho_test.expect_packet(conn, "disconnect", disconnect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/02-subscribe-qos2.py b/test/lib/02-subscribe-qos2.py
index a8430f1..38e7439 100755
--- a/test/lib/02-subscribe-qos2.py
+++ b/test/lib/02-subscribe-qos2.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client sends a correct SUBSCRIBE to a topic with QoS 2.
 
@@ -34,14 +34,14 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        conn.send(connack_packet)
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
 
-        if paho_test.expect_packet(conn, "subscribe", subscribe_packet):
-            conn.send(suback_packet)
+    paho_test.expect_packet(conn, "subscribe", subscribe_packet)
+    conn.send(suback_packet)
 
-            if paho_test.expect_packet(conn, "disconnect", disconnect_packet):
-                rc = 0
+    paho_test.expect_packet(conn, "disconnect", disconnect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/02-unsubscribe.py b/test/lib/02-unsubscribe.py
index 7e0271f..92ecda9 100755
--- a/test/lib/02-unsubscribe.py
+++ b/test/lib/02-unsubscribe.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client sends a correct UNSUBSCRIBE packet.
 
@@ -24,14 +24,14 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        conn.send(connack_packet)
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
 
-        if paho_test.expect_packet(conn, "unsubscribe", unsubscribe_packet):
-            conn.send(unsuback_packet)
+    paho_test.expect_packet(conn, "unsubscribe", unsubscribe_packet)
+    conn.send(unsuback_packet)
 
-            if paho_test.expect_packet(conn, "disconnect", disconnect_packet):
-                rc = 0
+    paho_test.expect_packet(conn, "disconnect", disconnect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/03-publish-b2c-qos1.py b/test/lib/03-publish-b2c-qos1.py
index 5422ebe..8b70a4c 100755
--- a/test/lib/03-publish-b2c-qos1.py
+++ b/test/lib/03-publish-b2c-qos1.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client responds correctly to a PUBLISH with QoS 1.
 
@@ -25,7 +25,7 @@ disconnect_packet = paho_test.gen_disconnect()
 
 mid = 123
 publish_packet = paho_test.gen_publish(
-    u"pub/qos1/receive", qos=1, mid=mid, payload="message".encode('utf-8'))
+    u"pub/qos1/receive", qos=1, mid=mid, payload="message")
 puback_packet = paho_test.gen_puback(mid)
 
 sock = paho_test.create_server_socket()
@@ -36,12 +36,12 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        conn.send(connack_packet)
-        conn.send(publish_packet)
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
+    conn.send(publish_packet)
 
-        if paho_test.expect_packet(conn, "puback", puback_packet):
-            rc = 0
+    paho_test.expect_packet(conn, "puback", puback_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/03-publish-b2c-qos2.py b/test/lib/03-publish-b2c-qos2.py
deleted file mode 100755
index ea9cc81..0000000
--- a/test/lib/03-publish-b2c-qos2.py
+++ /dev/null
@@ -1,71 +0,0 @@
-#!/usr/bin/env python
-
-# Test whether a client responds correctly to a PUBLISH with QoS 1.
-
-# The client should connect to port 1888 with keepalive=60, clean session set,
-# and client id publish-qos2-test
-# The test will send a CONNACK message to the client with rc=0. Upon receiving
-# the CONNACK the client should verify that rc==0.
-# The test will send the client a PUBLISH message with topic
-# "pub/qos2/receive", payload of "message", QoS=2 and mid=13423. The client
-# should handle this as per the spec by sending a PUBREC message.
-# The test will not respond to the first PUBREC message, so the client must
-# resend the PUBREC message with dup=1. Note that to keep test durations low, a
-# message retry timeout of less than 5 seconds is required for this test.
-# On receiving the second PUBREC with dup==1, the test will send the correct
-# PUBREL message. The client should respond to this with the correct PUBCOMP
-# message and then exit with return code=0.
-
-import time
-
-import context
-import paho_test
-
-rc = 1
-keepalive = 60
-connect_packet = paho_test.gen_connect("publish-qos2-test", keepalive=keepalive)
-connack_packet = paho_test.gen_connack(rc=0)
-
-disconnect_packet = paho_test.gen_disconnect()
-
-mid = 13423
-publish_packet = paho_test.gen_publish(
-    u"pub/qos2/receive", qos=2, mid=mid, payload="message".encode('utf-8'))
-pubrec_packet = paho_test.gen_pubrec(mid)
-pubrel_packet = paho_test.gen_pubrel(mid)
-pubcomp_packet = paho_test.gen_pubcomp(mid)
-
-sock = paho_test.create_server_socket()
-
-client = context.start_client()
-
-try:
-    (conn, address) = sock.accept()
-    conn.settimeout(10)
-
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        conn.send(connack_packet)
-        conn.send(publish_packet)
-
-        if paho_test.expect_packet(conn, "pubrec", pubrec_packet):
-            # Should be repeated due to timeout
-            if paho_test.expect_packet(conn, "pubrec", pubrec_packet):
-                conn.send(pubrel_packet)
-
-                if paho_test.expect_packet(conn, "pubcomp", pubcomp_packet):
-                    rc = 0
-
-    conn.close()
-finally:
-    for i in range(0, 5):
-        if client.returncode != None:
-            break
-        time.sleep(0.1)
-
-    client.terminate()
-    client.wait()
-    sock.close()
-    if client.returncode != 0:
-        exit(1)
-
-exit(rc)
diff --git a/test/lib/03-publish-c2b-qos1-disconnect.py b/test/lib/03-publish-c2b-qos1-disconnect.py
index efcd81b..555c0b3 100755
--- a/test/lib/03-publish-c2b-qos1-disconnect.py
+++ b/test/lib/03-publish-c2b-qos1-disconnect.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client sends a correct PUBLISH to a topic with QoS 1, then responds correctly to a disconnect.
 
@@ -16,9 +16,9 @@ disconnect_packet = paho_test.gen_disconnect()
 
 mid = 1
 publish_packet = paho_test.gen_publish(
-    u"pub/qos1/test", qos=1, mid=mid, payload="message".encode('utf-8'))
+    u"pub/qos1/test", qos=1, mid=mid, payload="message")
 publish_packet_dup = paho_test.gen_publish(
-    u"pub/qos1/test", qos=1, mid=mid, payload="message".encode('utf-8'), dup=True)
+    u"pub/qos1/test", qos=1, mid=mid, payload="message", dup=True)
 puback_packet = paho_test.gen_puback(mid)
 
 sock = paho_test.create_server_socket()
@@ -29,24 +29,24 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(15)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        conn.send(connack_packet)
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
 
-        if paho_test.expect_packet(conn, "publish", publish_packet):
-            # Disconnect client. It should reconnect.
-            conn.close()
+    paho_test.expect_packet(conn, "publish", publish_packet)
+    # Disconnect client. It should reconnect.
+    conn.close()
 
-            (conn, address) = sock.accept()
-            conn.settimeout(15)
+    (conn, address) = sock.accept()
+    conn.settimeout(15)
 
-            if paho_test.expect_packet(conn, "connect", connect_packet):
-                conn.send(connack_packet)
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
 
-                if paho_test.expect_packet(conn, "retried publish", publish_packet_dup):
-                    conn.send(puback_packet)
+    paho_test.expect_packet(conn, "retried publish", publish_packet_dup)
+    conn.send(puback_packet)
 
-                    if paho_test.expect_packet(conn, "disconnect", disconnect_packet):
-                        rc = 0
+    paho_test.expect_packet(conn, "disconnect", disconnect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/03-publish-c2b-qos1-timeout.py b/test/lib/03-publish-c2b-qos1-timeout.py
deleted file mode 100755
index 0e988b6..0000000
--- a/test/lib/03-publish-c2b-qos1-timeout.py
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/usr/bin/env python
-
-# Test whether a client sends a correct PUBLISH to a topic with QoS 1 and responds to a delay.
-
-# The client should connect to port 1888 with keepalive=60, clean session set,
-# and client id publish-qos1-test
-# The test will send a CONNACK message to the client with rc=0. Upon receiving
-# the CONNACK the client should verify that rc==0. If not, it should exit with
-# return code=1.
-# On a successful CONNACK, the client should send a PUBLISH message with topic
-# "pub/qos1/test", payload "message" and QoS=1.
-# The test will not respond to the first PUBLISH message, so the client must
-# resend the PUBLISH message with dup=1. Note that to keep test durations low, a
-# message retry timeout of less than 5 seconds is required for this test.
-# On receiving the second PUBLISH message, the test will send the correct
-# PUBACK response. On receiving the correct PUBACK response, the client should
-# send a DISCONNECT message.
-
-import context
-import paho_test
-
-rc = 1
-keepalive = 60
-connect_packet = paho_test.gen_connect("publish-qos1-test", keepalive=keepalive)
-connack_packet = paho_test.gen_connack(rc=0)
-
-disconnect_packet = paho_test.gen_disconnect()
-
-mid = 1
-publish_packet = paho_test.gen_publish(
-    u"pub/qos1/test", qos=1, mid=mid, payload="message".encode('utf-8'))
-publish_packet_dup = paho_test.gen_publish(
-    u"pub/qos1/test", qos=1, mid=mid, payload="message".encode('utf-8'), dup=True)
-puback_packet = paho_test.gen_puback(mid)
-
-sock = paho_test.create_server_socket()
-
-client = context.start_client()
-
-try:
-    (conn, address) = sock.accept()
-    conn.settimeout(5)
-
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        conn.send(connack_packet)
-
-        if paho_test.expect_packet(conn, "publish", publish_packet):
-            # Delay for > 3 seconds (message retry time)
-
-            if paho_test.expect_packet(conn, "dup publish", publish_packet_dup):
-                conn.send(puback_packet)
-
-                if paho_test.expect_packet(conn, "disconnect", disconnect_packet):
-                    rc = 0
-
-    conn.close()
-finally:
-    client.terminate()
-    client.wait()
-    sock.close()
-
-exit(rc)
diff --git a/test/lib/03-publish-c2b-qos2-disconnect.py b/test/lib/03-publish-c2b-qos2-disconnect.py
index 790601e..543d5f0 100755
--- a/test/lib/03-publish-c2b-qos2-disconnect.py
+++ b/test/lib/03-publish-c2b-qos2-disconnect.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client sends a correct PUBLISH to a topic with QoS 2 and responds to a disconnect.
 
@@ -16,9 +16,9 @@ disconnect_packet = paho_test.gen_disconnect()
 
 mid = 1
 publish_packet = paho_test.gen_publish(
-    u"pub/qos2/test", qos=2, mid=mid, payload="message".encode('utf-8'))
+    u"pub/qos2/test", qos=2, mid=mid, payload="message")
 publish_dup_packet = paho_test.gen_publish(
-    u"pub/qos2/test", qos=2, mid=mid, payload="message".encode('utf-8'), dup=True)
+    u"pub/qos2/test", qos=2, mid=mid, payload="message", dup=True)
 pubrec_packet = paho_test.gen_pubrec(mid)
 pubrel_packet = paho_test.gen_pubrel(mid)
 pubcomp_packet = paho_test.gen_pubcomp(mid)
@@ -31,38 +31,38 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(5)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        conn.send(connack_packet)
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
 
-        if paho_test.expect_packet(conn, "publish", publish_packet):
-            # Disconnect client. It should reconnect.
-            conn.close()
+    paho_test.expect_packet(conn, "publish", publish_packet)
+    # Disconnect client. It should reconnect.
+    conn.close()
 
-            (conn, address) = sock.accept()
-            conn.settimeout(15)
+    (conn, address) = sock.accept()
+    conn.settimeout(15)
 
-            if paho_test.expect_packet(conn, "connect", connect_packet):
-                conn.send(connack_packet)
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
 
-                if paho_test.expect_packet(conn, "retried publish", publish_dup_packet):
-                    conn.send(pubrec_packet)
+    paho_test.expect_packet(conn, "retried publish", publish_dup_packet)
+    conn.send(pubrec_packet)
 
-                    if paho_test.expect_packet(conn, "pubrel", pubrel_packet):
-                        # Disconnect client. It should reconnect.
-                        conn.close()
+    paho_test.expect_packet(conn, "pubrel", pubrel_packet)
+    # Disconnect client. It should reconnect.
+    conn.close()
 
-                        (conn, address) = sock.accept()
-                        conn.settimeout(15)
+    (conn, address) = sock.accept()
+    conn.settimeout(15)
 
-                        # Complete connection and message flow.
-                        if paho_test.expect_packet(conn, "connect", connect_packet):
-                            conn.send(connack_packet)
+    # Complete connection and message flow.
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
 
-                            if paho_test.expect_packet(conn, "retried pubrel", pubrel_packet):
-                                conn.send(pubcomp_packet)
+    paho_test.expect_packet(conn, "retried pubrel", pubrel_packet)
+    conn.send(pubcomp_packet)
 
-                                if paho_test.expect_packet(conn, "disconnect", disconnect_packet):
-                                    rc = 0
+    paho_test.expect_packet(conn, "disconnect", disconnect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/03-publish-c2b-qos2-timeout.py b/test/lib/03-publish-c2b-qos2-timeout.py
deleted file mode 100755
index ba7b708..0000000
--- a/test/lib/03-publish-c2b-qos2-timeout.py
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/usr/bin/env python
-
-# Test whether a client sends a correct PUBLISH to a topic with QoS 1 and responds to a delay.
-
-# The client should connect to port 1888 with keepalive=60, clean session set,
-# and client id publish-qos2-test
-# The test will send a CONNACK message to the client with rc=0. Upon receiving
-# the CONNACK the client should verify that rc==0. If not, it should exit with
-# return code=1.
-# On a successful CONNACK, the client should send a PUBLISH message with topic
-# "pub/qos2/test", payload "message" and QoS=2.
-# The test will not respond to the first PUBLISH message, so the client must
-# resend the PUBLISH message with dup=1. Note that to keep test durations low, a
-# message retry timeout of less than 5 seconds is required for this test.
-# On receiving the second PUBLISH message, the test will send the correct
-# PUBREC response. On receiving the correct PUBREC response, the client should
-# send a PUBREL message.
-# The test will not respond to the first PUBREL message, so the client must
-# resend the PUBREL message with dup=1. On receiving the second PUBREL message,
-# the test will send the correct PUBCOMP response. On receiving the correct
-# PUBCOMP response, the client should send a DISCONNECT message.
-
-import context
-import paho_test
-
-rc = 1
-keepalive = 60
-connect_packet = paho_test.gen_connect("publish-qos2-test", keepalive=keepalive)
-connack_packet = paho_test.gen_connack(rc=0)
-
-disconnect_packet = paho_test.gen_disconnect()
-
-mid = 1
-publish_packet = paho_test.gen_publish(
-    u"pub/qos2/test", qos=2, mid=mid, payload="message".encode('utf-8'))
-publish_dup_packet = paho_test.gen_publish(
-    u"pub/qos2/test", qos=2, mid=mid, payload="message".encode('utf-8'), dup=True)
-pubrec_packet = paho_test.gen_pubrec(mid)
-pubrel_packet = paho_test.gen_pubrel(mid)
-pubcomp_packet = paho_test.gen_pubcomp(mid)
-
-sock = paho_test.create_server_socket()
-
-client = context.start_client()
-
-try:
-    (conn, address) = sock.accept()
-    conn.settimeout(5)
-
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        conn.send(connack_packet)
-
-        if paho_test.expect_packet(conn, "publish", publish_packet):
-            # Delay for > 3 seconds (message retry time)
-
-            if paho_test.expect_packet(conn, "dup publish", publish_dup_packet):
-                conn.send(pubrec_packet)
-
-                if paho_test.expect_packet(conn, "pubrel", pubrel_packet):
-                    if paho_test.expect_packet(conn, "dup pubrel", pubrel_packet):
-                        conn.send(pubcomp_packet)
-
-                        if paho_test.expect_packet(conn, "disconnect", disconnect_packet):
-                            rc = 0
-
-    conn.close()
-finally:
-    client.terminate()
-    client.wait()
-    sock.close()
-
-exit(rc)
diff --git a/test/lib/03-publish-helper-qos0-v5.py b/test/lib/03-publish-helper-qos0-v5.py
new file mode 100755
index 0000000..0bea537
--- /dev/null
+++ b/test/lib/03-publish-helper-qos0-v5.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+
+# Test whether a client sends a correct PUBLISH to a topic with QoS 0.
+# Use paho.mqtt.publish helper for that.
+
+# The client should connect to port 1888 with keepalive=60, clean session set,
+# and client id publish-helper-qos0-test
+# The test will send a CONNACK message to the client with rc=0. Upon receiving
+# the CONNACK and verifying that rc=0, the client should send a PUBLISH message
+# to topic "pub/qos0/test" with payload "message" and QoS=0. If rc!=0, the
+# client should exit with an error.
+# After sending the PUBLISH message, the client should send a
+# DISCONNECT message.
+
+import context
+import paho_test
+
+rc = 1
+keepalive = 60
+connect_packet = paho_test.gen_connect(
+    "publish-helper-qos0-test", keepalive=keepalive, proto_ver=5, properties=None
+)
+connack_packet = paho_test.gen_connack(rc=0, proto_ver=5)
+
+publish_packet = paho_test.gen_publish(
+    u"pub/qos0/test", qos=0, payload="message", proto_ver=5
+)
+
+disconnect_packet = paho_test.gen_disconnect()
+
+sock = paho_test.create_server_socket()
+
+client = context.start_client()
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
+
+    paho_test.expect_packet(conn, "publish", publish_packet)
+    paho_test.expect_packet(conn, "disconnect", disconnect_packet)
+    rc = 0
+
+    conn.close()
+finally:
+    client.terminate()
+    client.wait()
+    sock.close()
+
+exit(rc)
diff --git a/test/lib/03-publish-helper-qos0.py b/test/lib/03-publish-helper-qos0.py
index d07876e..5bd1af6 100755
--- a/test/lib/03-publish-helper-qos0.py
+++ b/test/lib/03-publish-helper-qos0.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client sends a correct PUBLISH to a topic with QoS 0.
 # Use paho.mqtt.publish helper for that.
@@ -23,7 +23,7 @@ connect_packet = paho_test.gen_connect(
 connack_packet = paho_test.gen_connack(rc=0)
 
 publish_packet = paho_test.gen_publish(
-    u"pub/qos0/test", qos=0, payload="message".encode('utf-8'),
+    u"pub/qos0/test", qos=0, payload="message"
 )
 
 disconnect_packet = paho_test.gen_disconnect()
@@ -36,12 +36,12 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        conn.send(connack_packet)
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
 
-        if paho_test.expect_packet(conn, "publish", publish_packet):
-            if paho_test.expect_packet(conn, "disconnect", disconnect_packet):
-                rc = 0
+    paho_test.expect_packet(conn, "publish", publish_packet)
+    paho_test.expect_packet(conn, "disconnect", disconnect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/03-publish-helper-qos1-disconnect.py b/test/lib/03-publish-helper-qos1-disconnect.py
index 428c1e4..b490a2d 100755
--- a/test/lib/03-publish-helper-qos1-disconnect.py
+++ b/test/lib/03-publish-helper-qos1-disconnect.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client sends a correct PUBLISH to a topic with QoS 1,
 # then responds correctly to a disconnect.
@@ -16,10 +16,10 @@ connack_packet = paho_test.gen_connack(rc=0)
 
 mid = 1
 publish_packet = paho_test.gen_publish(
-    u"pub/qos1/test", qos=1, mid=mid, payload="message".encode('utf-8'),
+    u"pub/qos1/test", qos=1, mid=mid, payload="message"
 )
 publish_packet_dup = paho_test.gen_publish(
-    u"pub/qos1/test", qos=1, mid=mid, payload="message".encode('utf-8'),
+    u"pub/qos1/test", qos=1, mid=mid, payload="message",
     dup=True,
 )
 puback_packet = paho_test.gen_puback(mid)
@@ -34,24 +34,24 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        conn.send(connack_packet)
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
 
-        if paho_test.expect_packet(conn, "publish", publish_packet):
-            # Disconnect client. It should reconnect.
-            conn.close()
+    paho_test.expect_packet(conn, "publish", publish_packet)
+    # Disconnect client. It should reconnect.
+    conn.close()
 
-            (conn, address) = sock.accept()
-            conn.settimeout(15)
+    (conn, address) = sock.accept()
+    conn.settimeout(15)
 
-            if paho_test.expect_packet(conn, "connect", connect_packet):
-                conn.send(connack_packet)
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
 
-                if paho_test.expect_packet(conn, "retried publish", publish_packet_dup):
-                    conn.send(puback_packet)
+    paho_test.expect_packet(conn, "retried publish", publish_packet_dup)
+    conn.send(puback_packet)
 
-                    if paho_test.expect_packet(conn, "disconnect", disconnect_packet):
-                        rc = 0
+    paho_test.expect_packet(conn, "disconnect", disconnect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/03-publish-qos0-no-payload.py b/test/lib/03-publish-qos0-no-payload.py
index cbf0e24..569afb3 100755
--- a/test/lib/03-publish-qos0-no-payload.py
+++ b/test/lib/03-publish-qos0-no-payload.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client sends a correct PUBLISH to a topic with QoS 0 and no payload.
 
@@ -30,12 +30,12 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        conn.send(connack_packet)
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
 
-        if paho_test.expect_packet(conn, "publish", publish_packet):
-            if paho_test.expect_packet(conn, "disconnect", disconnect_packet):
-                rc = 0
+    paho_test.expect_packet(conn, "publish", publish_packet)
+    paho_test.expect_packet(conn, "disconnect", disconnect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/03-publish-qos0.py b/test/lib/03-publish-qos0.py
index 28d5d71..ca47d05 100755
--- a/test/lib/03-publish-qos0.py
+++ b/test/lib/03-publish-qos0.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client sends a correct PUBLISH to a topic with QoS 0.
 
@@ -18,7 +18,7 @@ keepalive = 60
 connect_packet = paho_test.gen_connect("publish-qos0-test", keepalive=keepalive)
 connack_packet = paho_test.gen_connack(rc=0)
 
-publish_packet = paho_test.gen_publish(u"pub/qos0/test", qos=0, payload="message".encode('utf-8'))
+publish_packet = paho_test.gen_publish(u"pub/qos0/test", qos=0, payload="message")
 
 disconnect_packet = paho_test.gen_disconnect()
 
@@ -30,12 +30,12 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        conn.send(connack_packet)
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
 
-        if paho_test.expect_packet(conn, "publish", publish_packet):
-            if paho_test.expect_packet(conn, "disconnect", disconnect_packet):
-                rc = 0
+    paho_test.expect_packet(conn, "publish", publish_packet)
+    paho_test.expect_packet(conn, "disconnect", disconnect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/04-retain-qos0.py b/test/lib/04-retain-qos0.py
index 6e72524..99a62ef 100755
--- a/test/lib/04-retain-qos0.py
+++ b/test/lib/04-retain-qos0.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client sends a correct retained PUBLISH to a topic with QoS 0.
 
@@ -12,7 +12,7 @@ connect_packet = paho_test.gen_connect("retain-qos0-test", keepalive=keepalive)
 connack_packet = paho_test.gen_connack(rc=0)
 
 publish_packet = paho_test.gen_publish(
-    u"retain/qos0/test", qos=0, payload="retained message".encode('utf-8'), retain=True)
+    u"retain/qos0/test", qos=0, payload="retained message", retain=True)
 
 sock = paho_test.create_server_socket()
 
@@ -22,11 +22,11 @@ try:
     (conn, address) = sock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        conn.send(connack_packet)
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
 
-        if paho_test.expect_packet(conn, "publish", publish_packet):
-            rc = 0
+    paho_test.expect_packet(conn, "publish", publish_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/08-ssl-bad-cacert.py b/test/lib/08-ssl-bad-cacert.py
index 58fcaf4..19e100c 100755
--- a/test/lib/08-ssl-bad-cacert.py
+++ b/test/lib/08-ssl-bad-cacert.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import context
 
diff --git a/test/lib/08-ssl-connect-cert-auth-pw.py b/test/lib/08-ssl-connect-cert-auth-pw.py
new file mode 100755
index 0000000..94d5655
--- /dev/null
+++ b/test/lib/08-ssl-connect-cert-auth-pw.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+
+# Test whether a client produces a correct connect and subsequent disconnect when using SSL.
+# Client must provide a certificate - the private key is encrypted with a password.
+
+# The client should connect to port 1888 with keepalive=60, clean session set,
+# and client id 08-ssl-connect-crt-auth
+# It should use the CA certificate ssl/all-ca.crt for verifying the server.
+# The test will send a CONNACK message to the client with rc=0. Upon receiving
+# the CONNACK and verifying that rc=0, the client should send a DISCONNECT
+# message. If rc!=0, the client should exit with an error.
+
+import context
+import paho_test
+from paho_test import ssl
+
+context.check_ssl()
+
+rc = 1
+keepalive = 60
+connect_packet = paho_test.gen_connect("08-ssl-connect-crt-auth-pw", keepalive=keepalive)
+connack_packet = paho_test.gen_connack(rc=0)
+disconnect_packet = paho_test.gen_disconnect()
+
+ssock = paho_test.create_server_socket_ssl(cert_reqs=ssl.CERT_REQUIRED)
+
+client = context.start_client()
+
+try:
+    (conn, address) = ssock.accept()
+    conn.settimeout(10)
+
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
+
+    paho_test.expect_packet(conn, "disconnect", disconnect_packet)
+    rc = 0
+
+    conn.close()
+finally:
+    client.terminate()
+    client.wait()
+    ssock.close()
+
+exit(rc)
diff --git a/test/lib/08-ssl-connect-cert-auth.py b/test/lib/08-ssl-connect-cert-auth.py
index bdbfc58..fbe3b33 100755
--- a/test/lib/08-ssl-connect-cert-auth.py
+++ b/test/lib/08-ssl-connect-cert-auth.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client produces a correct connect and subsequent disconnect when using SSL.
 # Client must provide a certificate.
@@ -30,11 +30,11 @@ try:
     (conn, address) = ssock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        conn.send(connack_packet)
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
 
-        if paho_test.expect_packet(conn, "disconnect", disconnect_packet):
-            rc = 0
+    paho_test.expect_packet(conn, "disconnect", disconnect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/08-ssl-connect-no-auth.py b/test/lib/08-ssl-connect-no-auth.py
index 368acb6..df3322d 100755
--- a/test/lib/08-ssl-connect-no-auth.py
+++ b/test/lib/08-ssl-connect-no-auth.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Test whether a client produces a correct connect and subsequent disconnect when using SSL.
 
@@ -28,11 +28,11 @@ try:
     (conn, address) = ssock.accept()
     conn.settimeout(10)
 
-    if paho_test.expect_packet(conn, "connect", connect_packet):
-        conn.send(connack_packet)
+    paho_test.expect_packet(conn, "connect", connect_packet)
+    conn.send(connack_packet)
 
-        if paho_test.expect_packet(conn, "disconnect", disconnect_packet):
-            rc = 0
+    paho_test.expect_packet(conn, "disconnect", disconnect_packet)
+    rc = 0
 
     conn.close()
 finally:
diff --git a/test/lib/08-ssl-fake-cacert.py b/test/lib/08-ssl-fake-cacert.py
index 9e4b223..fb46604 100755
--- a/test/lib/08-ssl-fake-cacert.py
+++ b/test/lib/08-ssl-fake-cacert.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import time
 
diff --git a/test/lib/Makefile b/test/lib/Makefile
index d57b2db..1e41b36 100644
--- a/test/lib/Makefile
+++ b/test/lib/Makefile
@@ -1,33 +1,37 @@
 .PHONY: all test
 .NOTPARALLEL:
 
+PYTHON?=python3
+
 all :
 
 test :
-	python ./01-will-set.py python/01-will-set.test
-	python ./01-unpwd-set.py python/01-unpwd-set.test
-	python ./01-unpwd-unicode-set.py python/01-unpwd-unicode-set.test
-	python ./01-unpwd-empty-password-set.py python/01-unpwd-empty-password-set.test
-	python ./01-unpwd-empty-set.py python/01-unpwd-empty-set.test
-	python ./01-will-unpwd-set.py python/01-will-unpwd-set.test
-	python ./01-zero-length-clientid.py python/01-zero-length-clientid.test
-	python ./01-no-clean-session.py python/01-no-clean-session.test
-	python ./01-keepalive-pingreq.py python/01-keepalive-pingreq.test
-	python ./02-subscribe-qos0.py python/02-subscribe-qos0.test
-	python ./02-subscribe-qos1.py python/02-subscribe-qos1.test
-	python ./02-subscribe-qos2.py python/02-subscribe-qos2.test
-	python ./02-unsubscribe.py python/02-unsubscribe.test
-	python ./03-publish-qos0.py python/03-publish-qos0.test
-	python ./03-publish-qos0-no-payload.py python/03-publish-qos0-no-payload.test
-	python ./03-publish-c2b-qos1-timeout.py python/03-publish-c2b-qos1-timeout.test
-	python ./03-publish-c2b-qos1-disconnect.py python/03-publish-c2b-qos1-disconnect.test
-	python ./03-publish-c2b-qos2-timeout.py python/03-publish-c2b-qos2-timeout.test
-	python ./03-publish-c2b-qos2-disconnect.py python/03-publish-c2b-qos2-disconnect.test
-	python ./03-publish-b2c-qos1.py python/03-publish-b2c-qos1.test
-	python ./03-publish-b2c-qos2.py python/03-publish-b2c-qos2.test
-	python ./03-publish-helper-qos0.py python/03-publish-helper-qos0.test
-	python ./03-publish-helper-qos1-disconnect.py python/03-publish-helper-qos1-disconnect.test
-	python ./04-retain-qos0.py python/04-retain-qos0.test
-	python ./08-ssl-connect-no-auth.py python/08-ssl-connect-no-auth.test
-	python ./08-ssl-connect-cert-auth.py python/08-ssl-connect-cert-auth.test
-	python ./08-ssl-bad-cacert.py python/08-ssl-bad-cacert.test
+	$(PYTHON) ./01-asyncio.py python/01-asyncio.test
+	$(PYTHON) ./01-decorators.py python/01-decorators.test
+	$(PYTHON) ./01-keepalive-pingreq.py python/01-keepalive-pingreq.test
+	$(PYTHON) ./01-no-clean-session.py python/01-no-clean-session.test
+	$(PYTHON) ./01-reconnect-on-failure.py python/01-reconnect-on-failure.test
+	$(PYTHON) ./01-unpwd-empty-password-set.py python/01-unpwd-empty-password-set.test
+	$(PYTHON) ./01-unpwd-empty-set.py python/01-unpwd-empty-set.test
+	$(PYTHON) ./01-unpwd-set.py python/01-unpwd-set.test
+	$(PYTHON) ./01-unpwd-unicode-set.py python/01-unpwd-unicode-set.test
+	$(PYTHON) ./01-will-set.py python/01-will-set.test
+	$(PYTHON) ./01-will-unpwd-set.py python/01-will-unpwd-set.test
+	$(PYTHON) ./01-zero-length-clientid.py python/01-zero-length-clientid.test
+	$(PYTHON) ./02-subscribe-qos0.py python/02-subscribe-qos0.test
+	$(PYTHON) ./02-subscribe-qos1.py python/02-subscribe-qos1.test
+	$(PYTHON) ./02-subscribe-qos2.py python/02-subscribe-qos2.test
+	$(PYTHON) ./02-unsubscribe.py python/02-unsubscribe.test
+	$(PYTHON) ./03-publish-b2c-qos1.py python/03-publish-b2c-qos1.test
+	$(PYTHON) ./03-publish-c2b-qos1-disconnect.py python/03-publish-c2b-qos1-disconnect.test
+	$(PYTHON) ./03-publish-c2b-qos2-disconnect.py python/03-publish-c2b-qos2-disconnect.test
+	$(PYTHON) ./03-publish-helper-qos0.py python/03-publish-helper-qos0.test
+	$(PYTHON) ./03-publish-helper-qos0-v5.py python/03-publish-helper-qos0-v5.test
+	$(PYTHON) ./03-publish-helper-qos1-disconnect.py python/03-publish-helper-qos1-disconnect.test
+	$(PYTHON) ./03-publish-qos0-no-payload.py python/03-publish-qos0-no-payload.test
+	$(PYTHON) ./03-publish-qos0.py python/03-publish-qos0.test
+	$(PYTHON) ./04-retain-qos0.py python/04-retain-qos0.test
+	$(PYTHON) ./08-ssl-bad-cacert.py python/08-ssl-bad-cacert.test
+	$(PYTHON) ./08-ssl-connect-cert-auth-pw.py python/08-ssl-connect-cert-auth-pw.test
+	$(PYTHON) ./08-ssl-connect-cert-auth.py python/08-ssl-connect-cert-auth.test
+	$(PYTHON) ./08-ssl-connect-no-auth.py python/08-ssl-connect-no-auth.test
diff --git a/test/lib/context.py b/test/lib/context.py
index 7a191b2..8cfc07c 100644
--- a/test/lib/context.py
+++ b/test/lib/context.py
@@ -1,8 +1,8 @@
 # -*- coding: utf-8 -*-
 
-import sys
 import os
 import subprocess
+import sys
 
 try:
     import ssl
diff --git a/test/lib/python/01-asyncio.test b/test/lib/python/01-asyncio.test
new file mode 100755
index 0000000..43368a7
--- /dev/null
+++ b/test/lib/python/01-asyncio.test
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+
+import asyncio
+import socket
+import uuid
+
+import context  # Ensures paho is in PYTHONPATH
+
+import paho.mqtt.client as mqtt
+
+client_id = 'asyncio-test'
+
+class AsyncioHelper:
+    def __init__(self, loop, client):
+        self.loop = loop
+        self.client = client
+        self.client.on_socket_open = self.on_socket_open
+        self.client.on_socket_close = self.on_socket_close
+        self.client.on_socket_register_write = self.on_socket_register_write
+        self.client.on_socket_unregister_write = self.on_socket_unregister_write
+
+    def on_socket_open(self, client, userdata, sock):
+        def cb():
+            client.loop_read()
+
+        self.loop.add_reader(sock, cb)
+        self.misc = self.loop.create_task(self.misc_loop())
+
+    def on_socket_close(self, client, userdata, sock):
+        self.loop.remove_reader(sock)
+        self.misc.cancel()
+
+    def on_socket_register_write(self, client, userdata, sock):
+        def cb():
+            client.loop_write()
+
+        self.loop.add_writer(sock, cb)
+
+    def on_socket_unregister_write(self, client, userdata, sock):
+        self.loop.remove_writer(sock)
+
+    async def misc_loop(self):
+        while self.client.loop_misc() == mqtt.MQTT_ERR_SUCCESS:
+            try:
+                await asyncio.sleep(1)
+            except asyncio.CancelledError:
+                break
+
+
+class AsyncMqttExample:
+    def __init__(self, loop):
+        self.loop = loop
+        self.payload = ""
+        self.complete = False
+
+    def on_connect(self, client, obj, flags, rc):
+        client.subscribe("sub-test", 1)
+
+    def on_subscribe(self, client, obj, mid, granted_qos):
+        client.unsubscribe("unsub-test")
+
+    def on_unsubscribe(self, client, obj, mid):
+        self.payload = "message"
+
+    def on_message(self, client, obj, msg):
+        client.publish("asyncio", qos=1, payload=self.payload)
+
+    def on_publish(self, client, obj, mid):
+        client.disconnect()
+
+    def on_disconnect(self, client, userdata, rc):
+        self.disconnected.set_result(rc)
+
+    async def main(self):
+        global rc
+        self.disconnected = self.loop.create_future()
+
+        self.client = mqtt.Client(client_id=client_id)
+        self.client.on_connect = self.on_connect
+        self.client.on_message = self.on_message
+        self.client.on_publish = self.on_publish
+        self.client.on_subscribe = self.on_subscribe
+        self.client.on_unsubscribe = self.on_unsubscribe
+        self.client.on_disconnect = self.on_disconnect
+
+        aioh = AsyncioHelper(self.loop, self.client)
+
+        self.client.connect('localhost', 1888, 60)
+        self.client.socket().setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 2048)
+
+        await self.disconnected
+        rc = 0
+
+rc = 1
+loop = asyncio.get_event_loop()
+loop.run_until_complete(AsyncMqttExample(loop).main())
+loop.close()
+exit(rc)
diff --git a/test/lib/python/01-decorators.test b/test/lib/python/01-decorators.test
new file mode 100755
index 0000000..bff109b
--- /dev/null
+++ b/test/lib/python/01-decorators.test
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+
+import os
+import socket
+import subprocess
+import sys
+import time
+from struct import *
+
+import paho.mqtt.client as mqtt
+
+run = -1
+mqttc = mqtt.Client("decorators-test", run)
+payload = b""
+
+@mqttc.connect_callback()
+def on_connect(mqttc, obj, flags, rc):
+    mqttc.subscribe("sub-test", 1)
+
+@mqttc.subscribe_callback()
+def on_subscribe(mqttc, obj, mid, granted_qos):
+    mqttc.unsubscribe("unsub-test")
+
+@mqttc.unsubscribe_callback()
+def on_unsubscribe(mqttc, obj, mid):
+    global payload
+    payload = "message"
+
+@mqttc.message_callback()
+def on_message(mqttc, obj, msg):
+    global payload
+    mqttc.publish("decorators", qos=1, payload=payload)
+
+@mqttc.publish_callback()
+def on_publish(mqttc, obj, mid):
+    mqttc.disconnect()
+
+@mqttc.disconnect_callback()
+def on_disconnect(mqttc, obj, rc):
+    obj = rc
+
+mqttc.connect("localhost", 1888)
+while run == -1:
+    mqttc.loop()
+
+exit(run)
diff --git a/test/lib/python/01-keepalive-pingreq.test b/test/lib/python/01-keepalive-pingreq.test
index 7a092b5..632866a 100755
--- a/test/lib/python/01-keepalive-pingreq.test
+++ b/test/lib/python/01-keepalive-pingreq.test
@@ -1,8 +1,8 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import os
-import subprocess
 import socket
+import subprocess
 import sys
 import time
 from struct import *
diff --git a/test/lib/python/01-no-clean-session.test b/test/lib/python/01-no-clean-session.test
index f66acfd..8f05024 100755
--- a/test/lib/python/01-no-clean-session.test
+++ b/test/lib/python/01-no-clean-session.test
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import paho.mqtt.client as mqtt
 
diff --git a/test/lib/python/01-reconnect-on-failure.test b/test/lib/python/01-reconnect-on-failure.test
new file mode 100755
index 0000000..695249f
--- /dev/null
+++ b/test/lib/python/01-reconnect-on-failure.test
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+
+import os
+import socket
+import subprocess
+import sys
+import time
+from struct import *
+
+import paho.mqtt.client as mqtt
+
+
+def on_connect(mqttc, obj, flags, rc):
+    mqttc.publish("reconnect/test", "message")
+
+mqttc = mqtt.Client("01-reconnect-on-failure", reconnect_on_failure=False)
+mqttc.on_connect = on_connect
+
+mqttc.connect("localhost", 1888)
+mqttc.loop_forever()
+
+exit(42)
diff --git a/test/lib/python/01-unpwd-empty-password-set.test b/test/lib/python/01-unpwd-empty-password-set.test
index e95a18a..f3ac7f5 100755
--- a/test/lib/python/01-unpwd-empty-password-set.test
+++ b/test/lib/python/01-unpwd-empty-password-set.test
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import paho.mqtt.client as mqtt
 
diff --git a/test/lib/python/01-unpwd-empty-set.test b/test/lib/python/01-unpwd-empty-set.test
index e98dd72..0163862 100755
--- a/test/lib/python/01-unpwd-empty-set.test
+++ b/test/lib/python/01-unpwd-empty-set.test
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import paho.mqtt.client as mqtt
 
diff --git a/test/lib/python/01-unpwd-set.test b/test/lib/python/01-unpwd-set.test
index 9166102..d74b034 100755
--- a/test/lib/python/01-unpwd-set.test
+++ b/test/lib/python/01-unpwd-set.test
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import paho.mqtt.client as mqtt
 
diff --git a/test/lib/python/01-unpwd-unicode-set.test b/test/lib/python/01-unpwd-unicode-set.test
old mode 100644
new mode 100755
index 5c967db..fc733d2
--- a/test/lib/python/01-unpwd-unicode-set.test
+++ b/test/lib/python/01-unpwd-unicode-set.test
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 from __future__ import unicode_literals
 
diff --git a/test/lib/python/01-will-set.test b/test/lib/python/01-will-set.test
index 8cb77d8..e1d4c43 100755
--- a/test/lib/python/01-will-set.test
+++ b/test/lib/python/01-will-set.test
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import paho.mqtt.client as mqtt
 
diff --git a/test/lib/python/01-will-unpwd-set.test b/test/lib/python/01-will-unpwd-set.test
index aface84..ad1199f 100755
--- a/test/lib/python/01-will-unpwd-set.test
+++ b/test/lib/python/01-will-unpwd-set.test
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import paho.mqtt.client as mqtt
 
diff --git a/test/lib/python/01-zero-length-clientid.test b/test/lib/python/01-zero-length-clientid.test
index 8cd595d..24691ea 100755
--- a/test/lib/python/01-zero-length-clientid.test
+++ b/test/lib/python/01-zero-length-clientid.test
@@ -1,8 +1,8 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import os
-import subprocess
 import socket
+import subprocess
 import sys
 import time
 from struct import *
diff --git a/test/lib/python/02-subscribe-qos0.test b/test/lib/python/02-subscribe-qos0.test
index 001e0f8..602cf5c 100755
--- a/test/lib/python/02-subscribe-qos0.test
+++ b/test/lib/python/02-subscribe-qos0.test
@@ -1,8 +1,8 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import os
-import subprocess
 import socket
+import subprocess
 import sys
 import time
 from struct import *
diff --git a/test/lib/python/02-subscribe-qos1.test b/test/lib/python/02-subscribe-qos1.test
index 3400dc5..9066775 100755
--- a/test/lib/python/02-subscribe-qos1.test
+++ b/test/lib/python/02-subscribe-qos1.test
@@ -1,8 +1,8 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import os
-import subprocess
 import socket
+import subprocess
 import sys
 import time
 from struct import *
diff --git a/test/lib/python/02-subscribe-qos2.test b/test/lib/python/02-subscribe-qos2.test
index 2776317..d756ac7 100755
--- a/test/lib/python/02-subscribe-qos2.test
+++ b/test/lib/python/02-subscribe-qos2.test
@@ -1,8 +1,8 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import os
-import subprocess
 import socket
+import subprocess
 import sys
 import time
 from struct import *
diff --git a/test/lib/python/02-unsubscribe.test b/test/lib/python/02-unsubscribe.test
index eab5e8a..5ca6f77 100755
--- a/test/lib/python/02-unsubscribe.test
+++ b/test/lib/python/02-unsubscribe.test
@@ -1,8 +1,8 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import os
-import subprocess
 import socket
+import subprocess
 import sys
 import time
 from struct import *
diff --git a/test/lib/python/03-publish-b2c-qos1.test b/test/lib/python/03-publish-b2c-qos1.test
index 06d98cb..d127d38 100755
--- a/test/lib/python/03-publish-b2c-qos1.test
+++ b/test/lib/python/03-publish-b2c-qos1.test
@@ -1,8 +1,8 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import os
-import subprocess
 import socket
+import subprocess
 import sys
 import time
 from struct import *
@@ -31,7 +31,6 @@ def on_message(mqttc, obj, msg):
     if msg.retain != False:
         print("Invalid retain: ("+str(msg.retain)+")")
         exit(1)
-    exit(0)
 
 def on_connect(mqttc, obj, flags, rc):
     if rc != 0:
@@ -39,7 +38,6 @@ def on_connect(mqttc, obj, flags, rc):
         exit(rc)
 
 mqttc = mqtt.Client("publish-qos1-test")
-mqttc.message_retry_set(3)
 mqttc.on_connect = on_connect
 mqttc.on_message = on_message
 
@@ -47,5 +45,4 @@ mqttc.connect("localhost", 1888)
 rc = 0
 while rc == 0:
     rc = mqttc.loop()
-print("rc: "+str(rc))
-exit(1)
+exit(0)
diff --git a/test/lib/python/03-publish-b2c-qos2.test b/test/lib/python/03-publish-b2c-qos2.test
index dafcbfa..7593c51 100755
--- a/test/lib/python/03-publish-b2c-qos2.test
+++ b/test/lib/python/03-publish-b2c-qos2.test
@@ -1,8 +1,8 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import os
-import subprocess
 import socket
+import subprocess
 import sys
 import time
 from struct import *
@@ -41,7 +41,6 @@ def on_connect(mqttc, obj, flags, rc):
 
 run = -1
 mqttc = mqtt.Client("publish-qos2-test", run)
-mqttc.message_retry_set(3)
 mqttc.on_connect = on_connect
 mqttc.on_message = on_message
 
diff --git a/test/lib/python/03-publish-c2b-qos1-disconnect.test b/test/lib/python/03-publish-c2b-qos1-disconnect.test
index 374fd7b..89af39f 100755
--- a/test/lib/python/03-publish-c2b-qos1-disconnect.test
+++ b/test/lib/python/03-publish-c2b-qos1-disconnect.test
@@ -1,8 +1,8 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import os
-import subprocess
 import socket
+import subprocess
 import sys
 import time
 from struct import *
@@ -21,10 +21,10 @@ def on_connect(mqttc, obj, flags, rc):
             sent_mid = res[1]
 
 def on_disconnect(mqttc, obj, rc):
-    if rc == 1:
-        mqttc.reconnect()
-    else:
+    if rc == mqtt.MQTT_ERR_SUCCESS:
         run = 0
+    else:
+        mqttc.reconnect()
 
 def on_publish(mqttc, obj, mid):
     global sent_mid
@@ -34,7 +34,6 @@ def on_publish(mqttc, obj, mid):
         exit(1)
 
 mqttc = mqtt.Client("publish-qos1-test", clean_session=False)
-mqttc.message_retry_set(3)
 mqttc.on_connect = on_connect
 mqttc.on_disconnect = on_disconnect
 mqttc.on_publish = on_publish
diff --git a/test/lib/python/03-publish-c2b-qos1-timeout.test b/test/lib/python/03-publish-c2b-qos1-timeout.test
deleted file mode 100755
index b691267..0000000
--- a/test/lib/python/03-publish-c2b-qos1-timeout.test
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/usr/bin/env python
-
-import os
-import subprocess
-import socket
-import sys
-import time
-from struct import *
-
-import paho.mqtt.client as mqtt
-
-sent_mid = -1
-
-def on_connect(mqttc, obj, flags, rc):
-    global sent_mid
-    if rc != 0:
-        exit(rc)
-    else:
-        res = mqttc.publish("pub/qos1/test", "message", 1)
-        sent_mid = res[1]
-
-def on_disconnect(mqttc, obj, rc):
-    run = 0
-
-def on_publish(mqttc, obj, mid):
-    global sent_mid
-    if mid == sent_mid:
-        mqttc.disconnect()
-    else:
-        exit(1)
-
-run = -1
-mqttc = mqtt.Client("publish-qos1-test", run)
-mqttc.message_retry_set(3)
-mqttc.on_connect = on_connect
-mqttc.on_disconnect = on_disconnect
-mqttc.on_publish = on_publish
-
-mqttc.connect("localhost", 1888)
-rc = 0
-while run == -1 and rc == 0:
-    rc = mqttc.loop()
-
-exit(run)
diff --git a/test/lib/python/03-publish-c2b-qos2-disconnect.test b/test/lib/python/03-publish-c2b-qos2-disconnect.test
index 2b767a4..69aee78 100755
--- a/test/lib/python/03-publish-c2b-qos2-disconnect.test
+++ b/test/lib/python/03-publish-c2b-qos2-disconnect.test
@@ -1,8 +1,8 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import os
-import subprocess
 import socket
+import subprocess
 import sys
 import time
 from struct import *
@@ -21,16 +21,15 @@ def on_connect(mqttc, obj, flags, rc):
             first_connection = 0
 
 def on_disconnect(mqttc, obj, rc):
-    if rc == 1:
-        mqttc.reconnect()
-    else:
+    if rc == 0:
         run = 0
+    else:
+        mqttc.reconnect()
 
 def on_publish(mqttc, obj, mid):
     mqttc.disconnect()
 
 mqttc = mqtt.Client("publish-qos2-test", clean_session=False)
-mqttc.message_retry_set(3)
 mqttc.on_connect = on_connect
 mqttc.on_disconnect = on_disconnect
 mqttc.on_publish = on_publish
diff --git a/test/lib/python/03-publish-c2b-qos2-timeout.test b/test/lib/python/03-publish-c2b-qos2-timeout.test
deleted file mode 100755
index 17f8b7c..0000000
--- a/test/lib/python/03-publish-c2b-qos2-timeout.test
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/usr/bin/env python
-
-import os
-import subprocess
-import socket
-import sys
-import time
-from struct import *
-
-import paho.mqtt.client as mqtt
-
-
-def on_connect(mqttc, obj, flags, rc):
-    if rc != 0:
-        exit(rc)
-    else:
-        mqttc.publish("pub/qos2/test", "message", 2)
-
-def on_disconnect(mqttc, obj, rc):
-    run = 0
-
-def on_publish(mqttc, obj, mid):
-    mqttc.disconnect()
-
-run = -1
-mqttc = mqtt.Client("publish-qos2-test", run)
-mqttc.message_retry_set(3)
-mqttc.on_connect = on_connect
-mqttc.on_disconnect = on_disconnect
-mqttc.on_publish = on_publish
-
-mqttc.connect("localhost", 1888)
-rc = 0
-while run == -1 and rc == 0:
-    rc = mqttc.loop()
-
-exit(run)
diff --git a/test/lib/python/03-publish-helper-qos0-v5.test b/test/lib/python/03-publish-helper-qos0-v5.test
new file mode 100755
index 0000000..50a8db5
--- /dev/null
+++ b/test/lib/python/03-publish-helper-qos0-v5.test
@@ -0,0 +1,15 @@
+#!/usr/bin/env python3
+
+
+import paho.mqtt.client
+import paho.mqtt.publish
+
+paho.mqtt.publish.single(
+    "pub/qos0/test",
+    "message",
+    qos=0,
+    hostname="localhost",
+    port=1888,
+    client_id="publish-helper-qos0-test",
+    protocol=paho.mqtt.client.MQTTv5
+)
diff --git a/test/lib/python/03-publish-helper-qos0.test b/test/lib/python/03-publish-helper-qos0.test
index d37887b..52b8085 100755
--- a/test/lib/python/03-publish-helper-qos0.test
+++ b/test/lib/python/03-publish-helper-qos0.test
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 
 import paho.mqtt.publish
diff --git a/test/lib/python/03-publish-helper-qos1-disconnect.test b/test/lib/python/03-publish-helper-qos1-disconnect.test
index 85c4801..a989b64 100755
--- a/test/lib/python/03-publish-helper-qos1-disconnect.test
+++ b/test/lib/python/03-publish-helper-qos1-disconnect.test
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 
 import paho.mqtt.publish
diff --git a/test/lib/python/03-publish-qos0-no-payload.test b/test/lib/python/03-publish-qos0-no-payload.test
index fa44204..19ebbd8 100755
--- a/test/lib/python/03-publish-qos0-no-payload.test
+++ b/test/lib/python/03-publish-qos0-no-payload.test
@@ -1,8 +1,8 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import os
-import subprocess
 import socket
+import subprocess
 import sys
 import time
 from struct import *
diff --git a/test/lib/python/03-publish-qos0.test b/test/lib/python/03-publish-qos0.test
index af4f892..6801674 100755
--- a/test/lib/python/03-publish-qos0.test
+++ b/test/lib/python/03-publish-qos0.test
@@ -1,8 +1,8 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import os
-import subprocess
 import socket
+import subprocess
 import sys
 import time
 from struct import *
diff --git a/test/lib/python/04-retain-qos0.test b/test/lib/python/04-retain-qos0.test
index 4a46249..2d51dad 100755
--- a/test/lib/python/04-retain-qos0.test
+++ b/test/lib/python/04-retain-qos0.test
@@ -1,8 +1,8 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import os
-import subprocess
 import socket
+import subprocess
 import sys
 import time
 from struct import *
diff --git a/test/lib/python/08-ssl-bad-cacert.test b/test/lib/python/08-ssl-bad-cacert.test
index 5e36c18..ed0712c 100755
--- a/test/lib/python/08-ssl-bad-cacert.test
+++ b/test/lib/python/08-ssl-bad-cacert.test
@@ -1,8 +1,8 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import os
-import subprocess
 import socket
+import subprocess
 import sys
 import time
 from struct import *
diff --git a/test/lib/python/08-ssl-connect-cert-auth-pw.test b/test/lib/python/08-ssl-connect-cert-auth-pw.test
new file mode 100755
index 0000000..8011590
--- /dev/null
+++ b/test/lib/python/08-ssl-connect-cert-auth-pw.test
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+
+import os
+import socket
+import subprocess
+import sys
+import time
+from struct import *
+
+import paho.mqtt.client as mqtt
+
+if sys.version_info < (2, 7, 9):
+    print("WARNING: SSL/TLS not supported on Python 2.6")
+    exit(0)
+
+
+def on_connect(mqttc, obj, flags, rc):
+    if rc != 0:
+        exit(rc)
+    else:
+        mqttc.disconnect()
+
+def on_disconnect(mqttc, obj, rc):
+    obj = rc
+
+
+run = -1
+mqttc = mqtt.Client("08-ssl-connect-crt-auth-pw", run)
+mqttc.tls_set("../ssl/all-ca.crt", "../ssl/client-pw.crt", "../ssl/client-pw.key", keyfile_password="password")
+mqttc.on_connect = on_connect
+mqttc.on_disconnect = on_disconnect
+
+mqttc.connect("localhost", 1888)
+while run == -1:
+    mqttc.loop()
+
+exit(run)
diff --git a/test/lib/python/08-ssl-connect-cert-auth.test b/test/lib/python/08-ssl-connect-cert-auth.test
index f5e8cea..f12e014 100755
--- a/test/lib/python/08-ssl-connect-cert-auth.test
+++ b/test/lib/python/08-ssl-connect-cert-auth.test
@@ -1,8 +1,8 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import os
-import subprocess
 import socket
+import subprocess
 import sys
 import time
 from struct import *
diff --git a/test/lib/python/08-ssl-connect-no-auth.test b/test/lib/python/08-ssl-connect-no-auth.test
index 5398b04..6b90533 100755
--- a/test/lib/python/08-ssl-connect-no-auth.test
+++ b/test/lib/python/08-ssl-connect-no-auth.test
@@ -1,8 +1,8 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import os
-import subprocess
 import socket
+import subprocess
 import sys
 import time
 from struct import *
diff --git a/test/lib/python/08-ssl-fake-cacert.test b/test/lib/python/08-ssl-fake-cacert.test
index d671225..178461c 100755
--- a/test/lib/python/08-ssl-fake-cacert.test
+++ b/test/lib/python/08-ssl-fake-cacert.test
@@ -1,12 +1,12 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import os
-import subprocess
 import socket
+import ssl
+import subprocess
 import sys
 import time
 from struct import *
-import ssl
 
 import paho.mqtt.client as mqtt
 
diff --git a/test/mqtt5_opts.py b/test/mqtt5_opts.py
new file mode 100644
index 0000000..55675a7
--- /dev/null
+++ b/test/mqtt5_opts.py
@@ -0,0 +1,5 @@
+MQTT_SUB_OPT_NO_LOCAL = 0x04
+MQTT_SUB_OPT_RETAIN_AS_PUBLISHED = 0x08
+MQTT_SUB_OPT_SEND_RETAIN_ALWAYS = 0x00
+MQTT_SUB_OPT_SEND_RETAIN_NEW = 0x10
+MQTT_SUB_OPT_SEND_RETAIN_NEVER = 0x20
diff --git a/test/mqtt5_props.py b/test/mqtt5_props.py
new file mode 100644
index 0000000..4d61215
--- /dev/null
+++ b/test/mqtt5_props.py
@@ -0,0 +1,76 @@
+import struct
+
+PROP_PAYLOAD_FORMAT_INDICATOR = 1
+PROP_MESSAGE_EXPIRY_INTERVAL = 2
+PROP_CONTENT_TYPE = 3
+PROP_RESPONSE_TOPIC = 8
+PROP_CORRELATION_DATA = 9
+PROP_SUBSCRIPTION_IDENTIFIER = 11
+PROP_SESSION_EXPIRY_INTERVAL = 17
+PROP_ASSIGNED_CLIENT_IDENTIFIER = 18
+PROP_SERVER_KEEP_ALIVE = 19
+PROP_AUTHENTICATION_METHOD = 21
+PROP_AUTHENTICATION_DATA = 22
+PROP_REQUEST_PROBLEM_INFO = 23
+PROP_WILL_DELAY_INTERVAL = 24
+PROP_REQUEST_RESPONSE_INFO = 25
+PROP_RESPONSE_INFO = 26
+PROP_SERVER_REFERENCE = 28
+PROP_REASON_STRING = 31
+PROP_RECEIVE_MAXIMUM = 33
+PROP_TOPIC_ALIAS_MAXIMUM = 34
+PROP_TOPIC_ALIAS = 35
+PROP_MAXIMUM_QOS = 36
+PROP_RETAIN_AVAILABLE = 37
+PROP_USER_PROPERTY = 38
+PROP_MAXIMUM_PACKET_SIZE = 39
+PROP_WILDCARD_SUB_AVAILABLE = 40
+PROP_SUBSCRIPTION_ID_AVAILABLE = 41
+PROP_SHARED_SUB_AVAILABLE = 42
+
+def gen_byte_prop(identifier, byte):
+    prop = struct.pack('BB', identifier, byte)
+    return prop
+
+def gen_uint16_prop(identifier, word):
+    prop = struct.pack('!BH', identifier, word)
+    return prop
+
+def gen_uint32_prop(identifier, word):
+    prop = struct.pack('!BI', identifier, word)
+    return prop
+
+def gen_string_prop(identifier, s):
+    s = s.encode("utf-8")
+    prop = struct.pack('!BH%ds'%(len(s)), identifier, len(s), s)
+    return prop
+
+def gen_string_pair_prop(identifier, s1, s2):
+    s1 = s1.encode("utf-8")
+    s2 = s2.encode("utf-8")
+    prop = struct.pack('!BH%dsH%ds'%(len(s1), len(s2)), identifier, len(s1), s1, len(s2), s2)
+    return prop
+
+def gen_varint_prop(identifier, val):
+    v = pack_varint(val)
+    return struct.pack("!B"+str(len(v))+"s", identifier, v)
+
+def pack_varint(varint):
+    s = b""
+    while True:
+        byte = varint % 128
+        varint = varint // 128
+        # If there are more digits to encode, set the top bit of this digit
+        if varint > 0:
+            byte = byte | 0x80
+
+        s = s + struct.pack("!B", byte)
+        if varint == 0:
+            return s
+
+def prop_finalise(props):
+    if props is None:
+        return pack_varint(0)
+    else:
+        return pack_varint(len(props)) + props
+
diff --git a/test/mqtt5_rc.py b/test/mqtt5_rc.py
new file mode 100644
index 0000000..3987e72
--- /dev/null
+++ b/test/mqtt5_rc.py
@@ -0,0 +1,46 @@
+MQTT_RC_SUCCESS = 0
+MQTT_RC_NORMAL_DISCONNECTION = 0
+MQTT_RC_GRANTED_QOS0 = 0
+MQTT_RC_GRANTED_QOS1 = 1
+MQTT_RC_GRANTED_QOS2 = 2
+MQTT_RC_DISCONNECT_WITH_WILL_MSG = 4
+MQTT_RC_NO_MATCHING_SUBSCRIBERS = 16
+MQTT_RC_NO_SUBSCRIPTION_EXISTED = 17
+MQTT_RC_CONTINUE_AUTHENTICATION = 24
+MQTT_RC_REAUTHENTICATE = 25
+
+MQTT_RC_UNSPECIFIED = 128
+MQTT_RC_MALFORMED_PACKET = 129
+MQTT_RC_PROTOCOL_ERROR = 130
+MQTT_RC_IMPLEMENTATION_SPECIFIC = 131
+MQTT_RC_UNSUPPORTED_PROTOCOL_VERSION = 132
+MQTT_RC_CLIENTID_NOT_VALID = 133
+MQTT_RC_BAD_USERNAME_OR_PASSWORD = 134
+MQTT_RC_NOT_AUTHORIZED = 135
+MQTT_RC_SERVER_UNAVAILABLE = 136
+MQTT_RC_SERVER_BUSY = 137
+MQTT_RC_BANNED = 138
+MQTT_RC_SERVER_SHUTTING_DOWN = 139
+MQTT_RC_BAD_AUTHENTICATION_METHOD = 140
+MQTT_RC_KEEP_ALIVE_TIMEOUT = 141
+MQTT_RC_SESSION_TAKEN_OVER = 142
+MQTT_RC_TOPIC_FILTER_INVALID = 143
+MQTT_RC_TOPIC_NAME_INVALID = 144
+MQTT_RC_PACKET_ID_IN_USE = 145
+MQTT_RC_PACKET_ID_NOT_FOUND = 146
+MQTT_RC_RECEIVE_MAXIMUM_EXCEEDED = 147
+MQTT_RC_TOPIC_ALIAS_INVALID = 148
+MQTT_RC_PACKET_TOO_LARGE = 149
+MQTT_RC_MESSAGE_RATE_TOO_HIGH = 150
+MQTT_RC_QUOTA_EXCEEDED = 151
+MQTT_RC_ADMINISTRATIVE_ACTION = 152
+MQTT_RC_PAYLOAD_FORMAT_INVALID = 153
+MQTT_RC_RETAIN_NOT_SUPPORTED = 154
+MQTT_RC_QOS_NOT_SUPPORTED = 155
+MQTT_RC_USE_ANOTHER_SERVER = 156
+MQTT_RC_SERVER_MOVED = 157
+MQTT_RC_SHARED_SUBS_NOT_SUPPORTED = 158
+MQTT_RC_CONNECTION_RATE_EXCEEDED = 159
+MQTT_RC_MAXIMUM_CONNECT_TIME = 160
+MQTT_RC_SUBSCRIPTION_IDS_NOT_SUPPORTED = 161
+MQTT_RC_WILDCARD_SUBS_NOT_SUPPORTED = 162
diff --git a/test/paho_test.py b/test/paho_test.py
index 1a84b45..65c753d 100644
--- a/test/paho_test.py
+++ b/test/paho_test.py
@@ -1,18 +1,29 @@
 import binascii
-import struct
+import errno
+import os
 import socket
+import struct
+import subprocess
 import sys
+import time
 
 try:
     import ssl
 except ImportError:
     ssl = None
 
+import atexit
+
+import __main__
+import mqtt5_props
+
+vg_index = 1
+vg_logfiles = []
 
-if sys.version_info[0] >= 3:
-    # define some alias for python2 compatibility
-    unicode = str
 
+class TestError(Exception):
+    def __init__(self, message="Mismatched packets"):
+        self.message = message
 
 def create_server_socket():
     sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -49,8 +60,20 @@ def expect_packet(sock, name, expected):
     else:
         rlen = 1
 
-    packet_recvd = sock.recv(rlen)
-    return packet_matches(name, packet_recvd, expected)
+    packet_recvd = b""
+    try:
+        while len(packet_recvd) < rlen:
+            data = sock.recv(rlen-len(packet_recvd))
+            if len(data) == 0:
+                break
+            packet_recvd += data
+    except socket.timeout:
+        pass
+
+    if packet_matches(name, packet_recvd, expected):
+        return True
+    else:
+        raise TestError
 
 
 def packet_matches(name, recvd, expected):
@@ -67,9 +90,66 @@ def packet_matches(name, recvd, expected):
             print("Expected (not decoded): 0x" +
                   binascii.b2a_hex(expected).decode('utf8'))
 
-        return 0
+        return False
+    else:
+        return True
+
+
+def receive_unordered(sock, recv1_packet, recv2_packet, error_string):
+    expected1 = recv1_packet + recv2_packet
+    expected2 = recv2_packet + recv1_packet
+    recvd = b''
+    while len(recvd) < len(expected1):
+        r = sock.recv(1)
+        if len(r) == 0:
+            raise ValueError(error_string)
+        recvd += r
+
+    if recvd == expected1 or recvd == expected2:
+        return
+    else:
+        packet_matches(error_string, recvd, expected2)
+        raise ValueError(error_string)
+
+
+def do_send_receive(sock, send_packet, receive_packet, error_string="send receive error"):
+    size = len(send_packet)
+    total_sent = 0
+    while total_sent < size:
+        sent = sock.send(send_packet[total_sent:])
+        if sent == 0:
+            raise RuntimeError("socket connection broken")
+        total_sent += sent
+
+    if expect_packet(sock, error_string, receive_packet):
+        return sock
     else:
-        return 1
+        sock.close()
+        raise ValueError
+
+
+# Useful for mocking a client receiving (with ack) a qos1 publish
+def do_receive_send(sock, receive_packet, send_packet, error_string="receive send error"):
+    if expect_packet(sock, error_string, receive_packet):
+        size = len(send_packet)
+        total_sent = 0
+        while total_sent < size:
+            sent = sock.send(send_packet[total_sent:])
+            if sent == 0:
+                raise RuntimeError("socket connection broken")
+            total_sent += sent
+        return sock
+    else:
+        sock.close()
+        raise ValueError
+
+
+def do_client_connect(connect_packet, connack_packet, hostname="localhost", port=1888, timeout=10, connack_error="connack"):
+    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    sock.settimeout(timeout)
+    sock.connect((hostname, port))
+
+    return do_send_receive(sock, connect_packet, connack_packet, connack_error)
 
 
 def remaining_length(packet):
@@ -89,11 +169,24 @@ def remaining_length(packet):
     return (packet, rl)
 
 
+def to_hex_string(packet):
+    if len(packet) == 0:
+        return ""
+
+    s = ""
+    while len(packet) > 0:
+        packet0 = struct.unpack("!B", packet[0])
+        s = s+hex(packet0[0]) + " "
+        packet = packet[1:]
+
+    return s
+
+
 def to_string(packet):
     if len(packet) == 0:
         return ""
 
-    packet0 = struct.unpack("!B", packet[0:1])
+    packet0 = struct.unpack("!B%ds" % (len(packet)-1), bytes(packet))
     packet0 = packet0[0]
     cmd = packet0 & 0xF0
     if cmd == 0x00:
@@ -106,7 +199,7 @@ def to_string(packet):
         (slen, packet) = struct.unpack(pack_format, packet)
         pack_format = "!" + str(slen) + 'sBBH' + str(len(packet) - slen - 4) + 's'
         (protocol, proto_ver, flags, keepalive, packet) = struct.unpack(pack_format, packet)
-        s = "CONNECT, proto=" + protocol + str(proto_ver) + ", keepalive=" + str(keepalive)
+        s = "CONNECT, proto=" + str(protocol) + str(proto_ver) + ", keepalive=" + str(keepalive)
         if flags & 2:
             s = s + ", clean-session"
         else:
@@ -116,14 +209,14 @@ def to_string(packet):
         (slen, packet) = struct.unpack(pack_format, packet)
         pack_format = "!" + str(slen) + 's' + str(len(packet) - slen) + 's'
         (client_id, packet) = struct.unpack(pack_format, packet)
-        s = s + ", id=" + client_id
+        s = s + ", id=" + str(client_id)
 
         if flags & 4:
             pack_format = "!H" + str(len(packet) - 2) + 's'
             (slen, packet) = struct.unpack(pack_format, packet)
             pack_format = "!" + str(slen) + 's' + str(len(packet) - slen) + 's'
             (will_topic, packet) = struct.unpack(pack_format, packet)
-            s = s + ", will-topic=" + will_topic
+            s = s + ", will-topic=" + str(will_topic)
 
             pack_format = "!H" + str(len(packet) - 2) + 's'
             (slen, packet) = struct.unpack(pack_format, packet)
@@ -139,20 +232,30 @@ def to_string(packet):
             (slen, packet) = struct.unpack(pack_format, packet)
             pack_format = "!" + str(slen) + 's' + str(len(packet) - slen) + 's'
             (username, packet) = struct.unpack(pack_format, packet)
-            s = s + ", username=" + username
+            s = s + ", username=" + str(username)
 
         if flags & 64:
             pack_format = "!H" + str(len(packet) - 2) + 's'
             (slen, packet) = struct.unpack(pack_format, packet)
             pack_format = "!" + str(slen) + 's' + str(len(packet) - slen) + 's'
             (password, packet) = struct.unpack(pack_format, packet)
-            s = s + ", password=" + password
+            s = s + ", password=" + str(password)
+
+        if flags & 1:
+            s = s + ", reserved=1"
 
         return s
     elif cmd == 0x20:
         # CONNACK
-        (cmd, rl, resv, rc) = struct.unpack('!BBBB', packet)
-        return "CONNACK, rl=" + str(rl) + ", res=" + str(resv) + ", rc=" + str(rc)
+        if len(packet) == 4:
+            (cmd, rl, resv, rc) = struct.unpack('!BBBB', packet)
+            return "CONNACK, rl="+str(rl)+", res="+str(resv)+", rc="+str(rc)
+        elif len(packet) == 5:
+            (cmd, rl, flags, reason_code, proplen) = struct.unpack('!BBBBB', packet)
+            return "CONNACK, rl="+str(rl)+", flags="+str(flags)+", rc="+str(reason_code)+", proplen="+str(proplen)
+        else:
+            return "CONNACK, (not decoded)"
+
     elif cmd == 0x30:
         # PUBLISH
         dup = (packet0 & 0x08) >> 3
@@ -163,23 +266,30 @@ def to_string(packet):
         (tlen, packet) = struct.unpack(pack_format, packet)
         pack_format = "!" + str(tlen) + 's' + str(len(packet) - tlen) + 's'
         (topic, packet) = struct.unpack(pack_format, packet)
-        s = "PUBLISH, rl=" + str(rl) + ", topic=" + topic + ", qos=" + str(qos) + ", retain=" + str(
-            retain) + ", dup=" + str(dup)
+        s = "PUBLISH, rl=" + str(rl) + ", topic=" + str(topic) + ", qos=" + str(qos) + ", retain=" + str(retain) + ", dup=" + str(dup)
         if qos > 0:
             pack_format = "!H" + str(len(packet) - 2) + 's'
             (mid, packet) = struct.unpack(pack_format, packet)
             s = s + ", mid=" + str(mid)
 
-        s = s + ", payload=" + packet
+        s = s + ", payload=" + str(packet)
         return s
     elif cmd == 0x40:
         # PUBACK
-        (cmd, rl, mid) = struct.unpack('!BBH', packet)
-        return "PUBACK, rl=" + str(rl) + ", mid=" + str(mid)
+        if len(packet) == 5:
+            (cmd, rl, mid, reason_code) = struct.unpack('!BBHB', packet)
+            return "PUBACK, rl="+str(rl)+", mid="+str(mid)+", reason_code="+str(reason_code)
+        else:
+            (cmd, rl, mid) = struct.unpack('!BBH', packet)
+            return "PUBACK, rl="+str(rl)+", mid="+str(mid)
     elif cmd == 0x50:
         # PUBREC
-        (cmd, rl, mid) = struct.unpack('!BBH', packet)
-        return "PUBREC, rl=" + str(rl) + ", mid=" + str(mid)
+        if len(packet) == 5:
+            (cmd, rl, mid, reason_code) = struct.unpack('!BBHB', packet)
+            return "PUBREC, rl="+str(rl)+", mid="+str(mid)+", reason_code="+str(reason_code)
+        else:
+            (cmd, rl, mid) = struct.unpack('!BBH', packet)
+            return "PUBREC, rl="+str(rl)+", mid="+str(mid)
     elif cmd == 0x60:
         # PUBREL
         dup = (packet0 & 0x08) >> 3
@@ -201,7 +311,7 @@ def to_string(packet):
             (tlen, packet) = struct.unpack(pack_format, packet)
             pack_format = "!" + str(tlen) + 'sB' + str(len(packet) - tlen - 1) + 's'
             (topic, qos, packet) = struct.unpack(pack_format, packet)
-            s = s + ", topic" + str(topic_index) + "=" + topic + "," + str(qos)
+            s = s + ", topic" + str(topic_index) + "=" + str(topic) + "," + str(qos)
         return s
     elif cmd == 0x90:
         # SUBACK
@@ -227,7 +337,7 @@ def to_string(packet):
             (tlen, packet) = struct.unpack(pack_format, packet)
             pack_format = "!" + str(tlen) + 's' + str(len(packet) - tlen) + 's'
             (topic, packet) = struct.unpack(pack_format, packet)
-            s = s + ", topic" + str(topic_index) + "=" + topic
+            s = s + ", topic" + str(topic_index) + "=" + str(topic)
         return s
     elif cmd == 0xB0:
         # UNSUBACK
@@ -243,32 +353,103 @@ def to_string(packet):
         return "PINGRESP, rl=" + str(rl)
     elif cmd == 0xE0:
         # DISCONNECT
-        (cmd, rl) = struct.unpack('!BB', packet)
-        return "DISCONNECT, rl=" + str(rl)
+        if len(packet) == 3:
+            (cmd, rl, reason_code) = struct.unpack('!BBB', packet)
+            return "DISCONNECT, rl="+str(rl)+", reason_code="+str(reason_code)
+        else:
+            (cmd, rl) = struct.unpack('!BB', packet)
+            return "DISCONNECT, rl="+str(rl)
     elif cmd == 0xF0:
-        # Reserved
-        return "0xF0"
+        # AUTH
+        (cmd, rl) = struct.unpack('!BB', packet)
+        return "AUTH, rl="+str(rl)
+
+
+def read_varint(sock, rl):
+    varint = 0
+    multiplier = 1
+    while True:
+        byte = sock.recv(1)
+        byte, = struct.unpack("!B", byte)
+        varint += (byte & 127)*multiplier
+        multiplier *= 128
+        rl -= 1
+        if byte & 128 == 0x00:
+            return (varint, rl)
+
 
+def mqtt_read_string(sock, rl):
+    slen = sock.recv(2)
+    slen, = struct.unpack("!H", slen)
+    payload = sock.recv(slen)
+    payload, = struct.unpack("!%ds" % (slen), payload)
+    rl -= (2 + slen)
+    return (payload, rl)
 
-def gen_connect(client_id, clean_session=True, keepalive=60, username=None, password=None, will_topic=None, will_qos=0,
-                will_retain=False, will_payload="", proto_name=None, proto_ver=4):
-    proto_name = b"MQTT" if proto_ver >= 4 else b"MQIsdp"
 
-    if client_id is None:
+def read_publish(sock, proto_ver=4):
+    cmd, = struct.unpack("!B", sock.recv(1))
+    if cmd & 0xF0 != 0x30:
+        raise ValueError
+
+    qos = (cmd & 0x06) >> 1
+    rl, t = read_varint(sock, 0)
+    topic, rl = mqtt_read_string(sock, rl)
+
+    if qos > 0:
+        sock.recv(2)
+        rl -= 1
+
+    if proto_ver == 5:
+        proplen, rl = read_varint(sock, rl)
+        sock.recv(proplen)
+        rl -= proplen
+
+    payload = sock.recv(rl).decode('utf-8')
+    return payload
+
+
+def gen_connect(client_id, clean_session=True, keepalive=60, username=None, password=None, will_topic=None, will_qos=0, will_retain=False, will_payload=b"", proto_ver=4, connect_reserved=False, properties=b"", will_properties=b"", session_expiry=-1):
+    if (proto_ver&0x7F) == 3 or proto_ver == 0:
         remaining_length = 12
+    elif (proto_ver&0x7F) == 4 or proto_ver == 5:
+        remaining_length = 10
     else:
-        client_id = client_id.encode('utf-8')
-        remaining_length = 2 + len(proto_name) + 1 + 1 + 2 + 2 + len(client_id)
+        raise ValueError
+
+    if client_id is not None:
+        client_id = client_id.encode("utf-8")
+        remaining_length = remaining_length + 2+len(client_id)
+    else:
+        remaining_length = remaining_length + 2
+
     connect_flags = 0
+
+    if connect_reserved:
+        connect_flags = connect_flags | 0x01
+
     if clean_session:
         connect_flags = connect_flags | 0x02
 
+    if proto_ver == 5:
+        if properties == b"":
+            properties += mqtt5_props.gen_uint16_prop(mqtt5_props.PROP_RECEIVE_MAXIMUM, 20)
+
+        if session_expiry != -1:
+            properties += mqtt5_props.gen_uint32_prop(mqtt5_props.PROP_SESSION_EXPIRY_INTERVAL, session_expiry)
+
+        properties = mqtt5_props.prop_finalise(properties)
+        remaining_length += len(properties)
+
     if will_topic is not None:
         will_topic = will_topic.encode('utf-8')
         remaining_length = remaining_length + 2 + len(will_topic) + 2 + len(will_payload)
         connect_flags = connect_flags | 0x04 | ((will_qos & 0x03) << 3)
         if will_retain:
             connect_flags = connect_flags | 32
+        if proto_ver == 5:
+            will_properties = mqtt5_props.prop_finalise(will_properties)
+            remaining_length += len(will_properties)
 
     if username is not None:
         username = username.encode('utf-8')
@@ -281,16 +462,24 @@ def gen_connect(client_id, clean_session=True, keepalive=60, username=None, pass
 
     rl = pack_remaining_length(remaining_length)
     packet = struct.pack("!B" + str(len(rl)) + "s", 0x10, rl)
-    packet = packet + struct.pack("!H" + str(len(proto_name)) + "sBBH",
-                                  len(proto_name), proto_name,
-                                  proto_ver, connect_flags, keepalive)
+    if (proto_ver&0x7F) == 3 or proto_ver == 0:
+        packet = packet + struct.pack("!H6sBBH", len(b"MQIsdp"), b"MQIsdp", proto_ver, connect_flags, keepalive)
+    elif (proto_ver&0x7F) == 4 or proto_ver == 5:
+        packet = packet + struct.pack("!H4sBBH", len(b"MQTT"), b"MQTT", proto_ver, connect_flags, keepalive)
+
+    if proto_ver == 5:
+        packet += properties
+
     if client_id is not None:
-        packet = packet + struct.pack("!H" + str(len(client_id)) + "s", len(client_id), client_id)
+        packet = packet + struct.pack("!H" + str(len(client_id)) + "s", len(client_id), bytes(client_id))
+    else:
+        packet = packet + struct.pack("!H", 0)
 
     if will_topic is not None:
+        packet += will_properties
         packet = packet + struct.pack("!H" + str(len(will_topic)) + "s", len(will_topic), will_topic)
         if len(will_payload) > 0:
-            packet = packet + struct.pack("!H" + str(len(will_payload)) + "s", len(will_payload), will_payload)
+            packet = packet + struct.pack("!H" + str(len(will_payload)) + "s", len(will_payload), will_payload.encode('utf8'))
         else:
             packet = packet + struct.pack("!H", 0)
 
@@ -300,85 +489,196 @@ def gen_connect(client_id, clean_session=True, keepalive=60, username=None, pass
             packet = packet + struct.pack("!H" + str(len(password)) + "s", len(password), password)
     return packet
 
+def gen_connack(flags=0, rc=0, proto_ver=4, properties=b"", property_helper=True):
+    if proto_ver == 5:
+        if property_helper == True:
+            if properties is not None:
+                properties = mqtt5_props.gen_uint16_prop(mqtt5_props.PROP_TOPIC_ALIAS_MAXIMUM, 10) \
+                    + properties + mqtt5_props.gen_uint16_prop(mqtt5_props.PROP_RECEIVE_MAXIMUM, 20)
+            else:
+                properties = b""
+        properties = mqtt5_props.prop_finalise(properties)
+
+        packet = struct.pack('!BBBB', 32, 2+len(properties), flags, rc) + properties
+    else:
+        packet = struct.pack('!BBBB', 32, 2, flags, rc);
 
-def gen_connack(resv=0, rc=0):
-    return struct.pack('!BBBB', 32, 2, resv, rc)
-
+    return packet
 
-def gen_publish(topic, qos, payload=None, retain=False, dup=False, mid=0):
-    if isinstance(topic, unicode):
-        topic = topic.encode('utf-8')
-    rl = 2 + len(topic)
-    pack_format = "!BBH" + str(len(topic)) + "s"
+def gen_publish(topic, qos, payload=None, retain=False, dup=False, mid=0, proto_ver=4, properties=b""):
+    if isinstance(topic, str):
+        topic = topic.encode("utf-8")
+    rl = 2+len(topic)
+    pack_format = "H"+str(len(topic))+"s"
     if qos > 0:
         rl = rl + 2
         pack_format = pack_format + "H"
+
+    if proto_ver == 5:
+        properties = mqtt5_props.prop_finalise(properties)
+        rl += len(properties)
+        # This will break if len(properties) > 127
+        pack_format = pack_format + "%ds"%(len(properties))
+
     if payload is not None:
+        payload = payload.encode("utf-8")
         rl = rl + len(payload)
         pack_format = pack_format + str(len(payload)) + "s"
     else:
         payload = b""
         pack_format = pack_format + "0s"
 
+    rlpacked = pack_remaining_length(rl)
     cmd = 48 | (qos << 1)
     if retain:
         cmd = cmd + 1
     if dup:
         cmd = cmd + 8
 
-    if qos > 0:
-        return struct.pack(pack_format, cmd, rl, len(topic), topic, mid, payload)
+    if proto_ver == 5:
+        if qos > 0:
+            return struct.pack("!B" + str(len(rlpacked))+"s" + pack_format, cmd, rlpacked, len(topic), topic, mid, properties, payload)
+        else:
+            return struct.pack("!B" + str(len(rlpacked))+"s" + pack_format, cmd, rlpacked, len(topic), topic, properties, payload)
     else:
-        return struct.pack(pack_format, cmd, rl, len(topic), topic, payload)
-
-
-def gen_puback(mid):
-    return struct.pack('!BBH', 64, 2, mid)
-
-
-def gen_pubrec(mid):
-    return struct.pack('!BBH', 80, 2, mid)
+        if qos > 0:
+            return struct.pack("!B" + str(len(rlpacked))+"s" + pack_format, cmd, rlpacked, len(topic), topic, mid, payload)
+        else:
+            return struct.pack("!B" + str(len(rlpacked))+"s" + pack_format, cmd, rlpacked, len(topic), topic, payload)
 
+def _gen_command_with_mid(cmd, mid, proto_ver=4, reason_code=-1, properties=None):
+    if proto_ver == 5 and (reason_code != -1 or properties is not None):
+        if reason_code == -1:
+            reason_code = 0
 
-def gen_pubrel(mid):
-    cmd = 96 + 2
-    return struct.pack('!BBH', cmd, 2, mid)
+        if properties is None:
+            return struct.pack('!BBHB', cmd, 3, mid, reason_code)
+        elif properties == "":
+            return struct.pack('!BBHBB', cmd, 4, mid, reason_code, 0)
+        else:
+            properties = mqtt5_props.prop_finalise(properties)
+            pack_format = "!BBHB"+str(len(properties))+"s"
+            return struct.pack(pack_format, cmd, 2+1+len(properties), mid, reason_code, properties)
+    else:
+        return struct.pack('!BBH', cmd, 2, mid)
 
+def gen_puback(mid, proto_ver=4, reason_code=-1, properties=None):
+    return _gen_command_with_mid(64, mid, proto_ver, reason_code, properties)
 
-def gen_pubcomp(mid):
-    return struct.pack('!BBH', 112, 2, mid)
+def gen_pubrec(mid, proto_ver=4, reason_code=-1, properties=None):
+    return _gen_command_with_mid(80, mid, proto_ver, reason_code, properties)
 
+def gen_pubrel(mid, dup=False, proto_ver=4, reason_code=-1, properties=None):
+    if dup:
+        cmd = 96+8+2
+    else:
+        cmd = 96+2
+    return _gen_command_with_mid(cmd, mid, proto_ver, reason_code, properties)
 
-def gen_subscribe(mid, topic, qos):
-    topic = topic.encode('utf-8')
-    pack_format = "!BBHH" + str(len(topic)) + "sB"
-    return struct.pack(pack_format, 130, 2 + 2 + len(topic) + 1, mid, len(topic), topic, qos)
+def gen_pubcomp(mid, proto_ver=4, reason_code=-1, properties=None):
+    return _gen_command_with_mid(112, mid, proto_ver, reason_code, properties)
 
 
-def gen_suback(mid, qos):
-    return struct.pack('!BBHB', 144, 2 + 1, mid, qos)
+def gen_subscribe(mid, topic, qos, cmd=130, proto_ver=4, properties=b""):
+    topic = topic.encode("utf-8")
+    packet = struct.pack("!B", cmd)
+    if proto_ver == 5:
+        if properties == b"":
+            packet += pack_remaining_length(2+1+2+len(topic)+1)
+            pack_format = "!HBH"+str(len(topic))+"sB"
+            return packet + struct.pack(pack_format, mid, 0, len(topic), topic, qos)
+        else:
+            properties = mqtt5_props.prop_finalise(properties)
+            packet += pack_remaining_length(2+1+2+len(topic)+len(properties))
+            pack_format = "!H"+str(len(properties))+"s"+"H"+str(len(topic))+"sB"
+            return packet + struct.pack(pack_format, mid, properties, len(topic), topic, qos)
+    else:
+        packet += pack_remaining_length(2+2+len(topic)+1)
+        pack_format = "!HH"+str(len(topic))+"sB"
+        return packet + struct.pack(pack_format, mid, len(topic), topic, qos)
 
 
-def gen_unsubscribe(mid, topic):
-    topic = topic.encode('utf-8')
-    pack_format = "!BBHH" + str(len(topic)) + "s"
-    return struct.pack(pack_format, 162, 2 + 2 + len(topic), mid, len(topic), topic)
+def gen_suback(mid, qos, proto_ver=4):
+    if proto_ver == 5:
+        return struct.pack('!BBHBB', 144, 2+1+1, mid, 0, qos)
+    else:
+        return struct.pack('!BBHB', 144, 2+1, mid, qos)
+
+def gen_unsubscribe(mid, topic, cmd=162, proto_ver=4, properties=b""):
+    topic = topic.encode("utf-8")
+    if proto_ver == 5:
+        if properties == b"":
+            pack_format = "!BBHBH"+str(len(topic))+"s"
+            return struct.pack(pack_format, cmd, 2+2+len(topic)+1, mid, 0, len(topic), topic)
+        else:
+            properties = mqtt5_props.prop_finalise(properties)
+            packet = struct.pack("!B", cmd)
+            l = 2+2+len(topic)+1+len(properties)
+            packet += pack_remaining_length(l)
+            pack_format = "!HB"+str(len(properties))+"sH"+str(len(topic))+"s"
+            packet += struct.pack(pack_format, mid, len(properties), properties, len(topic), topic)
+            return packet
+    else:
+        pack_format = "!BBHH"+str(len(topic))+"s"
+        return struct.pack(pack_format, cmd, 2+2+len(topic), mid, len(topic), topic)
 
+def gen_unsubscribe_multiple(mid, topics, proto_ver=4):
+    packet = b""
+    remaining_length = 0
+    for t in topics:
+        t = t.encode("utf-8")
+        remaining_length += 2+len(t)
+        packet += struct.pack("!H"+str(len(t))+"s", len(t), t)
 
-def gen_unsuback(mid):
-    return struct.pack('!BBH', 176, 2, mid)
+    if proto_ver == 5:
+        remaining_length += 2+1
 
+        return struct.pack("!BBHB", 162, remaining_length, mid, 0) + packet
+    else:
+        remaining_length += 2
+
+        return struct.pack("!BBH", 162, remaining_length, mid) + packet
+
+def gen_unsuback(mid, reason_code=0, proto_ver=4):
+    if proto_ver == 5:
+        if isinstance(reason_code, list):
+            reason_code_count = len(reason_code)
+            p = struct.pack('!BBHB', 176, 3+reason_code_count, mid, 0)
+            for r in reason_code:
+                p += struct.pack('B', r)
+            return p
+        else:
+            return struct.pack('!BBHBB', 176, 4, mid, 0, reason_code)
+    else:
+        return struct.pack('!BBH', 176, 2, mid)
 
 def gen_pingreq():
     return struct.pack('!BB', 192, 0)
 
-
 def gen_pingresp():
     return struct.pack('!BB', 208, 0)
 
 
-def gen_disconnect():
-    return struct.pack('!BB', 224, 0)
+def _gen_short(cmd, reason_code=-1, proto_ver=5, properties=None):
+    if proto_ver == 5 and (reason_code != -1 or properties is not None):
+        if reason_code == -1:
+             reason_code = 0
+
+        if properties is None:
+            return struct.pack('!BBB', cmd, 1, reason_code)
+        elif properties == "":
+            return struct.pack('!BBBB', cmd, 2, reason_code, 0)
+        else:
+            properties = mqtt5_props.prop_finalise(properties)
+            return struct.pack("!BBB", cmd, 1+len(properties), reason_code) + properties
+    else:
+        return struct.pack('!BB', cmd, 0)
+
+def gen_disconnect(reason_code=-1, proto_ver=4, properties=None):
+    return _gen_short(0xE0, reason_code, proto_ver, properties)
+
+def gen_auth(reason_code=-1, properties=None):
+    return _gen_short(0xF0, reason_code, 5, properties)
 
 
 def pack_remaining_length(remaining_length):
@@ -393,3 +693,43 @@ def pack_remaining_length(remaining_length):
         s = s + struct.pack("!B", byte)
         if remaining_length == 0:
             return s
+
+
+def get_port(count=1):
+    if count == 1:
+        if len(sys.argv) == 2:
+            return int(sys.argv[1])
+        else:
+            return 1888
+    else:
+        if len(sys.argv) == 1+count:
+            p = ()
+            for i in range(0, count):
+                p = p + (int(sys.argv[1+i]),)
+            return p
+        else:
+            return tuple(range(1888, 1888+count))
+
+
+def get_lib_port():
+    if len(sys.argv) == 3:
+        return int(sys.argv[2])
+    else:
+        return 1888
+
+
+def do_ping(sock, error_string="pingresp"):
+     do_send_receive(sock, gen_pingreq(), gen_pingresp(), error_string)
+
+
+@atexit.register
+def test_cleanup():
+    global vg_logfiles
+
+    if os.environ.get('MOSQ_USE_VALGRIND') is not None:
+        for f in vg_logfiles:
+            try:
+                if os.stat(f).st_size == 0:
+                    os.remove(f)
+            except OSError:
+                pass
diff --git a/test/ssl/all-ca.crt b/test/ssl/all-ca.crt
index df5155d..06b6593 100644
--- a/test/ssl/all-ca.crt
+++ b/test/ssl/all-ca.crt
@@ -5,97 +5,97 @@ Certificate:
         Signature Algorithm: sha256WithRSAEncryption
         Issuer: C=GB, ST=Derbyshire, L=Derby, O=Paho Project, OU=Testing, CN=Root CA
         Validity
-            Not Before: Jul 28 09:12:10 2020 GMT
-            Not After : Jul 27 09:12:10 2025 GMT
+            Not Before: Jul  7 11:14:42 2021 GMT
+            Not After : Jul  6 11:14:42 2026 GMT
         Subject: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA
         Subject Public Key Info:
             Public Key Algorithm: rsaEncryption
                 RSA Public-Key: (2048 bit)
                 Modulus:
-                    00:c2:33:17:27:8f:e6:b3:b3:96:d8:fc:cc:11:8d:
-                    5e:da:3d:a5:8e:3a:14:c7:e6:38:80:25:e6:27:c4:
-                    cf:6f:3c:b8:18:4a:6e:53:ee:81:dc:c6:e2:4a:29:
-                    18:d9:d3:d8:45:3f:19:2c:01:b5:61:ce:b9:f1:89:
-                    9b:0b:0c:af:f2:38:05:2e:cf:e4:57:c5:9f:4f:fc:
-                    42:a1:2f:cc:2a:59:fa:1a:4a:24:24:55:60:d3:25:
-                    d5:a7:0f:d8:5d:f3:9a:dc:d8:13:8c:37:ae:8d:27:
-                    bb:d2:2c:37:34:3e:11:16:02:46:03:b2:48:24:de:
-                    5e:d0:65:63:cd:0d:af:58:48:13:dc:93:d0:52:84:
-                    fa:05:69:4c:09:69:da:00:c2:af:8a:00:4c:1c:c2:
-                    1c:f5:8b:a9:f4:30:13:33:50:fd:6a:1e:8a:10:8d:
-                    cd:01:11:1b:cf:a3:30:80:4b:96:a3:b6:e1:ed:df:
-                    6e:69:5f:26:ed:75:9e:04:3f:49:cd:c9:6f:4a:05:
-                    6f:6d:45:09:09:a8:34:03:97:f2:77:ae:8e:96:7f:
-                    a8:52:7b:20:71:35:2d:11:17:52:cd:b1:b1:93:26:
-                    2f:9a:17:c1:48:3e:15:99:85:db:c6:bd:c3:35:0f:
-                    fd:d1:d9:2a:63:2b:96:59:35:93:c1:cf:5c:e1:72:
-                    3c:73
+                    00:cb:32:6c:8c:48:e8:44:58:36:18:70:36:42:3d:
+                    2d:29:47:3c:69:12:9e:7b:f7:45:62:ef:91:44:46:
+                    97:a0:ea:5f:da:fd:9f:98:d4:bf:43:02:e3:39:90:
+                    33:7b:13:13:d5:31:30:9c:07:fc:ca:1b:a9:e4:89:
+                    42:e5:d0:6e:f4:a2:e0:23:ee:9d:9a:cc:80:3b:78:
+                    bf:7e:27:a8:46:1b:28:9f:4a:64:53:7a:89:3e:ab:
+                    65:6f:af:0b:29:fa:4d:4f:04:f1:1e:10:2c:bf:2b:
+                    ea:fc:c5:fa:77:c9:1a:7a:78:29:f5:a2:cb:25:7c:
+                    02:bb:91:8d:76:4d:23:bc:9c:19:da:be:c5:20:04:
+                    ad:fe:bd:b9:d4:bb:29:2a:c3:e4:fc:4c:84:db:a3:
+                    55:9f:f0:70:7f:40:38:b5:c3:78:a5:db:06:36:b7:
+                    10:8e:ca:6c:1a:92:66:be:0e:1a:97:59:6b:18:f4:
+                    c2:b8:c9:31:7b:d1:b1:a1:00:78:7f:c0:09:f6:ef:
+                    b2:8f:94:87:5d:b1:a2:23:93:4d:ec:fa:95:09:a9:
+                    90:c4:02:f0:1e:d9:ab:a2:8b:7f:7f:54:95:e7:da:
+                    c3:c9:7d:a7:d7:04:89:59:db:88:9d:57:16:5d:b9:
+                    66:b0:d6:88:bb:e0:ee:43:e9:ab:02:78:fc:bd:e8:
+                    98:d9
                 Exponent: 65537 (0x10001)
         X509v3 extensions:
             X509v3 Subject Key Identifier: 
-                63:AD:89:C4:23:13:13:D1:EB:00:4D:01:0E:25:E3:5E:0C:9D:41:C9
+                C2:8F:09:9B:D5:F1:BA:C4:74:5E:97:50:BB:86:9D:A1:F1:FA:C4:36
             X509v3 Authority Key Identifier: 
-                keyid:C7:BD:C0:22:65:FF:A7:97:A7:CB:9D:7E:44:E5:13:77:39:7D:BE:BA
+                keyid:13:A0:B6:1F:F5:C7:64:C2:F9:FD:2E:08:F2:19:01:77:54:19:73:7F
 
             X509v3 Basic Constraints: 
                 CA:TRUE
     Signature Algorithm: sha256WithRSAEncryption
-         18:5c:f3:6a:e2:82:d1:ba:ee:de:00:29:4e:b3:f7:87:46:1b:
-         fa:7e:52:9d:87:d8:73:19:5b:38:7a:af:31:aa:4c:bd:fe:45:
-         95:25:03:48:15:d7:e5:38:4f:e3:39:93:99:10:b4:06:dc:5a:
-         87:af:22:b5:2f:82:da:ef:6c:b5:f3:9c:82:71:0f:f4:d0:65:
-         46:b7:23:b1:7d:51:8d:d7:7c:80:12:39:99:46:2b:d4:db:c7:
-         42:96:1a:b6:0f:b3:10:9e:ad:84:0e:87:38:e8:34:f8:04:3d:
-         a2:fc:3d:24:1e:09:d6:63:52:82:e4:35:ae:39:5e:f9:82:a7:
-         ac:23:87:0e:fb:2d:ae:08:da:2a:3f:51:ee:f6:85:3a:43:80:
-         b0:f1:96:0c:fa:10:80:18:7b:75:14:70:09:48:fc:0c:38:ee:
-         ab:e4:32:7d:80:77:a8:2a:63:15:c9:11:b7:a9:0c:77:7d:be:
-         d6:a3:48:37:1e:56:33:13:71:e1:12:90:ce:95:72:68:ae:d5:
-         01:82:00:67:1b:ae:9d:93:5b:5d:c1:3d:6e:30:e5:e1:35:ac:
-         59:3b:eb:ef:ee:83:0b:eb:e5:ea:9c:62:29:8c:73:a3:b5:45:
-         45:e8:53:ea:b8:48:16:7e:f8:c5:af:4b:89:b5:1b:94:a5:85:
-         d9:d5:f4:13
+         3e:70:76:69:37:e4:6e:e0:08:c6:8e:5b:2e:aa:26:fe:e9:ed:
+         ac:02:ce:2c:37:08:6a:8a:c3:0d:c0:ef:43:51:01:2e:e0:96:
+         76:23:1b:1f:75:98:df:7c:d1:b7:c1:67:aa:62:c1:bd:ef:84:
+         eb:d9:28:47:50:f2:1b:54:7f:ed:cb:52:f7:fc:c3:f8:62:22:
+         0c:b3:95:ed:bb:3f:74:91:bc:d2:eb:c0:81:7d:74:12:85:61:
+         a3:7e:fb:22:4a:25:99:0b:5d:ef:69:f2:5a:e6:d5:12:a3:95:
+         38:30:0c:c7:d9:da:28:30:10:b4:3d:3e:ad:20:85:31:e0:bf:
+         30:33:2e:0b:e3:07:3d:ed:22:dc:67:f8:93:64:89:ed:e7:08:
+         74:b5:0a:7a:01:3d:f9:44:62:71:cf:60:12:92:c3:95:9a:e5:
+         a5:f2:24:6a:22:64:d5:76:22:c9:03:1c:c5:d1:a5:85:4d:55:
+         f9:80:47:ca:12:20:df:05:fb:82:12:45:6f:e8:c0:20:a8:ae:
+         f7:17:c5:c3:b6:9c:51:bd:d8:84:e4:db:c7:03:44:d2:cb:75:
+         51:79:3f:86:33:3c:e4:34:1d:77:b2:60:24:5c:21:c5:c3:53:
+         36:08:2f:a7:14:0b:68:78:67:95:90:b9:06:0e:85:04:65:57:
+         b4:34:31:cf
 -----BEGIN CERTIFICATE-----
 MIIDmDCCAoCgAwIBAgIBATANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQGEwJHQjET
 MBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwFRGVyYnkxFTATBgNVBAoMDFBh
 aG8gUHJvamVjdDEQMA4GA1UECwwHVGVzdGluZzEQMA4GA1UEAwwHUm9vdCBDQTAe
-Fw0yMDA3MjgwOTEyMTBaFw0yNTA3MjcwOTEyMTBaMGAxCzAJBgNVBAYTAkdCMRMw
+Fw0yMTA3MDcxMTE0NDJaFw0yNjA3MDYxMTE0NDJaMGAxCzAJBgNVBAYTAkdCMRMw
 EQYDVQQIDApEZXJieXNoaXJlMRUwEwYDVQQKDAxQYWhvIFByb2plY3QxEDAOBgNV
 BAsMB1Rlc3RpbmcxEzARBgNVBAMMClNpZ25pbmcgQ0EwggEiMA0GCSqGSIb3DQEB
-AQUAA4IBDwAwggEKAoIBAQDCMxcnj+azs5bY/MwRjV7aPaWOOhTH5jiAJeYnxM9v
-PLgYSm5T7oHcxuJKKRjZ09hFPxksAbVhzrnxiZsLDK/yOAUuz+RXxZ9P/EKhL8wq
-WfoaSiQkVWDTJdWnD9hd85rc2BOMN66NJ7vSLDc0PhEWAkYDskgk3l7QZWPNDa9Y
-SBPck9BShPoFaUwJadoAwq+KAEwcwhz1i6n0MBMzUP1qHooQjc0BERvPozCAS5aj
-tuHt325pXybtdZ4EP0nNyW9KBW9tRQkJqDQDl/J3ro6Wf6hSeyBxNS0RF1LNsbGT
-Ji+aF8FIPhWZhdvGvcM1D/3R2SpjK5ZZNZPBz1zhcjxzAgMBAAGjUDBOMB0GA1Ud
-DgQWBBRjrYnEIxMT0esATQEOJeNeDJ1ByTAfBgNVHSMEGDAWgBTHvcAiZf+nl6fL
-nX5E5RN3OX2+ujAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAYXPNq
-4oLRuu7eAClOs/eHRhv6flKdh9hzGVs4eq8xqky9/kWVJQNIFdflOE/jOZOZELQG
-3FqHryK1L4La72y185yCcQ/00GVGtyOxfVGN13yAEjmZRivU28dClhq2D7MQnq2E
-Doc46DT4BD2i/D0kHgnWY1KC5DWuOV75gqesI4cO+y2uCNoqP1Hu9oU6Q4Cw8ZYM
-+hCAGHt1FHAJSPwMOO6r5DJ9gHeoKmMVyRG3qQx3fb7Wo0g3HlYzE3HhEpDOlXJo
-rtUBggBnG66dk1tdwT1uMOXhNaxZO+vv7oML6+XqnGIpjHOjtUVF6FPquEgWfvjF
-r0uJtRuUpYXZ1fQT
+AQUAA4IBDwAwggEKAoIBAQDLMmyMSOhEWDYYcDZCPS0pRzxpEp5790Vi75FERpeg
+6l/a/Z+Y1L9DAuM5kDN7ExPVMTCcB/zKG6nkiULl0G70ouAj7p2azIA7eL9+J6hG
+GyifSmRTeok+q2Vvrwsp+k1PBPEeECy/K+r8xfp3yRp6eCn1osslfAK7kY12TSO8
+nBnavsUgBK3+vbnUuykqw+T8TITbo1Wf8HB/QDi1w3il2wY2txCOymwakma+DhqX
+WWsY9MK4yTF70bGhAHh/wAn277KPlIddsaIjk03s+pUJqZDEAvAe2auii39/VJXn
+2sPJfafXBIlZ24idVxZduWaw1oi74O5D6asCePy96JjZAgMBAAGjUDBOMB0GA1Ud
+DgQWBBTCjwmb1fG6xHRel1C7hp2h8frENjAfBgNVHSMEGDAWgBQToLYf9cdkwvn9
+LgjyGQF3VBlzfzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA+cHZp
+N+Ru4AjGjlsuqib+6e2sAs4sNwhqisMNwO9DUQEu4JZ2IxsfdZjffNG3wWeqYsG9
+74Tr2ShHUPIbVH/ty1L3/MP4YiIMs5Xtuz90kbzS68CBfXQShWGjfvsiSiWZC13v
+afJa5tUSo5U4MAzH2dooMBC0PT6tIIUx4L8wMy4L4wc97SLcZ/iTZInt5wh0tQp6
+AT35RGJxz2ASksOVmuWl8iRqImTVdiLJAxzF0aWFTVX5gEfKEiDfBfuCEkVv6MAg
+qK73F8XDtpxRvdiE5NvHA0TSy3VReT+GMzzkNB13smAkXCHFw1M2CC+nFAtoeGeV
+kLkGDoUEZVe0NDHP
 -----END CERTIFICATE-----
 -----BEGIN CERTIFICATE-----
-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
+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
-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
+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
-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
+AKpCq45dCrroNa+y3zgdBglQOtw4og3MD/3Rn6ZftyL0dv1rSMkCFU8lCtZ4bIpz
+iNSJKau79owCudX3qQTPfiX2pmR5uuYjvMzRiZohZtz5uqXByy/CMS8dPRI3po6i
+kfNx9n7EQqOlxdwkY1kae2j5ybkAld2MNci93BH4P8qqaQckVRKpv6cKq33KsXK7
+jHgjAYMGrihTAwxgP1JX9NS8yxxjMUYvFqeEOLARoeWc6Nl7oDbGLs2fr0j2Yssm
+cz0AMu7LWcbhnfs2S8Troksztnq38yHu+YTs6hX4NhANBgon5CAdyzmmE/b2OwOX
+p8rQepUfG7wO5QaS0OrAEXsCAwEAAaNQME4wHQYDVR0OBBYEFBOgth/1x2TC+f0u
+CPIZAXdUGXN/MB8GA1UdIwQYMBaAFBOgth/1x2TC+f0uCPIZAXdUGXN/MAwGA1Ud
+EwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAHgE1oMwIcilQFN4xPYCf8jbsa5o
+zA5ljTbxv7fU3Zd+7KdlDFYroGjgHb7o3r0//b8+ZarxBqn1274u4KPs39Ow7h6m
+YJo7IM2Z2fC6IWZroqeidfFx5SwejAP1j7coYLblTIbNF+P08sJG5nSQ+Yx0gams
+6C1x0mETaaglDwllU1KXHTm8fUpEwpISc/VfKABYgScODMpdsDghyHANvnFjmvp4
+ktABnasliZYTmdl0t3szNm7zIk+bntiK4KunFea8GqgslWqGPwtNxxJFHzPjMCxK
+EHgubLgp1lNZzH13XSO6ZpiNRDJ6IVed3Zq+yn+24uKH+1Hqp6Bt20ZFB4E=
 -----END CERTIFICATE-----
diff --git a/test/ssl/client-expired.crt b/test/ssl/client-expired.crt
index c2b60f6..435c94c 100644
--- a/test/ssl/client-expired.crt
+++ b/test/ssl/client-expired.crt
@@ -12,24 +12,24 @@ Certificate:
             Public Key Algorithm: rsaEncryption
                 RSA Public-Key: (2048 bit)
                 Modulus:
-                    00:b5:85:9e:ea:67:a7:6e:35:bc:0a:60:5f:dd:2f:
-                    b8:5d:ab:1f:56:82:1a:55:82:1e:ba:7e:05:59:fc:
-                    97:11:7b:73:59:f2:61:84:3b:da:ff:09:ba:21:25:
-                    b9:38:4b:76:c1:ae:2d:a0:7e:20:23:e9:21:50:e7:
-                    b9:a3:81:35:d7:f1:39:3c:8f:d5:34:82:7a:e4:68:
-                    4b:f3:f1:23:3b:67:e9:3e:d4:97:01:86:8e:52:fa:
-                    a2:a7:41:ea:03:92:fc:f9:1b:bb:9e:4d:b2:32:78:
-                    7e:da:8c:95:ef:c9:53:97:30:34:9e:5c:d0:b3:2f:
-                    44:33:01:29:23:bd:b8:82:05:f5:12:f6:9e:d2:6a:
-                    6f:74:14:12:08:6e:c2:85:88:7f:a4:7c:f5:e7:76:
-                    e2:2b:e8:c1:44:bf:1d:c7:c6:b4:84:c4:4b:4e:56:
-                    63:dc:91:b0:91:68:d3:07:55:c0:e3:92:ad:e4:75:
-                    2b:aa:f4:67:7e:7f:91:40:22:4e:11:6d:5e:97:ba:
-                    9a:ea:11:a8:19:1f:74:72:2b:a9:8f:05:24:03:11:
-                    1f:9c:ac:33:49:37:43:60:c7:0b:87:77:01:64:ad:
-                    f5:b4:42:c5:ca:38:6f:fc:0d:b1:df:f7:94:e5:14:
-                    d9:b9:7f:a1:da:2a:51:ab:4d:da:32:5c:3a:5b:5c:
-                    0e:a1
+                    00:d0:58:ed:ad:44:b4:f8:30:16:27:d9:b4:2c:b4:
+                    24:67:9a:19:fe:32:04:0d:9a:7e:75:97:12:d6:2c:
+                    d4:97:33:fb:30:8c:ef:a2:b1:ef:e5:92:d6:56:05:
+                    75:c8:19:82:92:4e:4b:13:8e:25:90:b9:21:72:f4:
+                    a4:bf:7a:e2:0f:75:52:08:04:4c:e8:6a:35:7e:7d:
+                    78:d9:b8:f7:2b:3d:8e:4e:b5:f3:7a:9a:06:10:50:
+                    ca:95:63:2c:bd:3a:89:d0:8a:84:12:32:9b:00:a7:
+                    25:33:70:d2:18:0a:43:94:12:62:e7:77:db:b8:0f:
+                    dc:23:48:95:5c:77:c6:11:4f:0f:d6:6e:73:59:7c:
+                    ed:6a:fd:ba:24:f0:b2:59:c3:a2:16:65:ad:19:7f:
+                    92:87:8c:ea:b5:e5:0f:26:f8:b1:74:98:c3:fd:ed:
+                    4d:74:d0:58:ce:d9:9c:24:34:9b:75:79:25:d0:aa:
+                    6c:03:03:0c:3a:4a:4c:9a:36:50:ab:55:74:1e:8b:
+                    de:41:a7:14:b9:57:ee:8b:31:90:5c:00:af:31:9d:
+                    e0:55:07:8d:05:ed:c9:5f:e1:79:b7:96:be:d9:5b:
+                    cf:a7:5c:cd:48:fc:bd:a4:34:bf:e0:49:d5:25:60:
+                    7a:4c:32:37:97:e4:f8:64:24:a6:79:c1:62:8d:93:
+                    52:53
                 Exponent: 65537 (0x10001)
         X509v3 extensions:
             X509v3 Basic Constraints: 
@@ -37,26 +37,26 @@ Certificate:
             Netscape Comment: 
                 OpenSSL Generated Certificate
             X509v3 Subject Key Identifier: 
-                5E:12:4A:C4:94:EA:40:AC:15:A8:14:93:63:F9:61:C8:35:89:79:9E
+                61:62:90:E6:BB:8A:BB:06:6C:8A:66:9F:A5:C7:85:12:43:5C:94:6F
             X509v3 Authority Key Identifier: 
-                keyid:63:AD:89:C4:23:13:13:D1:EB:00:4D:01:0E:25:E3:5E:0C:9D:41:C9
+                keyid:C2:8F:09:9B:D5:F1:BA:C4:74:5E:97:50:BB:86:9D:A1:F1:FA:C4:36
 
     Signature Algorithm: sha256WithRSAEncryption
-         b1:91:cd:ed:2f:8a:32:1f:90:7d:8f:5f:64:84:89:f9:9d:5c:
-         97:d6:d7:cd:08:ba:2d:8c:56:ca:f8:41:93:7a:4e:3e:d2:81:
-         57:e5:d4:3f:0c:3a:c8:f9:b1:5b:4c:8d:fc:cd:3c:d4:96:0a:
-         63:22:32:de:0e:c6:9c:f4:37:97:43:4b:aa:c9:e9:9d:85:4a:
-         f4:92:8b:14:50:8e:17:a5:41:49:70:9b:ae:2d:6b:07:d5:33:
-         64:86:bc:95:b2:46:dc:d4:be:77:35:04:91:99:08:31:53:d8:
-         5c:5e:44:91:1e:90:3c:c3:6a:f9:fb:05:17:e9:16:3a:0d:ad:
-         5d:af:4e:6a:4f:f2:b6:6f:9a:1f:59:4d:11:c4:94:dd:b0:af:
-         3c:68:cb:cf:4d:3d:b0:88:97:2b:7b:cd:bf:20:c0:57:ab:f5:
-         db:63:b7:7e:23:3c:dd:60:be:fb:20:e7:b6:14:30:c3:2f:09:
-         a1:44:41:f1:bc:b9:75:4c:c3:5e:4c:5c:5c:a7:a9:67:58:a6:
-         ed:de:2a:be:d9:28:03:ad:17:c0:de:aa:05:fc:8e:b7:cc:c1:
-         bc:8d:12:06:62:83:6f:e7:9a:7c:a5:31:73:5f:75:31:ac:c7:
-         b8:1b:c4:82:ea:c2:09:d6:d8:7f:27:6f:08:b9:13:e6:3a:60:
-         e5:e3:a3:24
+         8d:7b:2a:16:2a:2e:50:db:5d:6e:20:ec:4e:5f:2e:d0:f4:9a:
+         a9:c8:b3:f0:73:02:9f:2f:32:a2:2a:a5:a7:83:1e:e3:36:6b:
+         99:d2:4c:6a:ea:09:0b:73:5e:7f:69:da:50:69:5b:dc:0f:4d:
+         59:ec:d2:c7:ca:0e:a8:55:c0:5a:f6:67:e8:a0:0b:4b:0a:9a:
+         a8:1f:b3:f0:e7:e6:10:4b:db:1b:5a:18:7a:ee:52:16:93:2e:
+         70:1c:4f:7d:c6:eb:4a:11:35:92:db:8c:f0:86:1b:f7:64:4f:
+         f5:1b:31:d6:da:89:97:c6:46:4b:c9:df:7f:80:c4:77:5e:c6:
+         a8:b7:47:12:48:b5:2b:f2:73:80:e4:dd:5b:cf:a1:20:3c:3b:
+         b3:37:34:d1:72:37:e1:a6:06:d4:22:cc:65:d3:af:0f:aa:ea:
+         ad:dd:e9:21:c5:1e:86:81:94:33:6c:ca:68:c2:48:ed:ea:0e:
+         c4:be:38:a5:4f:bb:0b:2b:7f:e7:63:e1:9f:e1:c8:6a:c4:4c:
+         7b:43:a2:56:c9:ff:56:88:2e:e3:4f:d6:d0:69:59:96:6e:26:
+         d9:3d:f3:62:4e:c3:a3:79:8f:f9:e4:82:11:52:f0:a2:c7:79:
+         b6:54:50:21:31:e6:4a:8c:2c:df:23:e9:2e:50:6e:9d:a8:61:
+         5b:e1:cb:51
 -----BEGIN CERTIFICATE-----
 MIID1zCCAr+gAwIBAgIBAzANBgkqhkiG9w0BAQsFADBgMQswCQYDVQQGEwJHQjET
 MBEGA1UECAwKRGVyYnlzaGlyZTEVMBMGA1UECgwMUGFobyBQcm9qZWN0MRAwDgYD
@@ -64,19 +64,19 @@ VQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMB4XDTEyMDgyMDAwMDAw
 MFoXDTEyMDgyMTAwMDAwMFowgYAxCzAJBgNVBAYTAkdCMRgwFgYDVQQIDA9Ob3R0
 aW5naGFtc2hpcmUxEzARBgNVBAcMCk5vdHRpbmdoYW0xDzANBgNVBAoMBlNlcnZl
 cjETMBEGA1UECwwKUHJvZHVjdGlvbjEcMBoGA1UEAwwTdGVzdCBjbGllbnQgZXhw
-aXJlZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALWFnupnp241vApg
-X90vuF2rH1aCGlWCHrp+BVn8lxF7c1nyYYQ72v8JuiEluThLdsGuLaB+ICPpIVDn
-uaOBNdfxOTyP1TSCeuRoS/PxIztn6T7UlwGGjlL6oqdB6gOS/Pkbu55NsjJ4ftqM
-le/JU5cwNJ5c0LMvRDMBKSO9uIIF9RL2ntJqb3QUEghuwoWIf6R89ed24ivowUS/
-HcfGtITES05WY9yRsJFo0wdVwOOSreR1K6r0Z35/kUAiThFtXpe6muoRqBkfdHIr
-qY8FJAMRH5ysM0k3Q2DHC4d3AWSt9bRCxco4b/wNsd/3lOUU2bl/odoqUatN2jJc
-OltcDqECAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNT
-TCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFF4SSsSU6kCsFagUk2P5
-Ycg1iXmeMB8GA1UdIwQYMBaAFGOticQjExPR6wBNAQ4l414MnUHJMA0GCSqGSIb3
-DQEBCwUAA4IBAQCxkc3tL4oyH5B9j19khIn5nVyX1tfNCLotjFbK+EGTek4+0oFX
-5dQ/DDrI+bFbTI38zTzUlgpjIjLeDsac9DeXQ0uqyemdhUr0kosUUI4XpUFJcJuu
-LWsH1TNkhryVskbc1L53NQSRmQgxU9hcXkSRHpA8w2r5+wUX6RY6Da1dr05qT/K2
-b5ofWU0RxJTdsK88aMvPTT2wiJcre82/IMBXq/XbY7d+IzzdYL77IOe2FDDDLwmh
-REHxvLl1TMNeTFxcp6lnWKbt3iq+2SgDrRfA3qoF/I63zMG8jRIGYoNv55p8pTFz
-X3UxrMe4G8SC6sIJ1th/J28IuRPmOmDl46Mk
+aXJlZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANBY7a1EtPgwFifZ
+tCy0JGeaGf4yBA2afnWXEtYs1Jcz+zCM76Kx7+WS1lYFdcgZgpJOSxOOJZC5IXL0
+pL964g91UggETOhqNX59eNm49ys9jk6183qaBhBQypVjLL06idCKhBIymwCnJTNw
+0hgKQ5QSYud327gP3CNIlVx3xhFPD9Zuc1l87Wr9uiTwslnDohZlrRl/koeM6rXl
+Dyb4sXSYw/3tTXTQWM7ZnCQ0m3V5JdCqbAMDDDpKTJo2UKtVdB6L3kGnFLlX7osx
+kFwArzGd4FUHjQXtyV/hebeWvtlbz6dczUj8vaQ0v+BJ1SVgekwyN5fk+GQkpnnB
+Yo2TUlMCAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNT
+TCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFGFikOa7irsGbIpmn6XH
+hRJDXJRvMB8GA1UdIwQYMBaAFMKPCZvV8brEdF6XULuGnaHx+sQ2MA0GCSqGSIb3
+DQEBCwUAA4IBAQCNeyoWKi5Q211uIOxOXy7Q9JqpyLPwcwKfLzKiKqWngx7jNmuZ
+0kxq6gkLc15/adpQaVvcD01Z7NLHyg6oVcBa9mfooAtLCpqoH7Pw5+YQS9sbWhh6
+7lIWky5wHE99xutKETWS24zwhhv3ZE/1GzHW2omXxkZLyd9/gMR3Xsaot0cSSLUr
+8nOA5N1bz6EgPDuzNzTRcjfhpgbUIsxl068Pquqt3ekhxR6GgZQzbMpowkjt6g7E
+vjilT7sLK3/nY+Gf4chqxEx7Q6JWyf9WiC7jT9bQaVmWbibZPfNiTsOjeY/55IIR
+UvCix3m2VFAhMeZKjCzfI+kuUG6dqGFb4ctR
 -----END CERTIFICATE-----
diff --git a/test/ssl/client-pw.crt b/test/ssl/client-pw.crt
new file mode 100644
index 0000000..daf7b86
--- /dev/null
+++ b/test/ssl/client-pw.crt
@@ -0,0 +1,82 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 4 (0x4)
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA
+        Validity
+            Not Before: Jul  7 11:14:42 2021 GMT
+            Not After : Jul  6 11:14:42 2026 GMT
+        Subject: C=GB, ST=Nottinghamshire, L=Nottingham, O=Server, OU=Production, CN=test client with password
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                RSA Public-Key: (2048 bit)
+                Modulus:
+                    00:ca:cb:93:49:45:28:95:5b:c4:51:b4:2b:d0:e7:
+                    e4:b7:3b:37:34:f6:5c:ec:f8:7d:3a:8b:b8:da:3c:
+                    94:38:85:5f:41:ea:2b:08:d7:3e:97:12:50:09:1f:
+                    37:4f:e4:25:a1:59:b6:98:63:22:8d:80:7e:b1:b4:
+                    24:03:2e:5e:5d:45:a4:4c:76:e8:ac:2c:5f:ca:9d:
+                    ed:6e:0a:7b:6f:2b:34:d1:4e:6a:e1:b6:72:66:42:
+                    ec:fd:b8:97:bf:40:4b:24:9c:47:6c:8c:4a:73:aa:
+                    e0:3a:db:ac:45:65:23:df:8f:4a:30:ed:d6:ad:5c:
+                    eb:a9:e9:83:da:39:d1:eb:98:31:74:98:bd:99:6b:
+                    85:0e:1d:f8:93:cf:e2:bd:59:77:fe:b2:a0:c4:e5:
+                    63:ae:92:10:13:47:14:55:22:a0:30:b6:f0:cb:17:
+                    b6:2d:f9:7d:f9:82:50:b2:64:88:dd:5a:3b:b6:81:
+                    67:8c:e3:de:89:76:63:82:af:b7:ba:83:5c:3b:bc:
+                    cf:1f:8e:fe:25:04:6f:f2:70:bf:2f:b0:6b:4f:77:
+                    d2:2d:e4:37:20:84:f3:94:c3:12:80:ae:bc:c3:2b:
+                    93:d2:fa:92:a3:1a:33:8d:d7:4a:eb:23:04:c0:38:
+                    51:73:fb:7a:9f:f5:3a:ca:7e:2e:c7:b6:22:3e:68:
+                    69:0f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                8E:E7:D7:66:D5:0C:10:B5:7A:4F:7F:83:C3:43:94:E9:BC:E2:88:D0
+            X509v3 Authority Key Identifier: 
+                keyid:C2:8F:09:9B:D5:F1:BA:C4:74:5E:97:50:BB:86:9D:A1:F1:FA:C4:36
+
+    Signature Algorithm: sha256WithRSAEncryption
+         17:54:5a:5e:9e:7d:fe:3f:6d:82:c5:e8:42:6b:61:91:13:5f:
+         07:d5:25:4b:3c:05:e6:4c:99:a5:ff:20:ff:d3:e8:a4:25:08:
+         8c:82:1b:2f:25:73:79:97:12:5e:e9:30:a1:b2:16:36:51:e2:
+         a5:41:bf:1c:c1:db:d2:9a:36:67:75:da:e7:36:9a:b4:65:17:
+         74:af:73:02:b0:09:b3:ac:29:e7:ca:cd:01:12:7f:ba:39:29:
+         90:d4:7c:3f:99:89:66:e7:eb:79:80:77:91:e4:3d:7e:87:69:
+         7b:da:b5:68:07:26:ab:30:20:49:2b:46:33:3f:f7:4b:4e:e7:
+         a0:13:19:53:7d:73:ff:4a:95:86:35:d2:cd:ff:3c:b1:14:b4:
+         d8:d4:ca:de:b7:8d:2e:e3:47:f8:5d:2e:e7:b1:5b:b9:23:d3:
+         54:11:89:8e:98:12:a8:10:2a:da:bb:d0:0c:07:c7:d7:21:7e:
+         f0:88:91:31:07:2a:a6:42:84:4a:61:9e:68:72:d4:7c:3f:59:
+         b2:02:e1:a6:11:9b:d2:90:73:39:13:07:e1:6b:57:2a:78:b4:
+         b4:f0:75:7c:6d:48:9d:33:cd:3f:d0:ff:43:a4:7e:3a:8d:fe:
+         98:10:df:ab:ee:c0:58:82:cb:23:7a:b7:f5:5c:29:29:af:d0:
+         40:fc:42:a3
+-----BEGIN CERTIFICATE-----
+MIID3TCCAsWgAwIBAgIBBDANBgkqhkiG9w0BAQsFADBgMQswCQYDVQQGEwJHQjET
+MBEGA1UECAwKRGVyYnlzaGlyZTEVMBMGA1UECgwMUGFobyBQcm9qZWN0MRAwDgYD
+VQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMB4XDTIxMDcwNzExMTQ0
+MloXDTI2MDcwNjExMTQ0MlowgYYxCzAJBgNVBAYTAkdCMRgwFgYDVQQIDA9Ob3R0
+aW5naGFtc2hpcmUxEzARBgNVBAcMCk5vdHRpbmdoYW0xDzANBgNVBAoMBlNlcnZl
+cjETMBEGA1UECwwKUHJvZHVjdGlvbjEiMCAGA1UEAwwZdGVzdCBjbGllbnQgd2l0
+aCBwYXNzd29yZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMrLk0lF
+KJVbxFG0K9Dn5Lc7NzT2XOz4fTqLuNo8lDiFX0HqKwjXPpcSUAkfN0/kJaFZtphj
+Io2AfrG0JAMuXl1FpEx26KwsX8qd7W4Ke28rNNFOauG2cmZC7P24l79ASyScR2yM
+SnOq4DrbrEVlI9+PSjDt1q1c66npg9o50euYMXSYvZlrhQ4d+JPP4r1Zd/6yoMTl
+Y66SEBNHFFUioDC28MsXti35ffmCULJkiN1aO7aBZ4zj3ol2Y4Kvt7qDXDu8zx+O
+/iUEb/Jwvy+wa0930i3kNyCE85TDEoCuvMMrk9L6kqMaM43XSusjBMA4UXP7ep/1
+Osp+Lse2Ij5oaQ8CAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYd
+T3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFI7n12bVDBC1
+ek9/g8NDlOm84ojQMB8GA1UdIwQYMBaAFMKPCZvV8brEdF6XULuGnaHx+sQ2MA0G
+CSqGSIb3DQEBCwUAA4IBAQAXVFpenn3+P22CxehCa2GRE18H1SVLPAXmTJml/yD/
+0+ikJQiMghsvJXN5lxJe6TChshY2UeKlQb8cwdvSmjZnddrnNpq0ZRd0r3MCsAmz
+rCnnys0BEn+6OSmQ1Hw/mYlm5+t5gHeR5D1+h2l72rVoByarMCBJK0YzP/dLTueg
+ExlTfXP/SpWGNdLN/zyxFLTY1Mret40u40f4XS7nsVu5I9NUEYmOmBKoECrau9AM
+B8fXIX7wiJExByqmQoRKYZ5octR8P1myAuGmEZvSkHM5Ewfha1cqeLS08HV8bUid
+M80/0P9DpH46jf6YEN+r7sBYgssjerf1XCkpr9BA/EKj
+-----END CERTIFICATE-----
diff --git a/test/ssl/client-pw.key b/test/ssl/client-pw.key
new file mode 100644
index 0000000..468b275
--- /dev/null
+++ b/test/ssl/client-pw.key
@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,84549D95979482A29416CC3DBA507BC0
+
+tAbMy3JP4/R53W9E8sB+fFFvGQOb+QKSW0vtjb8Z3GlIW+9wdGJ89GhXcspP7+HN
+eQZy6trDmPHJ++m4sVEwBGjngLdDaajRvQVlqCIXgLvjCwIIaE2gHo3yF37umunR
+0dg4NVjfEgNS3luPu7DzCv9pwIQl2YOsoPK7TuHOTGVGHCovHAr/IJ7fdaT5N9G8
+ypC7rHZhneLUs02J/L0FZUPlztOXRuiqnJisZr0pPs6ZoVEoNvR4D3VgU0nOFMcF
+UmcWA+vJsXaPzmC0HDErYqfr0Mwc/7mZURCGm/+A6Q79PAAAR7pPoMczaRHP4szS
+BxegO6XyKYs8a52q/XojKTcb0FESNjX0syW3+OjZusCYpuBmK4MofSdDmXVNxBLD
+iBqUDLGSfU2W5H/UkHWh7O0VW1DT0RvqqFV1p1WI0dvixM80wx12rPiJ79RYLG8D
+HMD7lR2iODDibCXePMg0XeCz+zf8OSfqzw6YeAMxmapZWBd8cJJ4eaUq6ziZO0hE
+kvj6tUZk7d/nTSissQR2Tx6xlSm0AuHWdKx1s5gKnVg6xLKNKIVyeHnXdXkgCwSq
+dICwmtP/1iYrslWCrhnB4MLA6R2vgpglwBfh7h7rW59K2untdIzr/td+h/xkynHQ
+wMKJ5xZ2oRQc9oZrV0PXHQKdukniLr3owBPiu+i+QbqpzGtvhyt1wUs+NjDROrim
+kritxoz6SXSIH6Wv9ae1crdhK1YTaMt1YOJT4tPjTdZyhMXqYszAH25L3ar2HaEv
+Cv2YU9VqPno45/ZSVSA8xZ+E74AoZsgDMOWKFJimJv+P9CGNbm8d9SGlHDAsyj0U
++cTeyH6AWWHuAdEtVNA36qDdWOJOwhH2vT2iLuqdDySX5EJRszoxFo2RdGF3lRuQ
+lVFo1v41tnvB89i9g8ZVqqkfs1IjybU2Aq+hpnqRVThRrbN75o2s2BC0K2sUvgu5
+gKUzXBl2B5CX6kWrUZ9llTSi2nH6zFAMtKvvuRQx+r+qrjJbxiPkRm9HFXlRRKYG
+NZbYyrB0ovuNgL5mwraNBL8Ytzx/nGvnaJsxWqhNiDENEziGcjTiA0/gh/mtru7K
+xTAQt7vVgrsynAK7c5Yhu3BBJspjgq2S9mKNpgXadcYcKQRcJnsYR9VCNWy4f7dD
+sTDy9NPttZM24ayC8OjUtyusk/DxXubuqRf4mF7jKsmoTqZR/yW5/NGPOpYRs0If
+9ysiBP8ctyti+snS8jSzb7PVCBJzKgEDthjLvXmV2AIeuiXTFvqTmKOlTYT8mkev
+ZaXl4tS+3GGJgrSmPweAJsGFo58oQA8skExrXBW0w2rRSQDqzEo/GAbmd65IDGXh
+YMOwsdvjiu9Ug4E7icxB2w5bKmhEsIh/Vj3Np/h5xcJ9W9O8zq0oYhjle3GLDGro
+yPA1g0wWLeuxwPhPk7cNHFdF2Yr2CXFVug3Q9WkGTABKbZd2zV/7kk6YMRFmieV0
+h4nQBSFqR6qkF9CiUFkKS2dod0zKLPJiBRqgiopEur5QdHg1PpdEQAj6fLQo0Zfw
+LoQyVi3ta9IMO1wZU8fy9w+bXoo8c76VD5jzfXw1ig0bK3iu1ozZc2UjnNlBmpYp
+-----END RSA PRIVATE KEY-----
diff --git a/test/ssl/client-revoked.crt b/test/ssl/client-revoked.crt
index bdd3cbb..9a1a461 100644
--- a/test/ssl/client-revoked.crt
+++ b/test/ssl/client-revoked.crt
@@ -1,35 +1,35 @@
 Certificate:
     Data:
         Version: 3 (0x2)
-        Serial Number: 4 (0x4)
+        Serial Number: 5 (0x5)
         Signature Algorithm: sha256WithRSAEncryption
         Issuer: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA
         Validity
-            Not Before: Jul 28 09:12:11 2020 GMT
-            Not After : Jul 27 09:12:11 2025 GMT
+            Not Before: Jul  7 11:14:43 2021 GMT
+            Not After : Jul  6 11:14:43 2026 GMT
         Subject: C=GB, ST=Nottinghamshire, L=Nottingham, O=Server, OU=Production, CN=test client revoked
         Subject Public Key Info:
             Public Key Algorithm: rsaEncryption
                 RSA Public-Key: (2048 bit)
                 Modulus:
-                    00:a4:22:2c:88:b3:4f:f2:a0:b0:18:7d:28:d8:b4:
-                    69:72:a0:ad:90:f9:c4:1b:c1:f9:b1:e0:e5:87:02:
-                    74:21:6d:ad:87:87:e2:6b:af:82:02:6f:e8:65:a9:
-                    04:f8:05:a5:90:59:de:c9:26:41:a3:7b:dd:d6:91:
-                    0f:6b:57:92:04:54:e6:a1:74:1c:af:38:b7:f9:e8:
-                    c8:fb:bb:ff:ac:23:58:77:17:3a:76:42:a6:0f:f1:
-                    dc:bc:76:7e:25:1b:8a:89:8a:5b:06:76:41:6a:29:
-                    5c:72:27:e4:1b:68:c7:ca:3d:57:31:5e:9a:9d:5c:
-                    88:c8:bc:d8:e6:bd:b5:be:91:88:e4:4e:8b:37:33:
-                    13:6d:00:37:b4:12:02:da:1c:31:61:0e:e8:b5:0d:
-                    f2:f4:e2:46:55:d1:21:bf:ea:92:c3:8c:3d:16:0c:
-                    a4:e5:dd:d6:e5:f6:39:b5:4d:69:65:c0:fe:bf:1c:
-                    33:23:b4:f8:94:40:28:47:e7:8f:59:06:31:72:4b:
-                    6b:55:f2:7d:aa:c6:96:ff:1c:08:28:ab:82:b7:99:
-                    22:1f:1c:08:54:d1:4c:f6:f2:79:06:77:da:58:63:
-                    48:47:7f:13:6c:7f:eb:d3:06:1d:25:72:4b:04:d1:
-                    2e:1d:76:a8:56:f5:59:bc:be:c7:78:6d:e2:1d:57:
-                    b2:f3
+                    00:a5:7a:5f:a2:55:1f:50:22:cd:92:0d:9a:69:fa:
+                    47:d4:d1:2f:6f:e5:3e:22:06:2f:f4:ed:a8:85:9b:
+                    2a:0d:e7:e2:81:f4:23:08:68:e2:75:5b:ba:58:f5:
+                    57:61:5a:5c:c4:e5:27:5e:9c:8e:82:77:72:25:c2:
+                    2e:1d:e0:61:dc:32:f0:3b:be:7d:26:e3:a0:bb:5d:
+                    75:7f:87:d8:a1:26:2f:7f:01:7b:1e:2f:25:cb:bd:
+                    15:6c:43:12:6a:a6:02:1d:fd:7b:34:e2:1e:6c:06:
+                    13:de:39:e8:ee:ae:ed:cd:cc:bd:1e:48:d5:e6:11:
+                    95:12:08:61:88:13:d6:88:40:cc:9d:18:1c:c6:30:
+                    5e:8f:e8:a4:2a:c8:62:78:19:f6:95:6a:f0:ce:27:
+                    e3:af:aa:fd:46:41:9d:83:32:f6:8e:a4:1f:32:00:
+                    c3:ca:5f:a5:3e:bc:74:6e:96:3e:50:cd:12:ca:81:
+                    5a:ab:cf:a1:f8:3a:2f:fe:91:73:79:14:b3:fb:e6:
+                    6b:c3:57:a9:8c:2d:f6:6c:53:4f:2e:e9:4c:25:67:
+                    88:ac:ce:bc:84:ac:b8:d8:f5:6a:a4:ae:24:10:ea:
+                    4e:2c:ef:90:f5:a6:68:c3:5c:a7:e0:40:99:06:6a:
+                    ec:b1:63:f5:7a:0b:a9:f1:81:26:95:12:9c:02:20:
+                    77:df
                 Exponent: 65537 (0x10001)
         X509v3 extensions:
             X509v3 Basic Constraints: 
@@ -37,46 +37,46 @@ Certificate:
             Netscape Comment: 
                 OpenSSL Generated Certificate
             X509v3 Subject Key Identifier: 
-                21:58:60:7A:B1:55:A8:CA:EF:2A:D2:0B:25:83:81:DB:E3:A8:7C:7B
+                C7:6A:63:58:7D:DB:19:38:77:1F:41:E8:67:38:78:9D:0B:BE:51:92
             X509v3 Authority Key Identifier: 
-                keyid:63:AD:89:C4:23:13:13:D1:EB:00:4D:01:0E:25:E3:5E:0C:9D:41:C9
+                keyid:C2:8F:09:9B:D5:F1:BA:C4:74:5E:97:50:BB:86:9D:A1:F1:FA:C4:36
 
     Signature Algorithm: sha256WithRSAEncryption
-         ac:60:10:4d:cc:10:f8:71:5f:a2:94:20:d8:9e:8c:d9:fc:10:
-         4c:d7:b5:b7:57:8b:08:a5:97:50:24:27:38:47:e9:37:49:35:
-         92:07:fc:e2:75:37:4d:3c:b4:24:2c:62:da:6f:40:8a:55:d6:
-         a3:bf:ef:a0:90:8d:4e:ee:51:c7:5a:30:cc:36:9f:8c:72:c7:
-         36:4c:c4:96:ce:bf:df:e9:58:5a:54:63:be:e5:9c:27:63:e6:
-         55:4e:2f:09:df:10:b2:7c:42:eb:f1:86:66:f2:07:ca:b3:e5:
-         ca:14:58:34:ac:ac:4e:99:e4:37:c7:b1:8c:56:23:9a:5c:a8:
-         fe:17:77:ec:ce:c8:77:25:35:b7:98:1c:90:25:bc:fc:5a:4b:
-         85:b0:96:55:ca:cb:83:bc:a1:2a:1a:f1:cb:73:e1:8d:4b:65:
-         ca:86:1f:43:a5:f1:4a:86:d2:50:81:c3:c1:e6:c6:ed:15:0f:
-         01:2c:58:63:44:4e:17:c0:59:d1:cf:c8:cb:37:9c:9e:dc:e1:
-         83:10:90:9e:35:9d:43:23:f2:b9:1d:91:00:33:b4:eb:6a:9d:
-         5a:1c:e0:b8:6b:f0:dc:05:ae:15:f3:42:7f:0c:9a:bd:06:25:
-         75:79:53:ea:a8:9b:f5:4c:96:52:1a:85:91:a5:68:08:a5:f4:
-         32:ca:9d:58
+         5c:b9:89:ad:18:b6:0b:93:f1:b0:0a:3e:aa:f3:d0:ad:d1:6f:
+         04:31:29:5a:b8:74:81:3c:4b:bb:98:8a:ca:c8:95:fa:8f:3e:
+         89:ba:f2:c3:83:a2:18:1c:7c:c6:56:4b:52:83:ef:fe:87:23:
+         7f:2d:6f:a2:36:22:46:04:ed:bf:ad:34:e6:9d:87:7e:92:72:
+         80:b2:5d:b1:b3:23:f6:f2:bd:74:c5:34:ef:8f:50:89:8b:64:
+         77:95:9d:ec:72:09:a6:c4:74:da:1d:2a:57:60:38:8f:6c:22:
+         ff:9e:40:73:98:ac:1f:bc:b6:e4:1b:1d:2d:73:a2:9a:ad:53:
+         95:d2:17:b3:c5:8a:6c:5a:5a:be:e2:80:e4:f5:d6:99:06:61:
+         ec:66:44:1a:ec:ac:86:36:ef:84:4b:c5:b3:a0:c5:d7:0d:be:
+         51:8c:95:46:03:e4:74:61:bf:7c:10:68:91:12:46:b8:38:94:
+         9f:a2:68:77:4d:92:57:43:ff:a1:c2:67:43:33:01:1d:fd:29:
+         13:8d:04:ed:7e:2d:4c:ed:8c:2f:f6:6f:44:33:3c:71:4d:f6:
+         51:04:c5:c0:cb:2c:ea:95:6e:22:32:03:37:0b:32:87:89:c0:
+         e5:bc:72:d2:8f:73:db:40:a9:4d:f2:15:bd:c4:0d:aa:ea:2e:
+         0c:ce:77:9d
 -----BEGIN CERTIFICATE-----
-MIID1zCCAr+gAwIBAgIBBDANBgkqhkiG9w0BAQsFADBgMQswCQYDVQQGEwJHQjET
+MIID1zCCAr+gAwIBAgIBBTANBgkqhkiG9w0BAQsFADBgMQswCQYDVQQGEwJHQjET
 MBEGA1UECAwKRGVyYnlzaGlyZTEVMBMGA1UECgwMUGFobyBQcm9qZWN0MRAwDgYD
-VQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMB4XDTIwMDcyODA5MTIx
-MVoXDTI1MDcyNzA5MTIxMVowgYAxCzAJBgNVBAYTAkdCMRgwFgYDVQQIDA9Ob3R0
+VQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMB4XDTIxMDcwNzExMTQ0
+M1oXDTI2MDcwNjExMTQ0M1owgYAxCzAJBgNVBAYTAkdCMRgwFgYDVQQIDA9Ob3R0
 aW5naGFtc2hpcmUxEzARBgNVBAcMCk5vdHRpbmdoYW0xDzANBgNVBAoMBlNlcnZl
 cjETMBEGA1UECwwKUHJvZHVjdGlvbjEcMBoGA1UEAwwTdGVzdCBjbGllbnQgcmV2
-b2tlZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKQiLIizT/KgsBh9
-KNi0aXKgrZD5xBvB+bHg5YcCdCFtrYeH4muvggJv6GWpBPgFpZBZ3skmQaN73daR
-D2tXkgRU5qF0HK84t/noyPu7/6wjWHcXOnZCpg/x3Lx2fiUbiomKWwZ2QWopXHIn
-5Btox8o9VzFemp1ciMi82Oa9tb6RiOROizczE20AN7QSAtocMWEO6LUN8vTiRlXR
-Ib/qksOMPRYMpOXd1uX2ObVNaWXA/r8cMyO0+JRAKEfnj1kGMXJLa1XyfarGlv8c
-CCirgreZIh8cCFTRTPbyeQZ32lhjSEd/E2x/69MGHSVySwTRLh12qFb1Wby+x3ht
-4h1XsvMCAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNT
-TCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFCFYYHqxVajK7yrSCyWD
-gdvjqHx7MB8GA1UdIwQYMBaAFGOticQjExPR6wBNAQ4l414MnUHJMA0GCSqGSIb3
-DQEBCwUAA4IBAQCsYBBNzBD4cV+ilCDYnozZ/BBM17W3V4sIpZdQJCc4R+k3STWS
-B/zidTdNPLQkLGLab0CKVdajv++gkI1O7lHHWjDMNp+Mcsc2TMSWzr/f6VhaVGO+
-5ZwnY+ZVTi8J3xCyfELr8YZm8gfKs+XKFFg0rKxOmeQ3x7GMViOaXKj+F3fszsh3
-JTW3mByQJbz8WkuFsJZVysuDvKEqGvHLc+GNS2XKhh9DpfFKhtJQgcPB5sbtFQ8B
-LFhjRE4XwFnRz8jLN5ye3OGDEJCeNZ1DI/K5HZEAM7Trap1aHOC4a/DcBa4V80J/
-DJq9BiV1eVPqqJv1TJZSGoWRpWgIpfQyyp1Y
+b2tlZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKV6X6JVH1AizZIN
+mmn6R9TRL2/lPiIGL/TtqIWbKg3n4oH0Iwho4nVbulj1V2FaXMTlJ16cjoJ3ciXC
+Lh3gYdwy8Du+fSbjoLtddX+H2KEmL38Bex4vJcu9FWxDEmqmAh39ezTiHmwGE945
+6O6u7c3MvR5I1eYRlRIIYYgT1ohAzJ0YHMYwXo/opCrIYngZ9pVq8M4n46+q/UZB
+nYMy9o6kHzIAw8pfpT68dG6WPlDNEsqBWqvPofg6L/6Rc3kUs/vma8NXqYwt9mxT
+Ty7pTCVniKzOvISsuNj1aqSuJBDqTizvkPWmaMNcp+BAmQZq7LFj9XoLqfGBJpUS
+nAIgd98CAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNT
+TCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFMdqY1h92xk4dx9B6Gc4
+eJ0LvlGSMB8GA1UdIwQYMBaAFMKPCZvV8brEdF6XULuGnaHx+sQ2MA0GCSqGSIb3
+DQEBCwUAA4IBAQBcuYmtGLYLk/GwCj6q89Ct0W8EMSlauHSBPEu7mIrKyJX6jz6J
+uvLDg6IYHHzGVktSg+/+hyN/LW+iNiJGBO2/rTTmnYd+knKAsl2xsyP28r10xTTv
+j1CJi2R3lZ3scgmmxHTaHSpXYDiPbCL/nkBzmKwfvLbkGx0tc6KarVOV0hezxYps
+Wlq+4oDk9daZBmHsZkQa7KyGNu+ES8WzoMXXDb5RjJVGA+R0Yb98EGiREka4OJSf
+omh3TZJXQ/+hwmdDMwEd/SkTjQTtfi1M7Ywv9m9EMzxxTfZRBMXAyyzqlW4iMgM3
+CzKHicDlvHLSj3PbQKlN8hW9xA2q6i4Mzned
 -----END CERTIFICATE-----
diff --git a/test/ssl/client-revoked.key b/test/ssl/client-revoked.key
index e270389..9939572 100644
--- a/test/ssl/client-revoked.key
+++ b/test/ssl/client-revoked.key
@@ -1,27 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
-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
+MIIEpAIBAAKCAQEApXpfolUfUCLNkg2aafpH1NEvb+U+IgYv9O2ohZsqDefigfQj
+CGjidVu6WPVXYVpcxOUnXpyOgndyJcIuHeBh3DLwO759JuOgu111f4fYoSYvfwF7
+Hi8ly70VbEMSaqYCHf17NOIebAYT3jno7q7tzcy9HkjV5hGVEghhiBPWiEDMnRgc
+xjBej+ikKshieBn2lWrwzifjr6r9RkGdgzL2jqQfMgDDyl+lPrx0bpY+UM0SyoFa
+q8+h+Dov/pFzeRSz++Zrw1epjC32bFNPLulMJWeIrM68hKy42PVqpK4kEOpOLO+Q
+9aZow1yn4ECZBmrssWP1egup8YEmlRKcAiB33wIDAQABAoIBACVlqZVLPX9jzieS
+0XHf8TnkaJ8WJNuVoGLvDuXa8j8gR61s2jn9UiiJqWyPTccfn9WToDkekopjqjVk
+U/3Ghvc3v9kQrMIMMXgGoBZJQijxM0y1rfhdWWJZAi1sXw4hJFtYvO5vp8Zr/TN8
+zOqcN/wJqDfe6BBNqu3fXQNe0F4MQeiLVYi7c1Q0ZupiALZDFPJ1u03xFiIzlMrN
+QLghfUoq4pFgqC08wR31XncvcWQ/iOggznxjy16Ezx1ubqGf7cMWZmXEWtdD0fsP
+8P3x/VS+MBzVtX9hhTaS3pVUAZKriCLF8kQiCUgtzlbUvKNrRb6gLan+OcD+J2sE
+0wopLZkCgYEA0BsLbbf0pc+PT+oCFUUueyADSl3SuH8j1YCpSU0nl0h6lE90cOen
+HSPRa4WHhhQm9lgCtTLXlHxJsZym0Lau7Nd90MIrjuvgRv0uOk07tRyThoGGuSCL
+2fBnD+a8NGjh+s/KTxmBDdWPkRpaZ5MVl8ZSGSM5zUZzplwPLx/J+wUCgYEAy4/W
+nd+rU4oh3Hm6cVp4YaktZ9cU/YB0yDd/sIm7bJ+kDyz0LSw7l/FSxMzQ+Dqybqht
+We+jn07BOh89r70vbcxx+4kMtHbg5Ii1u/R74AgGK6JSiKKpybLi7LA+3vcW/Hg6
+lemxE1U/PmmdvvSjzN/EXpeABkSgbctkJeoYRJMCgYBKLvnZ+NNrMBxEPoTTlD/H
+gFfr8JonTps1hpHSIYDVeu7HY7N8c/eseZIzo/v1ncVt113Pvfn/Ynbaq58Dk7uz
+jfW5rx3b6tWeOK579gAsxa0JK68c2y8/V2VF09iPTjwQLnZN0CejCNgOv7guZ84w
+tm+Zqmb2eADN8s8u20QjCQKBgQCoUHXHskKqX6Ph9nD3+zNgpQ8bNldvyMBHMMSP
+B0OG7HUt6yC3HUTlPLAQc74yEe6p2vAYFjK3rdnNojlST16hLhPtRQPRUB5iOLvz
+/pJSyq+3co9F1SII2bYSuSQzHiHOfecLP+CfuLQDejbpxsSNyVRIVoKQLDxurGdR
+hj+sqwKBgQC+x2dapmrh7qcCbPxbheWH32ds9GgI1Nr8eW2Wm0wIguLA4TrBN0UH
+HQZSdOjQoD/gTtHu79xbD1LPJk4kDtkvdzAlvbEds+3ArlaibNPcDaatkFAJt+2e
+2t8UDdIKzxOaKEF8YfTaeCpyS7CZoVw3frA9nkK7h38PgFeB7Vx2Rw==
 -----END RSA PRIVATE KEY-----
diff --git a/test/ssl/client.crt b/test/ssl/client.crt
index 9839438..c3fa4f5 100644
--- a/test/ssl/client.crt
+++ b/test/ssl/client.crt
@@ -5,31 +5,31 @@ Certificate:
         Signature Algorithm: sha256WithRSAEncryption
         Issuer: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA
         Validity
-            Not Before: Jul 28 09:12:10 2020 GMT
-            Not After : Jul 27 09:12:10 2025 GMT
+            Not Before: Jul  7 11:14:42 2021 GMT
+            Not After : Jul  6 11:14:42 2026 GMT
         Subject: C=GB, ST=Nottinghamshire, L=Nottingham, O=Server, OU=Production, CN=test client
         Subject Public Key Info:
             Public Key Algorithm: rsaEncryption
                 RSA Public-Key: (2048 bit)
                 Modulus:
-                    00:b5:85:9e:ea:67:a7:6e:35:bc:0a:60:5f:dd:2f:
-                    b8:5d:ab:1f:56:82:1a:55:82:1e:ba:7e:05:59:fc:
-                    97:11:7b:73:59:f2:61:84:3b:da:ff:09:ba:21:25:
-                    b9:38:4b:76:c1:ae:2d:a0:7e:20:23:e9:21:50:e7:
-                    b9:a3:81:35:d7:f1:39:3c:8f:d5:34:82:7a:e4:68:
-                    4b:f3:f1:23:3b:67:e9:3e:d4:97:01:86:8e:52:fa:
-                    a2:a7:41:ea:03:92:fc:f9:1b:bb:9e:4d:b2:32:78:
-                    7e:da:8c:95:ef:c9:53:97:30:34:9e:5c:d0:b3:2f:
-                    44:33:01:29:23:bd:b8:82:05:f5:12:f6:9e:d2:6a:
-                    6f:74:14:12:08:6e:c2:85:88:7f:a4:7c:f5:e7:76:
-                    e2:2b:e8:c1:44:bf:1d:c7:c6:b4:84:c4:4b:4e:56:
-                    63:dc:91:b0:91:68:d3:07:55:c0:e3:92:ad:e4:75:
-                    2b:aa:f4:67:7e:7f:91:40:22:4e:11:6d:5e:97:ba:
-                    9a:ea:11:a8:19:1f:74:72:2b:a9:8f:05:24:03:11:
-                    1f:9c:ac:33:49:37:43:60:c7:0b:87:77:01:64:ad:
-                    f5:b4:42:c5:ca:38:6f:fc:0d:b1:df:f7:94:e5:14:
-                    d9:b9:7f:a1:da:2a:51:ab:4d:da:32:5c:3a:5b:5c:
-                    0e:a1
+                    00:d0:58:ed:ad:44:b4:f8:30:16:27:d9:b4:2c:b4:
+                    24:67:9a:19:fe:32:04:0d:9a:7e:75:97:12:d6:2c:
+                    d4:97:33:fb:30:8c:ef:a2:b1:ef:e5:92:d6:56:05:
+                    75:c8:19:82:92:4e:4b:13:8e:25:90:b9:21:72:f4:
+                    a4:bf:7a:e2:0f:75:52:08:04:4c:e8:6a:35:7e:7d:
+                    78:d9:b8:f7:2b:3d:8e:4e:b5:f3:7a:9a:06:10:50:
+                    ca:95:63:2c:bd:3a:89:d0:8a:84:12:32:9b:00:a7:
+                    25:33:70:d2:18:0a:43:94:12:62:e7:77:db:b8:0f:
+                    dc:23:48:95:5c:77:c6:11:4f:0f:d6:6e:73:59:7c:
+                    ed:6a:fd:ba:24:f0:b2:59:c3:a2:16:65:ad:19:7f:
+                    92:87:8c:ea:b5:e5:0f:26:f8:b1:74:98:c3:fd:ed:
+                    4d:74:d0:58:ce:d9:9c:24:34:9b:75:79:25:d0:aa:
+                    6c:03:03:0c:3a:4a:4c:9a:36:50:ab:55:74:1e:8b:
+                    de:41:a7:14:b9:57:ee:8b:31:90:5c:00:af:31:9d:
+                    e0:55:07:8d:05:ed:c9:5f:e1:79:b7:96:be:d9:5b:
+                    cf:a7:5c:cd:48:fc:bd:a4:34:bf:e0:49:d5:25:60:
+                    7a:4c:32:37:97:e4:f8:64:24:a6:79:c1:62:8d:93:
+                    52:53
                 Exponent: 65537 (0x10001)
         X509v3 extensions:
             X509v3 Basic Constraints: 
@@ -37,46 +37,46 @@ Certificate:
             Netscape Comment: 
                 OpenSSL Generated Certificate
             X509v3 Subject Key Identifier: 
-                5E:12:4A:C4:94:EA:40:AC:15:A8:14:93:63:F9:61:C8:35:89:79:9E
+                61:62:90:E6:BB:8A:BB:06:6C:8A:66:9F:A5:C7:85:12:43:5C:94:6F
             X509v3 Authority Key Identifier: 
-                keyid:63:AD:89:C4:23:13:13:D1:EB:00:4D:01:0E:25:E3:5E:0C:9D:41:C9
+                keyid:C2:8F:09:9B:D5:F1:BA:C4:74:5E:97:50:BB:86:9D:A1:F1:FA:C4:36
 
     Signature Algorithm: sha256WithRSAEncryption
-         4e:f3:5d:99:00:94:57:9e:a7:19:b8:9e:ee:f2:f9:01:9e:b7:
-         02:e7:fe:a4:89:cf:a1:5f:04:4e:46:98:b8:aa:de:8d:f4:c2:
-         6b:71:25:08:f4:64:7e:99:5e:07:dd:43:43:cf:08:f7:e7:11:
-         f1:79:1d:7a:0b:d3:b2:f1:70:9d:da:dd:c6:f7:a0:e2:3b:64:
-         22:7b:74:b8:70:93:74:51:a2:c4:aa:3d:15:b1:b9:68:20:9e:
-         ec:98:1d:86:4d:e9:42:61:41:92:a7:08:12:27:f3:7e:3e:6a:
-         54:1e:76:19:f5:b5:11:4d:de:25:22:63:ef:78:17:61:3e:4c:
-         d2:ea:ff:e9:1c:52:a8:82:29:24:cd:50:75:72:32:c1:00:83:
-         bf:14:c3:c6:bd:b0:0f:4f:83:86:ea:67:7a:8d:82:c6:42:4a:
-         e5:73:4b:69:c2:96:9a:74:ea:5e:82:62:8c:a4:02:17:08:cf:
-         f6:07:76:84:b5:91:b1:ce:79:4a:be:e5:43:5d:31:55:5e:4e:
-         38:42:cd:70:9b:22:c4:3e:ee:24:fa:ae:07:ae:75:f5:f9:b5:
-         05:53:58:bd:12:50:1f:68:5e:ff:8d:30:2d:61:85:97:5a:48:
-         0c:61:ab:ad:4c:95:67:32:c4:8c:3f:dd:9f:39:ed:9a:be:73:
-         50:53:20:de
+         a4:ad:a8:cf:8b:f1:c0:e5:ed:e0:9f:eb:b8:46:17:fe:cf:49:
+         ed:e2:94:3b:3c:ea:4d:8b:e5:e3:5b:5f:f0:51:f0:53:88:22:
+         61:fc:f9:9d:c3:67:5f:9c:20:f3:2c:bb:65:c3:66:d9:15:b8:
+         60:82:31:95:d4:96:43:11:c1:56:da:4b:ad:bc:3f:b2:5f:3d:
+         40:8d:e4:22:26:9b:d5:5d:ff:02:55:c1:f9:ca:f3:67:46:be:
+         7d:d0:8c:68:40:a6:64:01:f0:ce:8e:2c:c2:6c:16:96:23:64:
+         e6:2f:95:b9:95:a2:85:8e:ec:61:56:6f:b9:3a:87:e9:cc:f1:
+         94:ca:51:d4:ce:50:01:91:1a:8c:ff:f9:cf:30:d4:aa:53:44:
+         67:44:84:4c:07:a7:ab:c3:34:3a:16:69:8c:37:7f:a0:fb:e1:
+         fa:ec:e6:9d:3c:fd:13:a9:6f:b2:d8:dc:46:81:ae:a6:63:4f:
+         80:47:a7:80:51:a7:d4:d6:c8:11:85:7d:5f:ab:ef:3a:93:62:
+         d7:fb:c2:a9:e4:b9:40:7e:d1:59:d0:d4:ff:75:bf:70:72:a2:
+         93:a3:47:41:d8:cf:d5:c6:8c:90:b8:d3:01:d8:53:a6:c1:3c:
+         a9:d9:e4:ef:15:e9:47:9c:9d:eb:5a:bb:11:df:da:f1:81:5d:
+         89:c9:4a:8f
 -----BEGIN CERTIFICATE-----
 MIIDzjCCAragAwIBAgIBAjANBgkqhkiG9w0BAQsFADBgMQswCQYDVQQGEwJHQjET
 MBEGA1UECAwKRGVyYnlzaGlyZTEVMBMGA1UECgwMUGFobyBQcm9qZWN0MRAwDgYD
-VQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMB4XDTIwMDcyODA5MTIx
-MFoXDTI1MDcyNzA5MTIxMFoweDELMAkGA1UEBhMCR0IxGDAWBgNVBAgMD05vdHRp
+VQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMB4XDTIxMDcwNzExMTQ0
+MloXDTI2MDcwNjExMTQ0MloweDELMAkGA1UEBhMCR0IxGDAWBgNVBAgMD05vdHRp
 bmdoYW1zaGlyZTETMBEGA1UEBwwKTm90dGluZ2hhbTEPMA0GA1UECgwGU2VydmVy
 MRMwEQYDVQQLDApQcm9kdWN0aW9uMRQwEgYDVQQDDAt0ZXN0IGNsaWVudDCCASIw
-DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALWFnupnp241vApgX90vuF2rH1aC
-GlWCHrp+BVn8lxF7c1nyYYQ72v8JuiEluThLdsGuLaB+ICPpIVDnuaOBNdfxOTyP
-1TSCeuRoS/PxIztn6T7UlwGGjlL6oqdB6gOS/Pkbu55NsjJ4ftqMle/JU5cwNJ5c
-0LMvRDMBKSO9uIIF9RL2ntJqb3QUEghuwoWIf6R89ed24ivowUS/HcfGtITES05W
-Y9yRsJFo0wdVwOOSreR1K6r0Z35/kUAiThFtXpe6muoRqBkfdHIrqY8FJAMRH5ys
-M0k3Q2DHC4d3AWSt9bRCxco4b/wNsd/3lOUU2bl/odoqUatN2jJcOltcDqECAwEA
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANBY7a1EtPgwFifZtCy0JGeaGf4y
+BA2afnWXEtYs1Jcz+zCM76Kx7+WS1lYFdcgZgpJOSxOOJZC5IXL0pL964g91UggE
+TOhqNX59eNm49ys9jk6183qaBhBQypVjLL06idCKhBIymwCnJTNw0hgKQ5QSYud3
+27gP3CNIlVx3xhFPD9Zuc1l87Wr9uiTwslnDohZlrRl/koeM6rXlDyb4sXSYw/3t
+TXTQWM7ZnCQ0m3V5JdCqbAMDDDpKTJo2UKtVdB6L3kGnFLlX7osxkFwArzGd4FUH
+jQXtyV/hebeWvtlbz6dczUj8vaQ0v+BJ1SVgekwyN5fk+GQkpnnBYo2TUlMCAwEA
 AaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0
-ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFF4SSsSU6kCsFagUk2P5Ycg1iXmeMB8G
-A1UdIwQYMBaAFGOticQjExPR6wBNAQ4l414MnUHJMA0GCSqGSIb3DQEBCwUAA4IB
-AQBO812ZAJRXnqcZuJ7u8vkBnrcC5/6kic+hXwRORpi4qt6N9MJrcSUI9GR+mV4H
-3UNDzwj35xHxeR16C9Oy8XCd2t3G96DiO2Qie3S4cJN0UaLEqj0VsbloIJ7smB2G
-TelCYUGSpwgSJ/N+PmpUHnYZ9bURTd4lImPveBdhPkzS6v/pHFKogikkzVB1cjLB
-AIO/FMPGvbAPT4OG6md6jYLGQkrlc0tpwpaadOpegmKMpAIXCM/2B3aEtZGxznlK
-vuVDXTFVXk44Qs1wmyLEPu4k+q4HrnX1+bUFU1i9ElAfaF7/jTAtYYWXWkgMYaut
-TJVnMsSMP92fOe2avnNQUyDe
+ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFGFikOa7irsGbIpmn6XHhRJDXJRvMB8G
+A1UdIwQYMBaAFMKPCZvV8brEdF6XULuGnaHx+sQ2MA0GCSqGSIb3DQEBCwUAA4IB
+AQCkrajPi/HA5e3gn+u4Rhf+z0nt4pQ7POpNi+XjW1/wUfBTiCJh/Pmdw2dfnCDz
+LLtlw2bZFbhggjGV1JZDEcFW2kutvD+yXz1AjeQiJpvVXf8CVcH5yvNnRr590Ixo
+QKZkAfDOjizCbBaWI2TmL5W5laKFjuxhVm+5OofpzPGUylHUzlABkRqM//nPMNSq
+U0RnRIRMB6erwzQ6FmmMN3+g++H67OadPP0TqW+y2NxGga6mY0+AR6eAUafU1sgR
+hX1fq+86k2LX+8Kp5LlAftFZ0NT/db9wcqKTo0dB2M/VxoyQuNMB2FOmwTyp2eTv
+FelHnJ3rWrsR39rxgV2JyUqP
 -----END CERTIFICATE-----
diff --git a/test/ssl/client.csr b/test/ssl/client.csr
deleted file mode 100644
index 4bf11ff..0000000
--- a/test/ssl/client.csr
+++ /dev/null
@@ -1,17 +0,0 @@
------BEGIN CERTIFICATE REQUEST-----
-MIICvTCCAaUCAQAweDELMAkGA1UEBhMCR0IxGDAWBgNVBAgMD05vdHRpbmdoYW1z
-aGlyZTETMBEGA1UEBwwKTm90dGluZ2hhbTEPMA0GA1UECgwGU2VydmVyMRMwEQYD
-VQQLDApQcm9kdWN0aW9uMRQwEgYDVQQDDAt0ZXN0IGNsaWVudDCCASIwDQYJKoZI
-hvcNAQEBBQADggEPADCCAQoCggEBALWFnupnp241vApgX90vuF2rH1aCGlWCHrp+
-BVn8lxF7c1nyYYQ72v8JuiEluThLdsGuLaB+ICPpIVDnuaOBNdfxOTyP1TSCeuRo
-S/PxIztn6T7UlwGGjlL6oqdB6gOS/Pkbu55NsjJ4ftqMle/JU5cwNJ5c0LMvRDMB
-KSO9uIIF9RL2ntJqb3QUEghuwoWIf6R89ed24ivowUS/HcfGtITES05WY9yRsJFo
-0wdVwOOSreR1K6r0Z35/kUAiThFtXpe6muoRqBkfdHIrqY8FJAMRH5ysM0k3Q2DH
-C4d3AWSt9bRCxco4b/wNsd/3lOUU2bl/odoqUatN2jJcOltcDqECAwEAAaAAMA0G
-CSqGSIb3DQEBCwUAA4IBAQANrZ/V/Kiy3KqrtGsBu4Hg2YcQ3Jj6FaUN2vbsQGeF
-+FIJQwfWCy+fzmW7rp7nlgSfJg5P1FPY/FEYax5sNRrqTEakj4/JeSGj+DTkl7X/
-hK28a0yH5VBPrlczjJ0eD0oiBvYu79MFVoIhvzTeJejXWrh5D9jAQliQfhb3H5VN
-ayJLzngXSYVfH/jHlxVcwjr9/0CfDJL+cSxaFr/VM4uYOGPa7hxX2yc/ApihpEpi
-PIqdzfE5Z/68ulXx+9Hk9M9eOfLTbDNQYjinpCeN1EPNI/96ZZNcVCJnoC6cEhRx
-kyKcUFnJWZF/J4DTQ7NlDpNs/mlolPqqscLTxGHHCODf
------END CERTIFICATE REQUEST-----
diff --git a/test/ssl/client.key b/test/ssl/client.key
index b9b4aee..688b7ce 100644
--- a/test/ssl/client.key
+++ b/test/ssl/client.key
@@ -1,27 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
-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
+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
 -----END RSA PRIVATE KEY-----
diff --git a/test/ssl/crl.pem b/test/ssl/crl.pem
index 1aa25d7..1721021 100644
--- a/test/ssl/crl.pem
+++ b/test/ssl/crl.pem
@@ -1,12 +1,12 @@
 -----BEGIN X509 CRL-----
 MIIBzzCBuAIBATANBgkqhkiG9w0BAQsFADBgMQswCQYDVQQGEwJHQjETMBEGA1UE
 CAwKRGVyYnlzaGlyZTEVMBMGA1UECgwMUGFobyBQcm9qZWN0MRAwDgYDVQQLDAdU
-ZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBFw0yMDA3MjgwOTEyMTFaFw0yMDA4
-MjcwOTEyMTFaMBQwEgIBBBcNMjAwNzI4MDkxMjExWqAOMAwwCgYDVR0UBAMCAQEw
-DQYJKoZIhvcNAQELBQADggEBABxVRuI9LoDx0+9PjxmFeW3A0tAtY3A6LnQHbGmz
-zW9Ov6DR/FgGtV6KvuEQb7N59/40H09CE/iKRNQjsDetVBYAEVXqfiCHrEVxnlox
-fKwyrpqDnQQdM8BjNAZaF9sY2XLCvqT+x0hiw7WPZxuYFJou/aPUEJmMbGqWwSzQ
-5n+gjpLxqSolyWp8hmRWPo+UZbTRUmaLOpV2Xli949tadBHPbFAKcNmzEqwFpeK9
-/do30Nm560ri5na61VEpj/I18hDF0I17agsDyHOh52XwOG0u+B09+GolnZnf6kiE
-XZ+G4Kq1gUWyityXQglEfvW2KPYaZSpaW1EsUhafSVJWOpQ=
+ZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBFw0yMTA3MDcxMTE0NDNaFw0yMTA4
+MDYxMTE0NDNaMBQwEgIBBRcNMjEwNzA3MTExNDQzWqAOMAwwCgYDVR0UBAMCAQEw
+DQYJKoZIhvcNAQELBQADggEBAL+k0y+sONbBjgtGs6WX3AuKPNv+uEVSAdRR1UMX
+3KwcwT9jy/5ypT7dv7UNz5V3IR8t41sCN6E3rVvOv+DFn6Br+eabg6GR/iZMhoUq
+hqknHZZTeMZ5VwzDIZvINHtRvli2bC5/sfU0S44d19lWW4rCVz78c1zf8MvsDc9U
+fwjtZofTZr/8p7t5KIdohYCwlHu+ANxi7qCJIuPJyZaPQ4wUSbRtu6idvkgYgmpC
+O74Fe+/eg6zQwd/B1MZEVdBx+66PAyELnyCW+R9PgILzET+YMDni/lT1AYwnCCJ2
+lgiTIQlmyT/xlFCfmmzbsCkTcrMPym4m3zTOzbaeiYUBAco=
 -----END X509 CRL-----
diff --git a/test/ssl/demoCA/crlnumber b/test/ssl/demoCA/crlnumber
deleted file mode 100644
index 6496923..0000000
--- a/test/ssl/demoCA/crlnumber
+++ /dev/null
@@ -1 +0,0 @@
-04
diff --git a/test/ssl/demoCA/index.txt b/test/ssl/demoCA/index.txt
deleted file mode 100644
index ced7051..0000000
--- a/test/ssl/demoCA/index.txt
+++ /dev/null
@@ -1 +0,0 @@
-R	391118144000Z	120703155846Z	CDAE0E564A2891A7	unknown	/C=GB/ST=United Kingdom/L=Derby/O=Mosquitto Test Suite/OU=Broker Test/CN=localhost-client-test
diff --git a/test/ssl/demoCA/index.txt.attr b/test/ssl/demoCA/index.txt.attr
deleted file mode 100644
index 3a7e39e..0000000
--- a/test/ssl/demoCA/index.txt.attr
+++ /dev/null
@@ -1 +0,0 @@
-unique_subject = no
diff --git a/test/ssl/gen.sh b/test/ssl/gen.sh
index 15ebeb6..270bb61 100755
--- a/test/ssl/gen.sh
+++ b/test/ssl/gen.sh
@@ -44,27 +44,32 @@ openssl ca -batch -config openssl.cnf -name CA_root -extensions v3_ca -out test-
 # Valid server key and certificate.
 openssl genrsa -out server.key 2048
 openssl req -new -key server.key -out server.csr -config openssl.cnf -subj "${SBASESUBJ}/CN=localhost/"
-openssl ca -batch -config openssl.cnf -name CA_signing -out server.crt -infiles server.csr 
+openssl ca -batch -config openssl.cnf -name CA_signing -out server.crt -infiles server.csr
 
 # Expired server certificate, based on the above server key.
 openssl req -new -days 1 -key server.key -out server-expired.csr -config openssl.cnf -subj "${SBASESUBJ}/CN=localhost/"
 echo -n > signingCA/index.txt
 echo 01 > signingCA/serial
-openssl ca -batch -config openssl.cnf -name CA_signing -days 1 -startdate 120820000000Z -enddate 120821000000Z -out server-expired.crt -infiles server-expired.csr 
+openssl ca -batch -config openssl.cnf -name CA_signing -days 1 -startdate 120820000000Z -enddate 120821000000Z -out server-expired.crt -infiles server-expired.csr
 
 # Valid client key and certificate.
 openssl genrsa -out client.key 2048
 openssl req -new -key client.key -out client.csr -config openssl.cnf -subj "${SBASESUBJ}/CN=test client/"
-openssl ca -batch -config openssl.cnf -name CA_signing -out client.crt -infiles client.csr 
+openssl ca -batch -config openssl.cnf -name CA_signing -out client.crt -infiles client.csr
 
 # Expired client certificate, based on the above client key.
 openssl req -new -days 1 -key client.key -out client-expired.csr -config openssl.cnf -subj "${SBASESUBJ}/CN=test client expired/"
-openssl ca -batch -config openssl.cnf -name CA_signing -days 1 -startdate 120820000000Z -enddate 120821000000Z -out client-expired.crt -infiles client-expired.csr 
+openssl ca -batch -config openssl.cnf -name CA_signing -days 1 -startdate 120820000000Z -enddate 120821000000Z -out client-expired.crt -infiles client-expired.csr
+
+# Valid client key and certificate, key is encrypted with a password.
+openssl genrsa -aes128 -passout pass:password -out client-pw.key 2048
+openssl req -new -key client-pw.key -passin pass:password -out client-pw.csr -config openssl.cnf -subj "${SBASESUBJ}/CN=test client with password/"
+openssl ca -batch -config openssl.cnf -name CA_signing -out client-pw.crt -infiles client-pw.csr
 
 # Revoked client certificate, based on a new client key.
 openssl genrsa -out client-revoked.key 2048
 openssl req -new -days 1 -key client-revoked.key -out client-revoked.csr -config openssl.cnf -subj "${SBASESUBJ}/CN=test client revoked/"
-openssl ca -batch -config openssl.cnf -name CA_signing -out client-revoked.crt -infiles client-revoked.csr 
+openssl ca -batch -config openssl.cnf -name CA_signing -out client-revoked.crt -infiles client-revoked.csr
 openssl ca -batch -config openssl.cnf -name CA_signing -revoke client-revoked.crt
 openssl ca -batch -config openssl.cnf -name CA_signing -gencrl -out crl.pem
 
@@ -74,4 +79,4 @@ cat test-signing-ca.crt test-root-ca.crt > all-ca.crt
 #cp test-root-ca.crt certs/test-root.ca.pem
 c_rehash certs
 
-rm -f client-expired.csr client-revoked.csr server-expired.csr server.csr test-alt-ca.csr
\ No newline at end of file
+rm -f client-expired.csr client-revoked.csr server-expired.csr server.csr test-alt-ca.csr
diff --git a/test/ssl/rootCA/crlnumber b/test/ssl/rootCA/crlnumber
deleted file mode 100644
index 8a0f05e..0000000
--- a/test/ssl/rootCA/crlnumber
+++ /dev/null
@@ -1 +0,0 @@
-01
diff --git a/test/ssl/rootCA/index.txt b/test/ssl/rootCA/index.txt
deleted file mode 100644
index 5efb34e..0000000
--- a/test/ssl/rootCA/index.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-V	250727091210Z		01	unknown	/C=GB/ST=Derbyshire/O=Paho Project/OU=Testing/CN=Signing CA
-V	250727091210Z		02	unknown	/C=GB/ST=Derbyshire/O=Paho Project/OU=Testing/CN=Alternative Signing CA
diff --git a/test/ssl/rootCA/index.txt.attr b/test/ssl/rootCA/index.txt.attr
deleted file mode 100644
index 8f7e63a..0000000
--- a/test/ssl/rootCA/index.txt.attr
+++ /dev/null
@@ -1 +0,0 @@
-unique_subject = yes
diff --git a/test/ssl/rootCA/index.txt.attr.old b/test/ssl/rootCA/index.txt.attr.old
deleted file mode 100644
index 8f7e63a..0000000
--- a/test/ssl/rootCA/index.txt.attr.old
+++ /dev/null
@@ -1 +0,0 @@
-unique_subject = yes
diff --git a/test/ssl/rootCA/index.txt.old b/test/ssl/rootCA/index.txt.old
deleted file mode 100644
index cc9ab66..0000000
--- a/test/ssl/rootCA/index.txt.old
+++ /dev/null
@@ -1 +0,0 @@
-V	250727091210Z		01	unknown	/C=GB/ST=Derbyshire/O=Paho Project/OU=Testing/CN=Signing CA
diff --git a/test/ssl/rootCA/newcerts/01.pem b/test/ssl/rootCA/newcerts/01.pem
deleted file mode 100644
index fa138ef..0000000
--- a/test/ssl/rootCA/newcerts/01.pem
+++ /dev/null
@@ -1,79 +0,0 @@
-Certificate:
-    Data:
-        Version: 3 (0x2)
-        Serial Number: 1 (0x1)
-        Signature Algorithm: sha256WithRSAEncryption
-        Issuer: C=GB, ST=Derbyshire, L=Derby, O=Paho Project, OU=Testing, CN=Root CA
-        Validity
-            Not Before: Jul 28 09:12:10 2020 GMT
-            Not After : Jul 27 09:12:10 2025 GMT
-        Subject: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA
-        Subject Public Key Info:
-            Public Key Algorithm: rsaEncryption
-                RSA Public-Key: (2048 bit)
-                Modulus:
-                    00:c2:33:17:27:8f:e6:b3:b3:96:d8:fc:cc:11:8d:
-                    5e:da:3d:a5:8e:3a:14:c7:e6:38:80:25:e6:27:c4:
-                    cf:6f:3c:b8:18:4a:6e:53:ee:81:dc:c6:e2:4a:29:
-                    18:d9:d3:d8:45:3f:19:2c:01:b5:61:ce:b9:f1:89:
-                    9b:0b:0c:af:f2:38:05:2e:cf:e4:57:c5:9f:4f:fc:
-                    42:a1:2f:cc:2a:59:fa:1a:4a:24:24:55:60:d3:25:
-                    d5:a7:0f:d8:5d:f3:9a:dc:d8:13:8c:37:ae:8d:27:
-                    bb:d2:2c:37:34:3e:11:16:02:46:03:b2:48:24:de:
-                    5e:d0:65:63:cd:0d:af:58:48:13:dc:93:d0:52:84:
-                    fa:05:69:4c:09:69:da:00:c2:af:8a:00:4c:1c:c2:
-                    1c:f5:8b:a9:f4:30:13:33:50:fd:6a:1e:8a:10:8d:
-                    cd:01:11:1b:cf:a3:30:80:4b:96:a3:b6:e1:ed:df:
-                    6e:69:5f:26:ed:75:9e:04:3f:49:cd:c9:6f:4a:05:
-                    6f:6d:45:09:09:a8:34:03:97:f2:77:ae:8e:96:7f:
-                    a8:52:7b:20:71:35:2d:11:17:52:cd:b1:b1:93:26:
-                    2f:9a:17:c1:48:3e:15:99:85:db:c6:bd:c3:35:0f:
-                    fd:d1:d9:2a:63:2b:96:59:35:93:c1:cf:5c:e1:72:
-                    3c:73
-                Exponent: 65537 (0x10001)
-        X509v3 extensions:
-            X509v3 Subject Key Identifier: 
-                63:AD:89:C4:23:13:13:D1:EB:00:4D:01:0E:25:E3:5E:0C:9D:41:C9
-            X509v3 Authority Key Identifier: 
-                keyid:C7:BD:C0:22:65:FF:A7:97:A7:CB:9D:7E:44:E5:13:77:39:7D:BE:BA
-
-            X509v3 Basic Constraints: 
-                CA:TRUE
-    Signature Algorithm: sha256WithRSAEncryption
-         18:5c:f3:6a:e2:82:d1:ba:ee:de:00:29:4e:b3:f7:87:46:1b:
-         fa:7e:52:9d:87:d8:73:19:5b:38:7a:af:31:aa:4c:bd:fe:45:
-         95:25:03:48:15:d7:e5:38:4f:e3:39:93:99:10:b4:06:dc:5a:
-         87:af:22:b5:2f:82:da:ef:6c:b5:f3:9c:82:71:0f:f4:d0:65:
-         46:b7:23:b1:7d:51:8d:d7:7c:80:12:39:99:46:2b:d4:db:c7:
-         42:96:1a:b6:0f:b3:10:9e:ad:84:0e:87:38:e8:34:f8:04:3d:
-         a2:fc:3d:24:1e:09:d6:63:52:82:e4:35:ae:39:5e:f9:82:a7:
-         ac:23:87:0e:fb:2d:ae:08:da:2a:3f:51:ee:f6:85:3a:43:80:
-         b0:f1:96:0c:fa:10:80:18:7b:75:14:70:09:48:fc:0c:38:ee:
-         ab:e4:32:7d:80:77:a8:2a:63:15:c9:11:b7:a9:0c:77:7d:be:
-         d6:a3:48:37:1e:56:33:13:71:e1:12:90:ce:95:72:68:ae:d5:
-         01:82:00:67:1b:ae:9d:93:5b:5d:c1:3d:6e:30:e5:e1:35:ac:
-         59:3b:eb:ef:ee:83:0b:eb:e5:ea:9c:62:29:8c:73:a3:b5:45:
-         45:e8:53:ea:b8:48:16:7e:f8:c5:af:4b:89:b5:1b:94:a5:85:
-         d9:d5:f4:13
------BEGIN CERTIFICATE-----
-MIIDmDCCAoCgAwIBAgIBATANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQGEwJHQjET
-MBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwFRGVyYnkxFTATBgNVBAoMDFBh
-aG8gUHJvamVjdDEQMA4GA1UECwwHVGVzdGluZzEQMA4GA1UEAwwHUm9vdCBDQTAe
-Fw0yMDA3MjgwOTEyMTBaFw0yNTA3MjcwOTEyMTBaMGAxCzAJBgNVBAYTAkdCMRMw
-EQYDVQQIDApEZXJieXNoaXJlMRUwEwYDVQQKDAxQYWhvIFByb2plY3QxEDAOBgNV
-BAsMB1Rlc3RpbmcxEzARBgNVBAMMClNpZ25pbmcgQ0EwggEiMA0GCSqGSIb3DQEB
-AQUAA4IBDwAwggEKAoIBAQDCMxcnj+azs5bY/MwRjV7aPaWOOhTH5jiAJeYnxM9v
-PLgYSm5T7oHcxuJKKRjZ09hFPxksAbVhzrnxiZsLDK/yOAUuz+RXxZ9P/EKhL8wq
-WfoaSiQkVWDTJdWnD9hd85rc2BOMN66NJ7vSLDc0PhEWAkYDskgk3l7QZWPNDa9Y
-SBPck9BShPoFaUwJadoAwq+KAEwcwhz1i6n0MBMzUP1qHooQjc0BERvPozCAS5aj
-tuHt325pXybtdZ4EP0nNyW9KBW9tRQkJqDQDl/J3ro6Wf6hSeyBxNS0RF1LNsbGT
-Ji+aF8FIPhWZhdvGvcM1D/3R2SpjK5ZZNZPBz1zhcjxzAgMBAAGjUDBOMB0GA1Ud
-DgQWBBRjrYnEIxMT0esATQEOJeNeDJ1ByTAfBgNVHSMEGDAWgBTHvcAiZf+nl6fL
-nX5E5RN3OX2+ujAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAYXPNq
-4oLRuu7eAClOs/eHRhv6flKdh9hzGVs4eq8xqky9/kWVJQNIFdflOE/jOZOZELQG
-3FqHryK1L4La72y185yCcQ/00GVGtyOxfVGN13yAEjmZRivU28dClhq2D7MQnq2E
-Doc46DT4BD2i/D0kHgnWY1KC5DWuOV75gqesI4cO+y2uCNoqP1Hu9oU6Q4Cw8ZYM
-+hCAGHt1FHAJSPwMOO6r5DJ9gHeoKmMVyRG3qQx3fb7Wo0g3HlYzE3HhEpDOlXJo
-rtUBggBnG66dk1tdwT1uMOXhNaxZO+vv7oML6+XqnGIpjHOjtUVF6FPquEgWfvjF
-r0uJtRuUpYXZ1fQT
------END CERTIFICATE-----
diff --git a/test/ssl/rootCA/newcerts/02.pem b/test/ssl/rootCA/newcerts/02.pem
deleted file mode 100644
index c191a58..0000000
--- a/test/ssl/rootCA/newcerts/02.pem
+++ /dev/null
@@ -1,79 +0,0 @@
-Certificate:
-    Data:
-        Version: 3 (0x2)
-        Serial Number: 2 (0x2)
-        Signature Algorithm: sha256WithRSAEncryption
-        Issuer: C=GB, ST=Derbyshire, L=Derby, O=Paho Project, OU=Testing, CN=Root CA
-        Validity
-            Not Before: Jul 28 09:12:10 2020 GMT
-            Not After : Jul 27 09:12:10 2025 GMT
-        Subject: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Alternative Signing CA
-        Subject Public Key Info:
-            Public Key Algorithm: rsaEncryption
-                RSA Public-Key: (2048 bit)
-                Modulus:
-                    00:bd:c2:9f:2c:d2:a7:25:79:f0:3f:29:65:ac:d3:
-                    8c:ea:72:d2:73:2d:1b:5b:e3:0c:a1:5a:40:f4:a9:
-                    68:bc:a4:50:77:c9:08:75:12:a3:21:3d:a9:d6:dc:
-                    dc:08:b4:47:32:7e:ad:f4:87:de:48:fd:83:d1:b7:
-                    e4:ce:3b:8b:87:99:3c:fa:6b:0e:1c:70:71:6d:d9:
-                    b4:75:7c:6e:2a:03:cc:0e:bb:c7:8c:31:53:67:11:
-                    2f:fd:97:c9:67:05:48:23:81:60:f5:94:94:af:61:
-                    1c:a8:c1:4d:fe:2b:2e:f8:e6:bd:30:c2:52:ec:56:
-                    7a:b7:64:d0:a2:bf:09:e4:d2:a3:c3:f1:f9:e7:12:
-                    96:45:06:20:a2:fd:49:43:87:a7:0f:c1:c6:58:46:
-                    9f:1d:9f:be:86:ce:49:de:c7:35:4d:fd:08:9a:61:
-                    3f:ed:1f:cd:5c:55:77:4f:cc:0e:52:51:2a:7e:04:
-                    4c:10:e1:79:88:9e:f6:8b:6b:bc:20:d2:6b:e9:c4:
-                    c6:94:bc:d8:06:57:a1:b6:b2:30:cd:0d:d3:27:b3:
-                    9e:1b:ac:40:81:6a:f0:a3:f6:62:22:14:40:61:9d:
-                    26:82:b1:aa:fa:0a:0a:45:54:7d:5d:02:59:70:e6:
-                    d2:f9:fc:45:58:83:10:2a:db:10:e3:7d:10:73:1b:
-                    d9:49
-                Exponent: 65537 (0x10001)
-        X509v3 extensions:
-            X509v3 Subject Key Identifier: 
-                3A:BA:37:57:E0:82:EB:BA:77:1B:50:07:A6:C9:22:EA:92:76:F9:1C
-            X509v3 Authority Key Identifier: 
-                keyid:C7:BD:C0:22:65:FF:A7:97:A7:CB:9D:7E:44:E5:13:77:39:7D:BE:BA
-
-            X509v3 Basic Constraints: 
-                CA:TRUE
-    Signature Algorithm: sha256WithRSAEncryption
-         05:07:f6:5d:7d:2d:5e:35:07:82:1e:cd:06:e6:b6:4c:1b:03:
-         b4:28:3e:16:22:0d:26:5f:f9:69:19:72:24:5a:fb:c6:cf:70:
-         c8:a6:a6:19:8d:10:56:c4:d5:76:bb:38:93:e0:ff:d0:a0:81:
-         47:36:e7:b6:f5:bf:99:36:28:d1:db:59:c0:e9:84:95:0f:db:
-         c0:84:86:9a:ef:78:ac:dd:83:6d:51:e8:ae:28:b4:f8:78:bb:
-         91:87:f5:35:21:fd:12:ff:41:33:7f:22:1e:16:f3:43:c9:97:
-         e2:16:35:db:e8:c8:ee:5a:3e:d4:43:c1:90:52:12:7c:f7:7e:
-         18:ee:60:be:61:41:3a:aa:2d:99:f3:44:86:3c:fd:03:e1:a2:
-         d8:e1:1c:49:b0:39:3e:8c:f6:38:00:4a:84:a5:54:98:ef:c0:
-         6d:2d:48:4a:13:fb:80:5f:58:a1:95:86:3a:a1:63:56:fe:23:
-         7f:db:c9:19:a5:22:cb:34:a9:10:cb:b4:95:9f:c9:c6:16:e0:
-         e3:50:7a:29:60:b0:fe:c7:f5:c8:f9:c6:3b:82:a6:a8:d9:0c:
-         5b:aa:58:f2:31:c3:98:20:87:d8:a1:63:ad:52:ae:b2:85:57:
-         10:d5:75:9e:73:35:47:c5:22:26:1f:53:38:52:4d:fd:2e:16:
-         00:15:d4:da
------BEGIN CERTIFICATE-----
-MIIDpDCCAoygAwIBAgIBAjANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQGEwJHQjET
-MBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwFRGVyYnkxFTATBgNVBAoMDFBh
-aG8gUHJvamVjdDEQMA4GA1UECwwHVGVzdGluZzEQMA4GA1UEAwwHUm9vdCBDQTAe
-Fw0yMDA3MjgwOTEyMTBaFw0yNTA3MjcwOTEyMTBaMGwxCzAJBgNVBAYTAkdCMRMw
-EQYDVQQIDApEZXJieXNoaXJlMRUwEwYDVQQKDAxQYWhvIFByb2plY3QxEDAOBgNV
-BAsMB1Rlc3RpbmcxHzAdBgNVBAMMFkFsdGVybmF0aXZlIFNpZ25pbmcgQ0EwggEi
-MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9wp8s0qclefA/KWWs04zqctJz
-LRtb4wyhWkD0qWi8pFB3yQh1EqMhPanW3NwItEcyfq30h95I/YPRt+TOO4uHmTz6
-aw4ccHFt2bR1fG4qA8wOu8eMMVNnES/9l8lnBUgjgWD1lJSvYRyowU3+Ky745r0w
-wlLsVnq3ZNCivwnk0qPD8fnnEpZFBiCi/UlDh6cPwcZYRp8dn76GzknexzVN/Qia
-YT/tH81cVXdPzA5SUSp+BEwQ4XmInvaLa7wg0mvpxMaUvNgGV6G2sjDNDdMns54b
-rECBavCj9mIiFEBhnSaCsar6CgpFVH1dAllw5tL5/EVYgxAq2xDjfRBzG9lJAgMB
-AAGjUDBOMB0GA1UdDgQWBBQ6ujdX4ILruncbUAemySLqknb5HDAfBgNVHSMEGDAW
-gBTHvcAiZf+nl6fLnX5E5RN3OX2+ujAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB
-CwUAA4IBAQAFB/ZdfS1eNQeCHs0G5rZMGwO0KD4WIg0mX/lpGXIkWvvGz3DIpqYZ
-jRBWxNV2uziT4P/QoIFHNue29b+ZNijR21nA6YSVD9vAhIaa73is3YNtUeiuKLT4
-eLuRh/U1If0S/0EzfyIeFvNDyZfiFjXb6MjuWj7UQ8GQUhJ8934Y7mC+YUE6qi2Z
-80SGPP0D4aLY4RxJsDk+jPY4AEqEpVSY78BtLUhKE/uAX1ihlYY6oWNW/iN/28kZ
-pSLLNKkQy7SVn8nGFuDjUHopYLD+x/XI+cY7gqao2QxbqljyMcOYIIfYoWOtUq6y
-hVcQ1XWeczVHxSImH1M4Uk39LhYAFdTa
------END CERTIFICATE-----
diff --git a/test/ssl/rootCA/serial b/test/ssl/rootCA/serial
deleted file mode 100644
index 75016ea..0000000
--- a/test/ssl/rootCA/serial
+++ /dev/null
@@ -1 +0,0 @@
-03
diff --git a/test/ssl/rootCA/serial.old b/test/ssl/rootCA/serial.old
deleted file mode 100644
index 9e22bcb..0000000
--- a/test/ssl/rootCA/serial.old
+++ /dev/null
@@ -1 +0,0 @@
-02
diff --git a/test/ssl/server-expired.crt b/test/ssl/server-expired.crt
index 0ec40b9..0703ac6 100644
--- a/test/ssl/server-expired.crt
+++ b/test/ssl/server-expired.crt
@@ -12,24 +12,24 @@ Certificate:
             Public Key Algorithm: rsaEncryption
                 RSA Public-Key: (2048 bit)
                 Modulus:
-                    00:f8:5a:e9:fb:cc:aa:7f:8a:71:10:31:34:09:93:
-                    9a:bb:d9:da:2d:02:46:3e:1a:ed:ee:5a:3b:e8:1d:
-                    ed:3d:55:f8:bc:c5:0b:f4:77:3d:ae:4e:4a:83:a0:
-                    23:90:15:ef:99:5e:8e:fa:08:ce:04:82:71:75:02:
-                    54:51:72:ab:02:ea:06:07:7a:60:8d:7b:9c:54:53:
-                    24:b6:26:89:2b:c6:fa:fc:89:da:20:95:4c:92:e8:
-                    99:45:ea:d5:ba:42:62:11:05:88:94:79:0f:fe:68:
-                    12:b3:b0:5c:ea:39:0d:6f:cf:e2:12:16:cf:e0:5d:
-                    37:69:fd:8c:ab:51:ac:a8:97:bb:be:5c:29:17:eb:
-                    ee:4d:43:2e:24:cb:af:84:e4:96:36:33:3e:9a:45:
-                    54:25:77:ef:ed:76:4b:5e:6a:1b:35:83:09:54:f1:
-                    d1:b0:59:32:c4:64:bb:50:8f:43:e2:dd:15:c6:c4:
-                    b8:f2:12:9b:fb:86:48:9c:21:d4:bd:da:26:71:8e:
-                    ed:3f:9a:27:15:04:c2:2c:2c:4a:bf:21:fe:6d:35:
-                    40:cb:78:41:e2:cd:d4:39:bf:9a:1a:43:c1:d2:9e:
-                    a9:44:24:e4:dd:ef:9d:5b:6c:d5:44:5c:32:2f:4f:
-                    41:9f:0f:27:3d:55:91:96:ca:22:1b:d6:18:f9:63:
-                    a1:61
+                    00:a9:4c:88:db:56:36:f8:fc:e1:eb:6b:ad:9c:0f:
+                    78:3e:7d:d7:34:c6:83:94:6d:83:07:d5:3e:cb:fb:
+                    95:61:e5:73:78:43:db:51:d9:a0:4e:ec:8e:43:21:
+                    91:1f:56:95:08:47:7c:83:38:90:bb:53:91:ed:fd:
+                    b4:bd:08:27:dd:d6:d9:5b:fd:bb:84:1e:2e:62:d9:
+                    3c:1d:4d:c9:6b:17:45:d7:9e:b4:a5:9c:22:cd:14:
+                    41:32:c3:41:ad:8d:f5:2f:a3:d5:59:1f:a1:2b:67:
+                    d3:01:83:64:93:80:6b:bf:5a:b8:51:86:20:a0:e4:
+                    3f:18:0c:67:19:8d:e3:58:6d:85:83:8f:8b:37:b2:
+                    7d:21:3f:65:cf:19:53:2e:56:df:4d:89:50:7e:8c:
+                    6a:8e:dd:21:15:15:31:9b:c2:5c:98:68:1e:31:ff:
+                    c6:6c:1f:a8:42:b8:da:62:dc:ae:62:4c:40:f0:06:
+                    c6:e6:f4:a9:98:3d:ed:fb:c0:2a:63:da:60:69:83:
+                    11:0e:ce:ba:93:d7:4b:27:8f:86:91:ef:e4:65:5f:
+                    20:be:04:f2:4d:d6:d1:74:c5:ab:e9:18:df:16:f9:
+                    9a:8a:ff:2f:23:c5:46:3e:04:16:4e:fa:c1:0a:f4:
+                    dc:8e:1a:da:5f:a1:ad:50:7a:5d:60:00:3e:09:b8:
+                    8e:6d
                 Exponent: 65537 (0x10001)
         X509v3 extensions:
             X509v3 Basic Constraints: 
@@ -37,26 +37,26 @@ Certificate:
             Netscape Comment: 
                 OpenSSL Generated Certificate
             X509v3 Subject Key Identifier: 
-                8F:E3:5C:A3:F0:4A:C3:1C:62:92:0A:83:37:BD:E8:B9:B6:91:22:E4
+                C3:47:33:CF:07:18:14:7C:9A:E4:AB:11:62:89:88:54:3D:5D:7D:E8
             X509v3 Authority Key Identifier: 
-                keyid:63:AD:89:C4:23:13:13:D1:EB:00:4D:01:0E:25:E3:5E:0C:9D:41:C9
+                keyid:C2:8F:09:9B:D5:F1:BA:C4:74:5E:97:50:BB:86:9D:A1:F1:FA:C4:36
 
     Signature Algorithm: sha256WithRSAEncryption
-         59:0b:b1:28:ab:1b:31:93:37:b2:76:ef:83:97:09:0e:fe:44:
-         be:c0:c3:53:bf:82:79:de:e3:a7:90:54:af:70:c0:ac:a5:83:
-         24:0c:53:fc:b3:a5:d8:a2:f1:85:3f:d3:05:fe:30:a2:95:97:
-         b9:c0:74:68:9b:a9:39:6d:c5:a9:aa:a6:2c:e9:75:99:68:74:
-         aa:8f:fa:01:86:fe:54:1c:c7:7f:7f:1b:6b:39:cf:f9:6f:55:
-         4a:6f:5b:00:bc:d3:54:85:4a:e1:14:b7:94:cc:57:0a:a3:4c:
-         b5:13:ae:92:cd:c8:a2:d2:8d:28:03:a1:9b:b3:87:c4:68:a1:
-         27:5b:61:95:e0:f4:5e:71:6a:83:a1:6a:00:43:d9:93:fa:8d:
-         85:9a:ee:36:08:36:71:b5:d9:d5:36:90:99:02:27:64:97:87:
-         c1:d9:a0:4d:1b:27:66:0c:3d:52:94:91:13:86:70:12:15:3a:
-         eb:e5:10:40:9e:4b:9b:34:2e:07:f3:6f:83:5a:79:a1:91:d6:
-         01:4d:75:3c:f3:14:53:1c:9b:b6:11:55:db:21:87:22:90:0d:
-         d8:69:a5:46:07:6c:7a:96:db:e9:49:4f:51:be:b2:ae:e8:37:
-         4c:90:32:6c:95:35:e8:6e:a4:84:cb:57:dd:e2:f2:bf:37:62:
-         39:e0:0f:f4
+         ca:b6:c4:8a:76:e2:14:01:2f:58:ea:5f:28:f3:1d:de:a5:73:
+         17:13:d0:5a:2f:51:f8:a7:79:34:06:9d:73:8e:c9:bd:ba:e4:
+         03:64:7b:fc:29:b1:f3:4a:22:a1:bd:31:7a:4e:03:0d:f0:0c:
+         b0:d8:40:03:7a:b6:a5:2a:ff:78:0b:de:49:7b:ee:11:97:52:
+         2a:df:68:53:d3:88:ac:bd:f2:04:25:68:04:12:8f:ea:26:05:
+         0d:9b:71:76:a9:cd:ff:99:78:44:86:07:56:04:14:c6:d7:1d:
+         63:6e:9f:07:76:95:0b:a0:2b:a2:0d:c4:79:ff:80:c2:80:cb:
+         83:c3:ec:ae:46:62:bb:09:71:c9:65:00:b8:6a:13:a4:a7:31:
+         ad:ff:81:97:1c:84:1e:16:d5:c2:69:83:88:63:2d:33:31:52:
+         1b:fc:dc:c7:40:5c:c8:3e:0a:15:87:7f:82:47:8d:3e:f2:3e:
+         43:34:c1:8f:9c:16:61:1e:17:3f:4b:37:e1:aa:80:ad:87:09:
+         cb:5c:fe:5a:28:4d:85:ca:45:58:6f:a6:ab:e2:f7:7a:24:c9:
+         34:2a:75:b9:29:b8:db:cf:0b:72:e3:89:06:d6:6c:a9:9f:82:
+         e6:0f:90:b9:1a:4e:d1:f1:24:32:79:77:d3:cf:8f:27:64:f3:
+         d6:3e:ff:45
 -----BEGIN CERTIFICATE-----
 MIIDzDCCArSgAwIBAgIBATANBgkqhkiG9w0BAQsFADBgMQswCQYDVQQGEwJHQjET
 MBEGA1UECAwKRGVyYnlzaGlyZTEVMBMGA1UECgwMUGFobyBQcm9qZWN0MRAwDgYD
@@ -64,19 +64,19 @@ VQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMB4XDTEyMDgyMDAwMDAw
 MFoXDTEyMDgyMTAwMDAwMFowdjELMAkGA1UEBhMCR0IxGDAWBgNVBAgMD05vdHRp
 bmdoYW1zaGlyZTETMBEGA1UEBwwKTm90dGluZ2hhbTEPMA0GA1UECgwGU2VydmVy
 MRMwEQYDVQQLDApQcm9kdWN0aW9uMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0G
-CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD4Wun7zKp/inEQMTQJk5q72dotAkY+
-Gu3uWjvoHe09Vfi8xQv0dz2uTkqDoCOQFe+ZXo76CM4EgnF1AlRRcqsC6gYHemCN
-e5xUUyS2Jokrxvr8idoglUyS6JlF6tW6QmIRBYiUeQ/+aBKzsFzqOQ1vz+ISFs/g
-XTdp/YyrUayol7u+XCkX6+5NQy4ky6+E5JY2Mz6aRVQld+/tdkteahs1gwlU8dGw
-WTLEZLtQj0Pi3RXGxLjyEpv7hkicIdS92iZxju0/micVBMIsLEq/If5tNUDLeEHi
-zdQ5v5oaQ8HSnqlEJOTd751bbNVEXDIvT0GfDyc9VZGWyiIb1hj5Y6FhAgMBAAGj
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpTIjbVjb4/OHra62cD3g+fdc0xoOU
+bYMH1T7L+5Vh5XN4Q9tR2aBO7I5DIZEfVpUIR3yDOJC7U5Ht/bS9CCfd1tlb/buE
+Hi5i2TwdTclrF0XXnrSlnCLNFEEyw0GtjfUvo9VZH6ErZ9MBg2STgGu/WrhRhiCg
+5D8YDGcZjeNYbYWDj4s3sn0hP2XPGVMuVt9NiVB+jGqO3SEVFTGbwlyYaB4x/8Zs
+H6hCuNpi3K5iTEDwBsbm9KmYPe37wCpj2mBpgxEOzrqT10snj4aR7+RlXyC+BPJN
+1tF0xavpGN8W+ZqK/y8jxUY+BBZO+sEK9NyOGtpfoa1Qel1gAD4JuI5tAgMBAAGj
 ezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVk
-IENlcnRpZmljYXRlMB0GA1UdDgQWBBSP41yj8ErDHGKSCoM3vei5tpEi5DAfBgNV
-HSMEGDAWgBRjrYnEIxMT0esATQEOJeNeDJ1ByTANBgkqhkiG9w0BAQsFAAOCAQEA
-WQuxKKsbMZM3snbvg5cJDv5EvsDDU7+Ced7jp5BUr3DArKWDJAxT/LOl2KLxhT/T
-Bf4wopWXucB0aJupOW3FqaqmLOl1mWh0qo/6AYb+VBzHf38baznP+W9VSm9bALzT
-VIVK4RS3lMxXCqNMtROuks3IotKNKAOhm7OHxGihJ1thleD0XnFqg6FqAEPZk/qN
-hZruNgg2cbXZ1TaQmQInZJeHwdmgTRsnZgw9UpSRE4ZwEhU66+UQQJ5LmzQuB/Nv
-g1p5oZHWAU11PPMUUxybthFV2yGHIpAN2GmlRgdsepbb6UlPUb6yrug3TJAybJU1
-6G6khMtX3eLyvzdiOeAP9A==
+IENlcnRpZmljYXRlMB0GA1UdDgQWBBTDRzPPBxgUfJrkqxFiiYhUPV196DAfBgNV
+HSMEGDAWgBTCjwmb1fG6xHRel1C7hp2h8frENjANBgkqhkiG9w0BAQsFAAOCAQEA
+yrbEinbiFAEvWOpfKPMd3qVzFxPQWi9R+Kd5NAadc47JvbrkA2R7/Cmx80oiob0x
+ek4DDfAMsNhAA3q2pSr/eAveSXvuEZdSKt9oU9OIrL3yBCVoBBKP6iYFDZtxdqnN
+/5l4RIYHVgQUxtcdY26fB3aVC6Arog3Eef+AwoDLg8PsrkZiuwlxyWUAuGoTpKcx
+rf+BlxyEHhbVwmmDiGMtMzFSG/zcx0BcyD4KFYd/gkeNPvI+QzTBj5wWYR4XP0s3
+4aqArYcJy1z+WihNhcpFWG+mq+L3eiTJNCp1uSm4288LcuOJBtZsqZ+C5g+QuRpO
+0fEkMnl308+PJ2Tz1j7/RQ==
 -----END CERTIFICATE-----
diff --git a/test/ssl/server.crt b/test/ssl/server.crt
index 6dda234..08a6db5 100644
--- a/test/ssl/server.crt
+++ b/test/ssl/server.crt
@@ -5,31 +5,31 @@ Certificate:
         Signature Algorithm: sha256WithRSAEncryption
         Issuer: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA
         Validity
-            Not Before: Jul 28 09:12:10 2020 GMT
-            Not After : Jul 27 09:12:10 2025 GMT
+            Not Before: Jul  7 11:14:42 2021 GMT
+            Not After : Jul  6 11:14:42 2026 GMT
         Subject: C=GB, ST=Nottinghamshire, L=Nottingham, O=Server, OU=Production, CN=localhost
         Subject Public Key Info:
             Public Key Algorithm: rsaEncryption
                 RSA Public-Key: (2048 bit)
                 Modulus:
-                    00:f8:5a:e9:fb:cc:aa:7f:8a:71:10:31:34:09:93:
-                    9a:bb:d9:da:2d:02:46:3e:1a:ed:ee:5a:3b:e8:1d:
-                    ed:3d:55:f8:bc:c5:0b:f4:77:3d:ae:4e:4a:83:a0:
-                    23:90:15:ef:99:5e:8e:fa:08:ce:04:82:71:75:02:
-                    54:51:72:ab:02:ea:06:07:7a:60:8d:7b:9c:54:53:
-                    24:b6:26:89:2b:c6:fa:fc:89:da:20:95:4c:92:e8:
-                    99:45:ea:d5:ba:42:62:11:05:88:94:79:0f:fe:68:
-                    12:b3:b0:5c:ea:39:0d:6f:cf:e2:12:16:cf:e0:5d:
-                    37:69:fd:8c:ab:51:ac:a8:97:bb:be:5c:29:17:eb:
-                    ee:4d:43:2e:24:cb:af:84:e4:96:36:33:3e:9a:45:
-                    54:25:77:ef:ed:76:4b:5e:6a:1b:35:83:09:54:f1:
-                    d1:b0:59:32:c4:64:bb:50:8f:43:e2:dd:15:c6:c4:
-                    b8:f2:12:9b:fb:86:48:9c:21:d4:bd:da:26:71:8e:
-                    ed:3f:9a:27:15:04:c2:2c:2c:4a:bf:21:fe:6d:35:
-                    40:cb:78:41:e2:cd:d4:39:bf:9a:1a:43:c1:d2:9e:
-                    a9:44:24:e4:dd:ef:9d:5b:6c:d5:44:5c:32:2f:4f:
-                    41:9f:0f:27:3d:55:91:96:ca:22:1b:d6:18:f9:63:
-                    a1:61
+                    00:a9:4c:88:db:56:36:f8:fc:e1:eb:6b:ad:9c:0f:
+                    78:3e:7d:d7:34:c6:83:94:6d:83:07:d5:3e:cb:fb:
+                    95:61:e5:73:78:43:db:51:d9:a0:4e:ec:8e:43:21:
+                    91:1f:56:95:08:47:7c:83:38:90:bb:53:91:ed:fd:
+                    b4:bd:08:27:dd:d6:d9:5b:fd:bb:84:1e:2e:62:d9:
+                    3c:1d:4d:c9:6b:17:45:d7:9e:b4:a5:9c:22:cd:14:
+                    41:32:c3:41:ad:8d:f5:2f:a3:d5:59:1f:a1:2b:67:
+                    d3:01:83:64:93:80:6b:bf:5a:b8:51:86:20:a0:e4:
+                    3f:18:0c:67:19:8d:e3:58:6d:85:83:8f:8b:37:b2:
+                    7d:21:3f:65:cf:19:53:2e:56:df:4d:89:50:7e:8c:
+                    6a:8e:dd:21:15:15:31:9b:c2:5c:98:68:1e:31:ff:
+                    c6:6c:1f:a8:42:b8:da:62:dc:ae:62:4c:40:f0:06:
+                    c6:e6:f4:a9:98:3d:ed:fb:c0:2a:63:da:60:69:83:
+                    11:0e:ce:ba:93:d7:4b:27:8f:86:91:ef:e4:65:5f:
+                    20:be:04:f2:4d:d6:d1:74:c5:ab:e9:18:df:16:f9:
+                    9a:8a:ff:2f:23:c5:46:3e:04:16:4e:fa:c1:0a:f4:
+                    dc:8e:1a:da:5f:a1:ad:50:7a:5d:60:00:3e:09:b8:
+                    8e:6d
                 Exponent: 65537 (0x10001)
         X509v3 extensions:
             X509v3 Basic Constraints: 
@@ -37,46 +37,46 @@ Certificate:
             Netscape Comment: 
                 OpenSSL Generated Certificate
             X509v3 Subject Key Identifier: 
-                8F:E3:5C:A3:F0:4A:C3:1C:62:92:0A:83:37:BD:E8:B9:B6:91:22:E4
+                C3:47:33:CF:07:18:14:7C:9A:E4:AB:11:62:89:88:54:3D:5D:7D:E8
             X509v3 Authority Key Identifier: 
-                keyid:63:AD:89:C4:23:13:13:D1:EB:00:4D:01:0E:25:E3:5E:0C:9D:41:C9
+                keyid:C2:8F:09:9B:D5:F1:BA:C4:74:5E:97:50:BB:86:9D:A1:F1:FA:C4:36
 
     Signature Algorithm: sha256WithRSAEncryption
-         b4:32:28:0f:3f:ba:4d:97:0c:0d:8a:7e:cc:dd:cc:99:a4:50:
-         62:91:f5:21:e0:48:8f:a3:bb:90:5d:99:28:27:68:83:da:07:
-         60:cd:f2:b7:eb:81:91:4b:43:7b:44:c5:7f:3f:3f:f9:dc:11:
-         3e:8c:ba:bd:13:73:f8:81:8f:4d:3c:4a:4a:a7:13:fc:7d:c3:
-         33:a2:4f:8e:39:43:f9:7c:ec:59:ad:54:47:e3:08:a3:7b:bd:
-         e5:ca:00:b0:5b:ce:3d:f6:0d:a7:35:25:9e:a3:50:17:92:2c:
-         12:ae:d1:85:17:bc:09:b2:5c:06:a6:24:ad:2f:d0:57:5b:0d:
-         83:b5:c8:f7:c0:f9:31:d1:0c:1b:70:c4:a7:91:86:40:f7:76:
-         a0:a1:72:7f:d2:a3:c4:ff:67:a8:8a:e2:e9:82:19:a1:93:25:
-         27:cb:66:12:78:d7:f0:a0:69:3e:42:77:94:f4:15:a9:48:d0:
-         73:fc:e8:53:61:87:cb:95:de:73:bf:2a:ba:fc:32:74:ab:ba:
-         bf:21:45:85:56:ab:56:cb:51:58:fc:c6:49:a9:39:db:f3:76:
-         2a:18:be:c6:0a:d6:5c:09:5c:1e:1f:d7:41:4c:34:f1:59:ce:
-         7d:c9:fd:82:65:a5:ae:46:34:f7:13:c1:cf:a3:0e:32:c0:fc:
-         0f:77:99:f9
+         a9:34:8d:b4:6c:99:14:e7:10:dc:36:7e:c2:24:7f:bf:9d:65:
+         4c:2b:50:90:13:de:85:29:d7:b0:d5:c2:a0:d6:c2:42:40:9f:
+         6a:b7:6a:05:cf:7e:57:ff:2c:3c:ba:0c:cf:e7:0a:92:89:6e:
+         8b:bb:0c:b5:28:79:00:76:ed:12:cc:54:79:05:41:88:26:c9:
+         e3:a8:6b:ba:1a:31:92:e6:40:2c:c6:a9:e8:4b:1b:4c:25:f1:
+         7b:c5:19:0b:73:37:53:86:d5:8e:d1:1c:78:73:e4:a5:84:0f:
+         49:5a:eb:80:15:09:c2:69:83:34:c0:da:db:9d:fa:eb:32:1f:
+         e0:2e:99:f2:b0:76:91:8a:eb:34:b5:4d:c9:79:2a:f8:ef:f0:
+         6d:55:a4:9d:f9:5f:61:d3:f8:ab:95:0a:12:12:64:33:c3:2f:
+         6b:64:14:31:bf:42:c9:c8:9e:be:45:4f:02:c8:50:54:be:79:
+         fe:e2:9a:fa:2d:b7:73:25:34:ea:53:dd:03:a4:f9:82:28:a7:
+         95:37:f7:45:56:21:7a:e6:71:eb:95:34:99:15:1c:26:ac:00:
+         bc:95:b0:91:d8:8a:d0:0d:98:8e:28:d7:76:14:b6:94:c9:ab:
+         df:87:40:58:12:da:ee:65:d8:08:f2:05:f2:5e:3e:d2:8d:09:
+         38:8f:b2:79
 -----BEGIN CERTIFICATE-----
 MIIDzDCCArSgAwIBAgIBATANBgkqhkiG9w0BAQsFADBgMQswCQYDVQQGEwJHQjET
 MBEGA1UECAwKRGVyYnlzaGlyZTEVMBMGA1UECgwMUGFobyBQcm9qZWN0MRAwDgYD
-VQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMB4XDTIwMDcyODA5MTIx
-MFoXDTI1MDcyNzA5MTIxMFowdjELMAkGA1UEBhMCR0IxGDAWBgNVBAgMD05vdHRp
+VQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMB4XDTIxMDcwNzExMTQ0
+MloXDTI2MDcwNjExMTQ0MlowdjELMAkGA1UEBhMCR0IxGDAWBgNVBAgMD05vdHRp
 bmdoYW1zaGlyZTETMBEGA1UEBwwKTm90dGluZ2hhbTEPMA0GA1UECgwGU2VydmVy
 MRMwEQYDVQQLDApQcm9kdWN0aW9uMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0G
-CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD4Wun7zKp/inEQMTQJk5q72dotAkY+
-Gu3uWjvoHe09Vfi8xQv0dz2uTkqDoCOQFe+ZXo76CM4EgnF1AlRRcqsC6gYHemCN
-e5xUUyS2Jokrxvr8idoglUyS6JlF6tW6QmIRBYiUeQ/+aBKzsFzqOQ1vz+ISFs/g
-XTdp/YyrUayol7u+XCkX6+5NQy4ky6+E5JY2Mz6aRVQld+/tdkteahs1gwlU8dGw
-WTLEZLtQj0Pi3RXGxLjyEpv7hkicIdS92iZxju0/micVBMIsLEq/If5tNUDLeEHi
-zdQ5v5oaQ8HSnqlEJOTd751bbNVEXDIvT0GfDyc9VZGWyiIb1hj5Y6FhAgMBAAGj
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpTIjbVjb4/OHra62cD3g+fdc0xoOU
+bYMH1T7L+5Vh5XN4Q9tR2aBO7I5DIZEfVpUIR3yDOJC7U5Ht/bS9CCfd1tlb/buE
+Hi5i2TwdTclrF0XXnrSlnCLNFEEyw0GtjfUvo9VZH6ErZ9MBg2STgGu/WrhRhiCg
+5D8YDGcZjeNYbYWDj4s3sn0hP2XPGVMuVt9NiVB+jGqO3SEVFTGbwlyYaB4x/8Zs
+H6hCuNpi3K5iTEDwBsbm9KmYPe37wCpj2mBpgxEOzrqT10snj4aR7+RlXyC+BPJN
+1tF0xavpGN8W+ZqK/y8jxUY+BBZO+sEK9NyOGtpfoa1Qel1gAD4JuI5tAgMBAAGj
 ezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVk
-IENlcnRpZmljYXRlMB0GA1UdDgQWBBSP41yj8ErDHGKSCoM3vei5tpEi5DAfBgNV
-HSMEGDAWgBRjrYnEIxMT0esATQEOJeNeDJ1ByTANBgkqhkiG9w0BAQsFAAOCAQEA
-tDIoDz+6TZcMDYp+zN3MmaRQYpH1IeBIj6O7kF2ZKCdog9oHYM3yt+uBkUtDe0TF
-fz8/+dwRPoy6vRNz+IGPTTxKSqcT/H3DM6JPjjlD+XzsWa1UR+MIo3u95coAsFvO
-PfYNpzUlnqNQF5IsEq7RhRe8CbJcBqYkrS/QV1sNg7XI98D5MdEMG3DEp5GGQPd2
-oKFyf9KjxP9nqIri6YIZoZMlJ8tmEnjX8KBpPkJ3lPQVqUjQc/zoU2GHy5Xec78q
-uvwydKu6vyFFhVarVstRWPzGSak52/N2Khi+xgrWXAlcHh/XQUw08VnOfcn9gmWl
-rkY09xPBz6MOMsD8D3eZ+Q==
+IENlcnRpZmljYXRlMB0GA1UdDgQWBBTDRzPPBxgUfJrkqxFiiYhUPV196DAfBgNV
+HSMEGDAWgBTCjwmb1fG6xHRel1C7hp2h8frENjANBgkqhkiG9w0BAQsFAAOCAQEA
+qTSNtGyZFOcQ3DZ+wiR/v51lTCtQkBPehSnXsNXCoNbCQkCfardqBc9+V/8sPLoM
+z+cKkolui7sMtSh5AHbtEsxUeQVBiCbJ46hruhoxkuZALMap6EsbTCXxe8UZC3M3
+U4bVjtEceHPkpYQPSVrrgBUJwmmDNMDa25366zIf4C6Z8rB2kYrrNLVNyXkq+O/w
+bVWknflfYdP4q5UKEhJkM8Mva2QUMb9CycievkVPAshQVL55/uKa+i23cyU06lPd
+A6T5giinlTf3RVYheuZx65U0mRUcJqwAvJWwkdiK0A2YjijXdhS2lMmr34dAWBLa
+7mXYCPIF8l4+0o0JOI+yeQ==
 -----END CERTIFICATE-----
diff --git a/test/ssl/server.key b/test/ssl/server.key
index 2c5261e..ff9bead 100644
--- a/test/ssl/server.key
+++ b/test/ssl/server.key
@@ -1,27 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
-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
+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
 -----END RSA PRIVATE KEY-----
diff --git a/test/ssl/signingCA/crlnumber b/test/ssl/signingCA/crlnumber
deleted file mode 100644
index 9e22bcb..0000000
--- a/test/ssl/signingCA/crlnumber
+++ /dev/null
@@ -1 +0,0 @@
-02
diff --git a/test/ssl/signingCA/crlnumber.old b/test/ssl/signingCA/crlnumber.old
deleted file mode 100644
index 8a0f05e..0000000
--- a/test/ssl/signingCA/crlnumber.old
+++ /dev/null
@@ -1 +0,0 @@
-01
diff --git a/test/ssl/signingCA/index.txt b/test/ssl/signingCA/index.txt
deleted file mode 100644
index 23b24aa..0000000
--- a/test/ssl/signingCA/index.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-V	120821000000Z		01	unknown	/C=GB/ST=Nottinghamshire/L=Nottingham/O=Server/OU=Production/CN=localhost
-V	250727091210Z		02	unknown	/C=GB/ST=Nottinghamshire/L=Nottingham/O=Server/OU=Production/CN=test client
-V	120821000000Z		03	unknown	/C=GB/ST=Nottinghamshire/L=Nottingham/O=Server/OU=Production/CN=test client expired
-R	250727091211Z	200728091211Z	04	unknown	/C=GB/ST=Nottinghamshire/L=Nottingham/O=Server/OU=Production/CN=test client revoked
diff --git a/test/ssl/signingCA/index.txt.attr b/test/ssl/signingCA/index.txt.attr
deleted file mode 100644
index 8f7e63a..0000000
--- a/test/ssl/signingCA/index.txt.attr
+++ /dev/null
@@ -1 +0,0 @@
-unique_subject = yes
diff --git a/test/ssl/signingCA/index.txt.attr.old b/test/ssl/signingCA/index.txt.attr.old
deleted file mode 100644
index 8f7e63a..0000000
--- a/test/ssl/signingCA/index.txt.attr.old
+++ /dev/null
@@ -1 +0,0 @@
-unique_subject = yes
diff --git a/test/ssl/signingCA/index.txt.old b/test/ssl/signingCA/index.txt.old
deleted file mode 100644
index 86469be..0000000
--- a/test/ssl/signingCA/index.txt.old
+++ /dev/null
@@ -1,4 +0,0 @@
-V	120821000000Z		01	unknown	/C=GB/ST=Nottinghamshire/L=Nottingham/O=Server/OU=Production/CN=localhost
-V	250727091210Z		02	unknown	/C=GB/ST=Nottinghamshire/L=Nottingham/O=Server/OU=Production/CN=test client
-V	120821000000Z		03	unknown	/C=GB/ST=Nottinghamshire/L=Nottingham/O=Server/OU=Production/CN=test client expired
-V	250727091211Z		04	unknown	/C=GB/ST=Nottinghamshire/L=Nottingham/O=Server/OU=Production/CN=test client revoked
diff --git a/test/ssl/signingCA/newcerts/01.pem b/test/ssl/signingCA/newcerts/01.pem
deleted file mode 100644
index 0ec40b9..0000000
--- a/test/ssl/signingCA/newcerts/01.pem
+++ /dev/null
@@ -1,82 +0,0 @@
-Certificate:
-    Data:
-        Version: 3 (0x2)
-        Serial Number: 1 (0x1)
-        Signature Algorithm: sha256WithRSAEncryption
-        Issuer: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA
-        Validity
-            Not Before: Aug 20 00:00:00 2012 GMT
-            Not After : Aug 21 00:00:00 2012 GMT
-        Subject: C=GB, ST=Nottinghamshire, L=Nottingham, O=Server, OU=Production, CN=localhost
-        Subject Public Key Info:
-            Public Key Algorithm: rsaEncryption
-                RSA Public-Key: (2048 bit)
-                Modulus:
-                    00:f8:5a:e9:fb:cc:aa:7f:8a:71:10:31:34:09:93:
-                    9a:bb:d9:da:2d:02:46:3e:1a:ed:ee:5a:3b:e8:1d:
-                    ed:3d:55:f8:bc:c5:0b:f4:77:3d:ae:4e:4a:83:a0:
-                    23:90:15:ef:99:5e:8e:fa:08:ce:04:82:71:75:02:
-                    54:51:72:ab:02:ea:06:07:7a:60:8d:7b:9c:54:53:
-                    24:b6:26:89:2b:c6:fa:fc:89:da:20:95:4c:92:e8:
-                    99:45:ea:d5:ba:42:62:11:05:88:94:79:0f:fe:68:
-                    12:b3:b0:5c:ea:39:0d:6f:cf:e2:12:16:cf:e0:5d:
-                    37:69:fd:8c:ab:51:ac:a8:97:bb:be:5c:29:17:eb:
-                    ee:4d:43:2e:24:cb:af:84:e4:96:36:33:3e:9a:45:
-                    54:25:77:ef:ed:76:4b:5e:6a:1b:35:83:09:54:f1:
-                    d1:b0:59:32:c4:64:bb:50:8f:43:e2:dd:15:c6:c4:
-                    b8:f2:12:9b:fb:86:48:9c:21:d4:bd:da:26:71:8e:
-                    ed:3f:9a:27:15:04:c2:2c:2c:4a:bf:21:fe:6d:35:
-                    40:cb:78:41:e2:cd:d4:39:bf:9a:1a:43:c1:d2:9e:
-                    a9:44:24:e4:dd:ef:9d:5b:6c:d5:44:5c:32:2f:4f:
-                    41:9f:0f:27:3d:55:91:96:ca:22:1b:d6:18:f9:63:
-                    a1:61
-                Exponent: 65537 (0x10001)
-        X509v3 extensions:
-            X509v3 Basic Constraints: 
-                CA:FALSE
-            Netscape Comment: 
-                OpenSSL Generated Certificate
-            X509v3 Subject Key Identifier: 
-                8F:E3:5C:A3:F0:4A:C3:1C:62:92:0A:83:37:BD:E8:B9:B6:91:22:E4
-            X509v3 Authority Key Identifier: 
-                keyid:63:AD:89:C4:23:13:13:D1:EB:00:4D:01:0E:25:E3:5E:0C:9D:41:C9
-
-    Signature Algorithm: sha256WithRSAEncryption
-         59:0b:b1:28:ab:1b:31:93:37:b2:76:ef:83:97:09:0e:fe:44:
-         be:c0:c3:53:bf:82:79:de:e3:a7:90:54:af:70:c0:ac:a5:83:
-         24:0c:53:fc:b3:a5:d8:a2:f1:85:3f:d3:05:fe:30:a2:95:97:
-         b9:c0:74:68:9b:a9:39:6d:c5:a9:aa:a6:2c:e9:75:99:68:74:
-         aa:8f:fa:01:86:fe:54:1c:c7:7f:7f:1b:6b:39:cf:f9:6f:55:
-         4a:6f:5b:00:bc:d3:54:85:4a:e1:14:b7:94:cc:57:0a:a3:4c:
-         b5:13:ae:92:cd:c8:a2:d2:8d:28:03:a1:9b:b3:87:c4:68:a1:
-         27:5b:61:95:e0:f4:5e:71:6a:83:a1:6a:00:43:d9:93:fa:8d:
-         85:9a:ee:36:08:36:71:b5:d9:d5:36:90:99:02:27:64:97:87:
-         c1:d9:a0:4d:1b:27:66:0c:3d:52:94:91:13:86:70:12:15:3a:
-         eb:e5:10:40:9e:4b:9b:34:2e:07:f3:6f:83:5a:79:a1:91:d6:
-         01:4d:75:3c:f3:14:53:1c:9b:b6:11:55:db:21:87:22:90:0d:
-         d8:69:a5:46:07:6c:7a:96:db:e9:49:4f:51:be:b2:ae:e8:37:
-         4c:90:32:6c:95:35:e8:6e:a4:84:cb:57:dd:e2:f2:bf:37:62:
-         39:e0:0f:f4
------BEGIN CERTIFICATE-----
-MIIDzDCCArSgAwIBAgIBATANBgkqhkiG9w0BAQsFADBgMQswCQYDVQQGEwJHQjET
-MBEGA1UECAwKRGVyYnlzaGlyZTEVMBMGA1UECgwMUGFobyBQcm9qZWN0MRAwDgYD
-VQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMB4XDTEyMDgyMDAwMDAw
-MFoXDTEyMDgyMTAwMDAwMFowdjELMAkGA1UEBhMCR0IxGDAWBgNVBAgMD05vdHRp
-bmdoYW1zaGlyZTETMBEGA1UEBwwKTm90dGluZ2hhbTEPMA0GA1UECgwGU2VydmVy
-MRMwEQYDVQQLDApQcm9kdWN0aW9uMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0G
-CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD4Wun7zKp/inEQMTQJk5q72dotAkY+
-Gu3uWjvoHe09Vfi8xQv0dz2uTkqDoCOQFe+ZXo76CM4EgnF1AlRRcqsC6gYHemCN
-e5xUUyS2Jokrxvr8idoglUyS6JlF6tW6QmIRBYiUeQ/+aBKzsFzqOQ1vz+ISFs/g
-XTdp/YyrUayol7u+XCkX6+5NQy4ky6+E5JY2Mz6aRVQld+/tdkteahs1gwlU8dGw
-WTLEZLtQj0Pi3RXGxLjyEpv7hkicIdS92iZxju0/micVBMIsLEq/If5tNUDLeEHi
-zdQ5v5oaQ8HSnqlEJOTd751bbNVEXDIvT0GfDyc9VZGWyiIb1hj5Y6FhAgMBAAGj
-ezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVk
-IENlcnRpZmljYXRlMB0GA1UdDgQWBBSP41yj8ErDHGKSCoM3vei5tpEi5DAfBgNV
-HSMEGDAWgBRjrYnEIxMT0esATQEOJeNeDJ1ByTANBgkqhkiG9w0BAQsFAAOCAQEA
-WQuxKKsbMZM3snbvg5cJDv5EvsDDU7+Ced7jp5BUr3DArKWDJAxT/LOl2KLxhT/T
-Bf4wopWXucB0aJupOW3FqaqmLOl1mWh0qo/6AYb+VBzHf38baznP+W9VSm9bALzT
-VIVK4RS3lMxXCqNMtROuks3IotKNKAOhm7OHxGihJ1thleD0XnFqg6FqAEPZk/qN
-hZruNgg2cbXZ1TaQmQInZJeHwdmgTRsnZgw9UpSRE4ZwEhU66+UQQJ5LmzQuB/Nv
-g1p5oZHWAU11PPMUUxybthFV2yGHIpAN2GmlRgdsepbb6UlPUb6yrug3TJAybJU1
-6G6khMtX3eLyvzdiOeAP9A==
------END CERTIFICATE-----
diff --git a/test/ssl/signingCA/newcerts/02.pem b/test/ssl/signingCA/newcerts/02.pem
deleted file mode 100644
index 9839438..0000000
--- a/test/ssl/signingCA/newcerts/02.pem
+++ /dev/null
@@ -1,82 +0,0 @@
-Certificate:
-    Data:
-        Version: 3 (0x2)
-        Serial Number: 2 (0x2)
-        Signature Algorithm: sha256WithRSAEncryption
-        Issuer: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA
-        Validity
-            Not Before: Jul 28 09:12:10 2020 GMT
-            Not After : Jul 27 09:12:10 2025 GMT
-        Subject: C=GB, ST=Nottinghamshire, L=Nottingham, O=Server, OU=Production, CN=test client
-        Subject Public Key Info:
-            Public Key Algorithm: rsaEncryption
-                RSA Public-Key: (2048 bit)
-                Modulus:
-                    00:b5:85:9e:ea:67:a7:6e:35:bc:0a:60:5f:dd:2f:
-                    b8:5d:ab:1f:56:82:1a:55:82:1e:ba:7e:05:59:fc:
-                    97:11:7b:73:59:f2:61:84:3b:da:ff:09:ba:21:25:
-                    b9:38:4b:76:c1:ae:2d:a0:7e:20:23:e9:21:50:e7:
-                    b9:a3:81:35:d7:f1:39:3c:8f:d5:34:82:7a:e4:68:
-                    4b:f3:f1:23:3b:67:e9:3e:d4:97:01:86:8e:52:fa:
-                    a2:a7:41:ea:03:92:fc:f9:1b:bb:9e:4d:b2:32:78:
-                    7e:da:8c:95:ef:c9:53:97:30:34:9e:5c:d0:b3:2f:
-                    44:33:01:29:23:bd:b8:82:05:f5:12:f6:9e:d2:6a:
-                    6f:74:14:12:08:6e:c2:85:88:7f:a4:7c:f5:e7:76:
-                    e2:2b:e8:c1:44:bf:1d:c7:c6:b4:84:c4:4b:4e:56:
-                    63:dc:91:b0:91:68:d3:07:55:c0:e3:92:ad:e4:75:
-                    2b:aa:f4:67:7e:7f:91:40:22:4e:11:6d:5e:97:ba:
-                    9a:ea:11:a8:19:1f:74:72:2b:a9:8f:05:24:03:11:
-                    1f:9c:ac:33:49:37:43:60:c7:0b:87:77:01:64:ad:
-                    f5:b4:42:c5:ca:38:6f:fc:0d:b1:df:f7:94:e5:14:
-                    d9:b9:7f:a1:da:2a:51:ab:4d:da:32:5c:3a:5b:5c:
-                    0e:a1
-                Exponent: 65537 (0x10001)
-        X509v3 extensions:
-            X509v3 Basic Constraints: 
-                CA:FALSE
-            Netscape Comment: 
-                OpenSSL Generated Certificate
-            X509v3 Subject Key Identifier: 
-                5E:12:4A:C4:94:EA:40:AC:15:A8:14:93:63:F9:61:C8:35:89:79:9E
-            X509v3 Authority Key Identifier: 
-                keyid:63:AD:89:C4:23:13:13:D1:EB:00:4D:01:0E:25:E3:5E:0C:9D:41:C9
-
-    Signature Algorithm: sha256WithRSAEncryption
-         4e:f3:5d:99:00:94:57:9e:a7:19:b8:9e:ee:f2:f9:01:9e:b7:
-         02:e7:fe:a4:89:cf:a1:5f:04:4e:46:98:b8:aa:de:8d:f4:c2:
-         6b:71:25:08:f4:64:7e:99:5e:07:dd:43:43:cf:08:f7:e7:11:
-         f1:79:1d:7a:0b:d3:b2:f1:70:9d:da:dd:c6:f7:a0:e2:3b:64:
-         22:7b:74:b8:70:93:74:51:a2:c4:aa:3d:15:b1:b9:68:20:9e:
-         ec:98:1d:86:4d:e9:42:61:41:92:a7:08:12:27:f3:7e:3e:6a:
-         54:1e:76:19:f5:b5:11:4d:de:25:22:63:ef:78:17:61:3e:4c:
-         d2:ea:ff:e9:1c:52:a8:82:29:24:cd:50:75:72:32:c1:00:83:
-         bf:14:c3:c6:bd:b0:0f:4f:83:86:ea:67:7a:8d:82:c6:42:4a:
-         e5:73:4b:69:c2:96:9a:74:ea:5e:82:62:8c:a4:02:17:08:cf:
-         f6:07:76:84:b5:91:b1:ce:79:4a:be:e5:43:5d:31:55:5e:4e:
-         38:42:cd:70:9b:22:c4:3e:ee:24:fa:ae:07:ae:75:f5:f9:b5:
-         05:53:58:bd:12:50:1f:68:5e:ff:8d:30:2d:61:85:97:5a:48:
-         0c:61:ab:ad:4c:95:67:32:c4:8c:3f:dd:9f:39:ed:9a:be:73:
-         50:53:20:de
------BEGIN CERTIFICATE-----
-MIIDzjCCAragAwIBAgIBAjANBgkqhkiG9w0BAQsFADBgMQswCQYDVQQGEwJHQjET
-MBEGA1UECAwKRGVyYnlzaGlyZTEVMBMGA1UECgwMUGFobyBQcm9qZWN0MRAwDgYD
-VQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMB4XDTIwMDcyODA5MTIx
-MFoXDTI1MDcyNzA5MTIxMFoweDELMAkGA1UEBhMCR0IxGDAWBgNVBAgMD05vdHRp
-bmdoYW1zaGlyZTETMBEGA1UEBwwKTm90dGluZ2hhbTEPMA0GA1UECgwGU2VydmVy
-MRMwEQYDVQQLDApQcm9kdWN0aW9uMRQwEgYDVQQDDAt0ZXN0IGNsaWVudDCCASIw
-DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALWFnupnp241vApgX90vuF2rH1aC
-GlWCHrp+BVn8lxF7c1nyYYQ72v8JuiEluThLdsGuLaB+ICPpIVDnuaOBNdfxOTyP
-1TSCeuRoS/PxIztn6T7UlwGGjlL6oqdB6gOS/Pkbu55NsjJ4ftqMle/JU5cwNJ5c
-0LMvRDMBKSO9uIIF9RL2ntJqb3QUEghuwoWIf6R89ed24ivowUS/HcfGtITES05W
-Y9yRsJFo0wdVwOOSreR1K6r0Z35/kUAiThFtXpe6muoRqBkfdHIrqY8FJAMRH5ys
-M0k3Q2DHC4d3AWSt9bRCxco4b/wNsd/3lOUU2bl/odoqUatN2jJcOltcDqECAwEA
-AaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0
-ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFF4SSsSU6kCsFagUk2P5Ycg1iXmeMB8G
-A1UdIwQYMBaAFGOticQjExPR6wBNAQ4l414MnUHJMA0GCSqGSIb3DQEBCwUAA4IB
-AQBO812ZAJRXnqcZuJ7u8vkBnrcC5/6kic+hXwRORpi4qt6N9MJrcSUI9GR+mV4H
-3UNDzwj35xHxeR16C9Oy8XCd2t3G96DiO2Qie3S4cJN0UaLEqj0VsbloIJ7smB2G
-TelCYUGSpwgSJ/N+PmpUHnYZ9bURTd4lImPveBdhPkzS6v/pHFKogikkzVB1cjLB
-AIO/FMPGvbAPT4OG6md6jYLGQkrlc0tpwpaadOpegmKMpAIXCM/2B3aEtZGxznlK
-vuVDXTFVXk44Qs1wmyLEPu4k+q4HrnX1+bUFU1i9ElAfaF7/jTAtYYWXWkgMYaut
-TJVnMsSMP92fOe2avnNQUyDe
------END CERTIFICATE-----
diff --git a/test/ssl/signingCA/newcerts/03.pem b/test/ssl/signingCA/newcerts/03.pem
deleted file mode 100644
index c2b60f6..0000000
--- a/test/ssl/signingCA/newcerts/03.pem
+++ /dev/null
@@ -1,82 +0,0 @@
-Certificate:
-    Data:
-        Version: 3 (0x2)
-        Serial Number: 3 (0x3)
-        Signature Algorithm: sha256WithRSAEncryption
-        Issuer: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA
-        Validity
-            Not Before: Aug 20 00:00:00 2012 GMT
-            Not After : Aug 21 00:00:00 2012 GMT
-        Subject: C=GB, ST=Nottinghamshire, L=Nottingham, O=Server, OU=Production, CN=test client expired
-        Subject Public Key Info:
-            Public Key Algorithm: rsaEncryption
-                RSA Public-Key: (2048 bit)
-                Modulus:
-                    00:b5:85:9e:ea:67:a7:6e:35:bc:0a:60:5f:dd:2f:
-                    b8:5d:ab:1f:56:82:1a:55:82:1e:ba:7e:05:59:fc:
-                    97:11:7b:73:59:f2:61:84:3b:da:ff:09:ba:21:25:
-                    b9:38:4b:76:c1:ae:2d:a0:7e:20:23:e9:21:50:e7:
-                    b9:a3:81:35:d7:f1:39:3c:8f:d5:34:82:7a:e4:68:
-                    4b:f3:f1:23:3b:67:e9:3e:d4:97:01:86:8e:52:fa:
-                    a2:a7:41:ea:03:92:fc:f9:1b:bb:9e:4d:b2:32:78:
-                    7e:da:8c:95:ef:c9:53:97:30:34:9e:5c:d0:b3:2f:
-                    44:33:01:29:23:bd:b8:82:05:f5:12:f6:9e:d2:6a:
-                    6f:74:14:12:08:6e:c2:85:88:7f:a4:7c:f5:e7:76:
-                    e2:2b:e8:c1:44:bf:1d:c7:c6:b4:84:c4:4b:4e:56:
-                    63:dc:91:b0:91:68:d3:07:55:c0:e3:92:ad:e4:75:
-                    2b:aa:f4:67:7e:7f:91:40:22:4e:11:6d:5e:97:ba:
-                    9a:ea:11:a8:19:1f:74:72:2b:a9:8f:05:24:03:11:
-                    1f:9c:ac:33:49:37:43:60:c7:0b:87:77:01:64:ad:
-                    f5:b4:42:c5:ca:38:6f:fc:0d:b1:df:f7:94:e5:14:
-                    d9:b9:7f:a1:da:2a:51:ab:4d:da:32:5c:3a:5b:5c:
-                    0e:a1
-                Exponent: 65537 (0x10001)
-        X509v3 extensions:
-            X509v3 Basic Constraints: 
-                CA:FALSE
-            Netscape Comment: 
-                OpenSSL Generated Certificate
-            X509v3 Subject Key Identifier: 
-                5E:12:4A:C4:94:EA:40:AC:15:A8:14:93:63:F9:61:C8:35:89:79:9E
-            X509v3 Authority Key Identifier: 
-                keyid:63:AD:89:C4:23:13:13:D1:EB:00:4D:01:0E:25:E3:5E:0C:9D:41:C9
-
-    Signature Algorithm: sha256WithRSAEncryption
-         b1:91:cd:ed:2f:8a:32:1f:90:7d:8f:5f:64:84:89:f9:9d:5c:
-         97:d6:d7:cd:08:ba:2d:8c:56:ca:f8:41:93:7a:4e:3e:d2:81:
-         57:e5:d4:3f:0c:3a:c8:f9:b1:5b:4c:8d:fc:cd:3c:d4:96:0a:
-         63:22:32:de:0e:c6:9c:f4:37:97:43:4b:aa:c9:e9:9d:85:4a:
-         f4:92:8b:14:50:8e:17:a5:41:49:70:9b:ae:2d:6b:07:d5:33:
-         64:86:bc:95:b2:46:dc:d4:be:77:35:04:91:99:08:31:53:d8:
-         5c:5e:44:91:1e:90:3c:c3:6a:f9:fb:05:17:e9:16:3a:0d:ad:
-         5d:af:4e:6a:4f:f2:b6:6f:9a:1f:59:4d:11:c4:94:dd:b0:af:
-         3c:68:cb:cf:4d:3d:b0:88:97:2b:7b:cd:bf:20:c0:57:ab:f5:
-         db:63:b7:7e:23:3c:dd:60:be:fb:20:e7:b6:14:30:c3:2f:09:
-         a1:44:41:f1:bc:b9:75:4c:c3:5e:4c:5c:5c:a7:a9:67:58:a6:
-         ed:de:2a:be:d9:28:03:ad:17:c0:de:aa:05:fc:8e:b7:cc:c1:
-         bc:8d:12:06:62:83:6f:e7:9a:7c:a5:31:73:5f:75:31:ac:c7:
-         b8:1b:c4:82:ea:c2:09:d6:d8:7f:27:6f:08:b9:13:e6:3a:60:
-         e5:e3:a3:24
------BEGIN CERTIFICATE-----
-MIID1zCCAr+gAwIBAgIBAzANBgkqhkiG9w0BAQsFADBgMQswCQYDVQQGEwJHQjET
-MBEGA1UECAwKRGVyYnlzaGlyZTEVMBMGA1UECgwMUGFobyBQcm9qZWN0MRAwDgYD
-VQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMB4XDTEyMDgyMDAwMDAw
-MFoXDTEyMDgyMTAwMDAwMFowgYAxCzAJBgNVBAYTAkdCMRgwFgYDVQQIDA9Ob3R0
-aW5naGFtc2hpcmUxEzARBgNVBAcMCk5vdHRpbmdoYW0xDzANBgNVBAoMBlNlcnZl
-cjETMBEGA1UECwwKUHJvZHVjdGlvbjEcMBoGA1UEAwwTdGVzdCBjbGllbnQgZXhw
-aXJlZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALWFnupnp241vApg
-X90vuF2rH1aCGlWCHrp+BVn8lxF7c1nyYYQ72v8JuiEluThLdsGuLaB+ICPpIVDn
-uaOBNdfxOTyP1TSCeuRoS/PxIztn6T7UlwGGjlL6oqdB6gOS/Pkbu55NsjJ4ftqM
-le/JU5cwNJ5c0LMvRDMBKSO9uIIF9RL2ntJqb3QUEghuwoWIf6R89ed24ivowUS/
-HcfGtITES05WY9yRsJFo0wdVwOOSreR1K6r0Z35/kUAiThFtXpe6muoRqBkfdHIr
-qY8FJAMRH5ysM0k3Q2DHC4d3AWSt9bRCxco4b/wNsd/3lOUU2bl/odoqUatN2jJc
-OltcDqECAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNT
-TCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFF4SSsSU6kCsFagUk2P5
-Ycg1iXmeMB8GA1UdIwQYMBaAFGOticQjExPR6wBNAQ4l414MnUHJMA0GCSqGSIb3
-DQEBCwUAA4IBAQCxkc3tL4oyH5B9j19khIn5nVyX1tfNCLotjFbK+EGTek4+0oFX
-5dQ/DDrI+bFbTI38zTzUlgpjIjLeDsac9DeXQ0uqyemdhUr0kosUUI4XpUFJcJuu
-LWsH1TNkhryVskbc1L53NQSRmQgxU9hcXkSRHpA8w2r5+wUX6RY6Da1dr05qT/K2
-b5ofWU0RxJTdsK88aMvPTT2wiJcre82/IMBXq/XbY7d+IzzdYL77IOe2FDDDLwmh
-REHxvLl1TMNeTFxcp6lnWKbt3iq+2SgDrRfA3qoF/I63zMG8jRIGYoNv55p8pTFz
-X3UxrMe4G8SC6sIJ1th/J28IuRPmOmDl46Mk
------END CERTIFICATE-----
diff --git a/test/ssl/signingCA/newcerts/04.pem b/test/ssl/signingCA/newcerts/04.pem
deleted file mode 100644
index bdd3cbb..0000000
--- a/test/ssl/signingCA/newcerts/04.pem
+++ /dev/null
@@ -1,82 +0,0 @@
-Certificate:
-    Data:
-        Version: 3 (0x2)
-        Serial Number: 4 (0x4)
-        Signature Algorithm: sha256WithRSAEncryption
-        Issuer: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA
-        Validity
-            Not Before: Jul 28 09:12:11 2020 GMT
-            Not After : Jul 27 09:12:11 2025 GMT
-        Subject: C=GB, ST=Nottinghamshire, L=Nottingham, O=Server, OU=Production, CN=test client revoked
-        Subject Public Key Info:
-            Public Key Algorithm: rsaEncryption
-                RSA Public-Key: (2048 bit)
-                Modulus:
-                    00:a4:22:2c:88:b3:4f:f2:a0:b0:18:7d:28:d8:b4:
-                    69:72:a0:ad:90:f9:c4:1b:c1:f9:b1:e0:e5:87:02:
-                    74:21:6d:ad:87:87:e2:6b:af:82:02:6f:e8:65:a9:
-                    04:f8:05:a5:90:59:de:c9:26:41:a3:7b:dd:d6:91:
-                    0f:6b:57:92:04:54:e6:a1:74:1c:af:38:b7:f9:e8:
-                    c8:fb:bb:ff:ac:23:58:77:17:3a:76:42:a6:0f:f1:
-                    dc:bc:76:7e:25:1b:8a:89:8a:5b:06:76:41:6a:29:
-                    5c:72:27:e4:1b:68:c7:ca:3d:57:31:5e:9a:9d:5c:
-                    88:c8:bc:d8:e6:bd:b5:be:91:88:e4:4e:8b:37:33:
-                    13:6d:00:37:b4:12:02:da:1c:31:61:0e:e8:b5:0d:
-                    f2:f4:e2:46:55:d1:21:bf:ea:92:c3:8c:3d:16:0c:
-                    a4:e5:dd:d6:e5:f6:39:b5:4d:69:65:c0:fe:bf:1c:
-                    33:23:b4:f8:94:40:28:47:e7:8f:59:06:31:72:4b:
-                    6b:55:f2:7d:aa:c6:96:ff:1c:08:28:ab:82:b7:99:
-                    22:1f:1c:08:54:d1:4c:f6:f2:79:06:77:da:58:63:
-                    48:47:7f:13:6c:7f:eb:d3:06:1d:25:72:4b:04:d1:
-                    2e:1d:76:a8:56:f5:59:bc:be:c7:78:6d:e2:1d:57:
-                    b2:f3
-                Exponent: 65537 (0x10001)
-        X509v3 extensions:
-            X509v3 Basic Constraints: 
-                CA:FALSE
-            Netscape Comment: 
-                OpenSSL Generated Certificate
-            X509v3 Subject Key Identifier: 
-                21:58:60:7A:B1:55:A8:CA:EF:2A:D2:0B:25:83:81:DB:E3:A8:7C:7B
-            X509v3 Authority Key Identifier: 
-                keyid:63:AD:89:C4:23:13:13:D1:EB:00:4D:01:0E:25:E3:5E:0C:9D:41:C9
-
-    Signature Algorithm: sha256WithRSAEncryption
-         ac:60:10:4d:cc:10:f8:71:5f:a2:94:20:d8:9e:8c:d9:fc:10:
-         4c:d7:b5:b7:57:8b:08:a5:97:50:24:27:38:47:e9:37:49:35:
-         92:07:fc:e2:75:37:4d:3c:b4:24:2c:62:da:6f:40:8a:55:d6:
-         a3:bf:ef:a0:90:8d:4e:ee:51:c7:5a:30:cc:36:9f:8c:72:c7:
-         36:4c:c4:96:ce:bf:df:e9:58:5a:54:63:be:e5:9c:27:63:e6:
-         55:4e:2f:09:df:10:b2:7c:42:eb:f1:86:66:f2:07:ca:b3:e5:
-         ca:14:58:34:ac:ac:4e:99:e4:37:c7:b1:8c:56:23:9a:5c:a8:
-         fe:17:77:ec:ce:c8:77:25:35:b7:98:1c:90:25:bc:fc:5a:4b:
-         85:b0:96:55:ca:cb:83:bc:a1:2a:1a:f1:cb:73:e1:8d:4b:65:
-         ca:86:1f:43:a5:f1:4a:86:d2:50:81:c3:c1:e6:c6:ed:15:0f:
-         01:2c:58:63:44:4e:17:c0:59:d1:cf:c8:cb:37:9c:9e:dc:e1:
-         83:10:90:9e:35:9d:43:23:f2:b9:1d:91:00:33:b4:eb:6a:9d:
-         5a:1c:e0:b8:6b:f0:dc:05:ae:15:f3:42:7f:0c:9a:bd:06:25:
-         75:79:53:ea:a8:9b:f5:4c:96:52:1a:85:91:a5:68:08:a5:f4:
-         32:ca:9d:58
------BEGIN CERTIFICATE-----
-MIID1zCCAr+gAwIBAgIBBDANBgkqhkiG9w0BAQsFADBgMQswCQYDVQQGEwJHQjET
-MBEGA1UECAwKRGVyYnlzaGlyZTEVMBMGA1UECgwMUGFobyBQcm9qZWN0MRAwDgYD
-VQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMB4XDTIwMDcyODA5MTIx
-MVoXDTI1MDcyNzA5MTIxMVowgYAxCzAJBgNVBAYTAkdCMRgwFgYDVQQIDA9Ob3R0
-aW5naGFtc2hpcmUxEzARBgNVBAcMCk5vdHRpbmdoYW0xDzANBgNVBAoMBlNlcnZl
-cjETMBEGA1UECwwKUHJvZHVjdGlvbjEcMBoGA1UEAwwTdGVzdCBjbGllbnQgcmV2
-b2tlZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKQiLIizT/KgsBh9
-KNi0aXKgrZD5xBvB+bHg5YcCdCFtrYeH4muvggJv6GWpBPgFpZBZ3skmQaN73daR
-D2tXkgRU5qF0HK84t/noyPu7/6wjWHcXOnZCpg/x3Lx2fiUbiomKWwZ2QWopXHIn
-5Btox8o9VzFemp1ciMi82Oa9tb6RiOROizczE20AN7QSAtocMWEO6LUN8vTiRlXR
-Ib/qksOMPRYMpOXd1uX2ObVNaWXA/r8cMyO0+JRAKEfnj1kGMXJLa1XyfarGlv8c
-CCirgreZIh8cCFTRTPbyeQZ32lhjSEd/E2x/69MGHSVySwTRLh12qFb1Wby+x3ht
-4h1XsvMCAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNT
-TCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFCFYYHqxVajK7yrSCyWD
-gdvjqHx7MB8GA1UdIwQYMBaAFGOticQjExPR6wBNAQ4l414MnUHJMA0GCSqGSIb3
-DQEBCwUAA4IBAQCsYBBNzBD4cV+ilCDYnozZ/BBM17W3V4sIpZdQJCc4R+k3STWS
-B/zidTdNPLQkLGLab0CKVdajv++gkI1O7lHHWjDMNp+Mcsc2TMSWzr/f6VhaVGO+
-5ZwnY+ZVTi8J3xCyfELr8YZm8gfKs+XKFFg0rKxOmeQ3x7GMViOaXKj+F3fszsh3
-JTW3mByQJbz8WkuFsJZVysuDvKEqGvHLc+GNS2XKhh9DpfFKhtJQgcPB5sbtFQ8B
-LFhjRE4XwFnRz8jLN5ye3OGDEJCeNZ1DI/K5HZEAM7Trap1aHOC4a/DcBa4V80J/
-DJq9BiV1eVPqqJv1TJZSGoWRpWgIpfQyyp1Y
------END CERTIFICATE-----
diff --git a/test/ssl/signingCA/serial b/test/ssl/signingCA/serial
deleted file mode 100644
index eeee65e..0000000
--- a/test/ssl/signingCA/serial
+++ /dev/null
@@ -1 +0,0 @@
-05
diff --git a/test/ssl/signingCA/serial.old b/test/ssl/signingCA/serial.old
deleted file mode 100644
index 6496923..0000000
--- a/test/ssl/signingCA/serial.old
+++ /dev/null
@@ -1 +0,0 @@
-04
diff --git a/test/ssl/test-alt-ca.crt b/test/ssl/test-alt-ca.crt
index c191a58..88e8560 100644
--- a/test/ssl/test-alt-ca.crt
+++ b/test/ssl/test-alt-ca.crt
@@ -5,75 +5,75 @@ Certificate:
         Signature Algorithm: sha256WithRSAEncryption
         Issuer: C=GB, ST=Derbyshire, L=Derby, O=Paho Project, OU=Testing, CN=Root CA
         Validity
-            Not Before: Jul 28 09:12:10 2020 GMT
-            Not After : Jul 27 09:12:10 2025 GMT
+            Not Before: Jul  7 11:14:42 2021 GMT
+            Not After : Jul  6 11:14:42 2026 GMT
         Subject: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Alternative Signing CA
         Subject Public Key Info:
             Public Key Algorithm: rsaEncryption
                 RSA Public-Key: (2048 bit)
                 Modulus:
-                    00:bd:c2:9f:2c:d2:a7:25:79:f0:3f:29:65:ac:d3:
-                    8c:ea:72:d2:73:2d:1b:5b:e3:0c:a1:5a:40:f4:a9:
-                    68:bc:a4:50:77:c9:08:75:12:a3:21:3d:a9:d6:dc:
-                    dc:08:b4:47:32:7e:ad:f4:87:de:48:fd:83:d1:b7:
-                    e4:ce:3b:8b:87:99:3c:fa:6b:0e:1c:70:71:6d:d9:
-                    b4:75:7c:6e:2a:03:cc:0e:bb:c7:8c:31:53:67:11:
-                    2f:fd:97:c9:67:05:48:23:81:60:f5:94:94:af:61:
-                    1c:a8:c1:4d:fe:2b:2e:f8:e6:bd:30:c2:52:ec:56:
-                    7a:b7:64:d0:a2:bf:09:e4:d2:a3:c3:f1:f9:e7:12:
-                    96:45:06:20:a2:fd:49:43:87:a7:0f:c1:c6:58:46:
-                    9f:1d:9f:be:86:ce:49:de:c7:35:4d:fd:08:9a:61:
-                    3f:ed:1f:cd:5c:55:77:4f:cc:0e:52:51:2a:7e:04:
-                    4c:10:e1:79:88:9e:f6:8b:6b:bc:20:d2:6b:e9:c4:
-                    c6:94:bc:d8:06:57:a1:b6:b2:30:cd:0d:d3:27:b3:
-                    9e:1b:ac:40:81:6a:f0:a3:f6:62:22:14:40:61:9d:
-                    26:82:b1:aa:fa:0a:0a:45:54:7d:5d:02:59:70:e6:
-                    d2:f9:fc:45:58:83:10:2a:db:10:e3:7d:10:73:1b:
-                    d9:49
+                    00:ca:f5:4f:c2:30:7d:fd:65:75:06:00:22:72:0a:
+                    d0:74:1e:00:03:aa:f3:64:1a:d4:d0:25:85:b9:2e:
+                    73:72:41:06:12:d9:52:1d:39:11:78:2d:c3:0d:d7:
+                    6f:06:05:68:3a:cc:ce:36:8d:a7:3a:a9:31:77:eb:
+                    e9:2d:87:00:7e:86:7a:bb:52:c2:02:d7:ef:07:4f:
+                    a9:88:91:d6:6e:dd:19:84:89:dc:72:bb:08:23:b4:
+                    be:1a:cf:af:b8:1a:af:62:21:d3:d4:a2:78:2f:b6:
+                    4a:44:6f:ab:7f:d7:27:21:79:40:2b:db:bf:90:bf:
+                    fb:cf:a4:fa:8b:25:f6:ad:f9:73:57:41:49:86:1d:
+                    ed:3c:c9:d5:43:e0:ac:8a:4a:88:51:ea:cf:95:f0:
+                    50:4b:ee:4c:fc:74:1d:92:00:5f:75:97:23:e4:b1:
+                    79:b1:b0:b8:e1:97:38:6c:78:b6:c1:a6:e7:2e:95:
+                    39:c8:ed:2a:65:65:b7:09:45:d4:f2:f1:4f:bf:97:
+                    9d:98:b7:26:0d:c1:cf:93:d1:55:9f:af:39:6f:71:
+                    29:a4:e9:74:48:2c:eb:8a:11:3d:3f:c4:3c:12:fe:
+                    0c:d9:c9:fc:2c:77:22:de:c8:bb:8e:05:55:0a:2b:
+                    18:38:0f:68:5d:2f:26:ea:cc:ec:04:df:fb:54:c4:
+                    83:3b
                 Exponent: 65537 (0x10001)
         X509v3 extensions:
             X509v3 Subject Key Identifier: 
-                3A:BA:37:57:E0:82:EB:BA:77:1B:50:07:A6:C9:22:EA:92:76:F9:1C
+                9E:74:83:0D:43:6F:21:68:72:E1:A3:FC:E0:2D:C3:D0:47:55:0C:31
             X509v3 Authority Key Identifier: 
-                keyid:C7:BD:C0:22:65:FF:A7:97:A7:CB:9D:7E:44:E5:13:77:39:7D:BE:BA
+                keyid:13:A0:B6:1F:F5:C7:64:C2:F9:FD:2E:08:F2:19:01:77:54:19:73:7F
 
             X509v3 Basic Constraints: 
                 CA:TRUE
     Signature Algorithm: sha256WithRSAEncryption
-         05:07:f6:5d:7d:2d:5e:35:07:82:1e:cd:06:e6:b6:4c:1b:03:
-         b4:28:3e:16:22:0d:26:5f:f9:69:19:72:24:5a:fb:c6:cf:70:
-         c8:a6:a6:19:8d:10:56:c4:d5:76:bb:38:93:e0:ff:d0:a0:81:
-         47:36:e7:b6:f5:bf:99:36:28:d1:db:59:c0:e9:84:95:0f:db:
-         c0:84:86:9a:ef:78:ac:dd:83:6d:51:e8:ae:28:b4:f8:78:bb:
-         91:87:f5:35:21:fd:12:ff:41:33:7f:22:1e:16:f3:43:c9:97:
-         e2:16:35:db:e8:c8:ee:5a:3e:d4:43:c1:90:52:12:7c:f7:7e:
-         18:ee:60:be:61:41:3a:aa:2d:99:f3:44:86:3c:fd:03:e1:a2:
-         d8:e1:1c:49:b0:39:3e:8c:f6:38:00:4a:84:a5:54:98:ef:c0:
-         6d:2d:48:4a:13:fb:80:5f:58:a1:95:86:3a:a1:63:56:fe:23:
-         7f:db:c9:19:a5:22:cb:34:a9:10:cb:b4:95:9f:c9:c6:16:e0:
-         e3:50:7a:29:60:b0:fe:c7:f5:c8:f9:c6:3b:82:a6:a8:d9:0c:
-         5b:aa:58:f2:31:c3:98:20:87:d8:a1:63:ad:52:ae:b2:85:57:
-         10:d5:75:9e:73:35:47:c5:22:26:1f:53:38:52:4d:fd:2e:16:
-         00:15:d4:da
+         31:c8:c3:5c:31:7c:85:12:e0:01:9c:1a:eb:be:32:f0:19:cd:
+         f3:55:e8:13:34:27:39:69:ca:88:1e:e9:44:47:9b:e1:bf:ff:
+         3f:65:62:02:9f:ae:be:21:1d:03:83:02:3e:a8:f2:d4:fa:e8:
+         14:50:e6:53:9e:e1:90:f9:96:5d:73:f3:da:8d:38:33:6d:5f:
+         f9:ce:9b:60:d3:ae:86:18:7f:ef:4a:d1:69:4d:03:a7:e8:a5:
+         c4:42:59:50:22:d1:25:bd:a4:22:d1:9c:f9:4c:72:ee:3d:e3:
+         e1:c7:b0:c2:16:ba:46:4e:c9:29:91:e0:97:52:d8:3c:be:e2:
+         ef:1c:aa:89:6d:ba:75:35:80:12:5d:5c:33:15:6c:fe:1b:1f:
+         4a:b4:1a:12:47:d3:4b:cd:d2:96:61:88:69:ac:b4:3c:d5:be:
+         52:7e:a0:99:5a:52:65:6a:86:ea:a7:a2:50:66:48:71:e3:82:
+         9f:fc:ff:89:58:ef:04:fa:af:76:98:1b:40:d6:71:14:29:1e:
+         db:b8:31:47:2b:4b:de:f3:e2:e5:d0:a0:75:1e:b6:d9:32:3f:
+         8e:54:c3:92:e1:0f:74:85:0d:e9:27:5b:21:e8:f0:7b:10:3c:
+         14:e4:9d:97:65:18:ef:57:ce:de:b9:f7:01:d0:b9:e4:81:7a:
+         a3:d2:35:8c
 -----BEGIN CERTIFICATE-----
 MIIDpDCCAoygAwIBAgIBAjANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQGEwJHQjET
 MBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwFRGVyYnkxFTATBgNVBAoMDFBh
 aG8gUHJvamVjdDEQMA4GA1UECwwHVGVzdGluZzEQMA4GA1UEAwwHUm9vdCBDQTAe
-Fw0yMDA3MjgwOTEyMTBaFw0yNTA3MjcwOTEyMTBaMGwxCzAJBgNVBAYTAkdCMRMw
+Fw0yMTA3MDcxMTE0NDJaFw0yNjA3MDYxMTE0NDJaMGwxCzAJBgNVBAYTAkdCMRMw
 EQYDVQQIDApEZXJieXNoaXJlMRUwEwYDVQQKDAxQYWhvIFByb2plY3QxEDAOBgNV
 BAsMB1Rlc3RpbmcxHzAdBgNVBAMMFkFsdGVybmF0aXZlIFNpZ25pbmcgQ0EwggEi
-MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9wp8s0qclefA/KWWs04zqctJz
-LRtb4wyhWkD0qWi8pFB3yQh1EqMhPanW3NwItEcyfq30h95I/YPRt+TOO4uHmTz6
-aw4ccHFt2bR1fG4qA8wOu8eMMVNnES/9l8lnBUgjgWD1lJSvYRyowU3+Ky745r0w
-wlLsVnq3ZNCivwnk0qPD8fnnEpZFBiCi/UlDh6cPwcZYRp8dn76GzknexzVN/Qia
-YT/tH81cVXdPzA5SUSp+BEwQ4XmInvaLa7wg0mvpxMaUvNgGV6G2sjDNDdMns54b
-rECBavCj9mIiFEBhnSaCsar6CgpFVH1dAllw5tL5/EVYgxAq2xDjfRBzG9lJAgMB
-AAGjUDBOMB0GA1UdDgQWBBQ6ujdX4ILruncbUAemySLqknb5HDAfBgNVHSMEGDAW
-gBTHvcAiZf+nl6fLnX5E5RN3OX2+ujAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB
-CwUAA4IBAQAFB/ZdfS1eNQeCHs0G5rZMGwO0KD4WIg0mX/lpGXIkWvvGz3DIpqYZ
-jRBWxNV2uziT4P/QoIFHNue29b+ZNijR21nA6YSVD9vAhIaa73is3YNtUeiuKLT4
-eLuRh/U1If0S/0EzfyIeFvNDyZfiFjXb6MjuWj7UQ8GQUhJ8934Y7mC+YUE6qi2Z
-80SGPP0D4aLY4RxJsDk+jPY4AEqEpVSY78BtLUhKE/uAX1ihlYY6oWNW/iN/28kZ
-pSLLNKkQy7SVn8nGFuDjUHopYLD+x/XI+cY7gqao2QxbqljyMcOYIIfYoWOtUq6y
-hVcQ1XWeczVHxSImH1M4Uk39LhYAFdTa
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDK9U/CMH39ZXUGACJyCtB0HgAD
+qvNkGtTQJYW5LnNyQQYS2VIdORF4LcMN128GBWg6zM42jac6qTF36+kthwB+hnq7
+UsIC1+8HT6mIkdZu3RmEidxyuwgjtL4az6+4Gq9iIdPUongvtkpEb6t/1ycheUAr
+27+Qv/vPpPqLJfat+XNXQUmGHe08ydVD4KyKSohR6s+V8FBL7kz8dB2SAF91lyPk
+sXmxsLjhlzhseLbBpuculTnI7SplZbcJRdTy8U+/l52YtyYNwc+T0VWfrzlvcSmk
+6XRILOuKET0/xDwS/gzZyfwsdyLeyLuOBVUKKxg4D2hdLybqzOwE3/tUxIM7AgMB
+AAGjUDBOMB0GA1UdDgQWBBSedIMNQ28haHLho/zgLcPQR1UMMTAfBgNVHSMEGDAW
+gBQToLYf9cdkwvn9LgjyGQF3VBlzfzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB
+CwUAA4IBAQAxyMNcMXyFEuABnBrrvjLwGc3zVegTNCc5acqIHulER5vhv/8/ZWIC
+n66+IR0DgwI+qPLU+ugUUOZTnuGQ+ZZdc/PajTgzbV/5zptg066GGH/vStFpTQOn
+6KXEQllQItElvaQi0Zz5THLuPePhx7DCFrpGTskpkeCXUtg8vuLvHKqJbbp1NYAS
+XVwzFWz+Gx9KtBoSR9NLzdKWYYhprLQ81b5SfqCZWlJlaobqp6JQZkhx44Kf/P+J
+WO8E+q92mBtA1nEUKR7buDFHK0ve8+Ll0KB1HrbZMj+OVMOS4Q90hQ3pJ1sh6PB7
+EDwU5J2XZRjvV87eufcB0LnkgXqj0jWM
 -----END CERTIFICATE-----
diff --git a/test/ssl/test-alt-ca.key b/test/ssl/test-alt-ca.key
index 11ad615..334cc21 100644
--- a/test/ssl/test-alt-ca.key
+++ b/test/ssl/test-alt-ca.key
@@ -1,27 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
-MIIEpgIBAAKCAQEAvcKfLNKnJXnwPyllrNOM6nLScy0bW+MMoVpA9KlovKRQd8kI
-dRKjIT2p1tzcCLRHMn6t9IfeSP2D0bfkzjuLh5k8+msOHHBxbdm0dXxuKgPMDrvH
-jDFTZxEv/ZfJZwVII4Fg9ZSUr2EcqMFN/isu+Oa9MMJS7FZ6t2TQor8J5NKjw/H5
-5xKWRQYgov1JQ4enD8HGWEafHZ++hs5J3sc1Tf0ImmE/7R/NXFV3T8wOUlEqfgRM
-EOF5iJ72i2u8INJr6cTGlLzYBlehtrIwzQ3TJ7OeG6xAgWrwo/ZiIhRAYZ0mgrGq
-+goKRVR9XQJZcObS+fxFWIMQKtsQ430QcxvZSQIDAQABAoIBAQCQQ4X/7aZlN28O
-lcpWuf5Hv0N/jKtgEB1qDdOULbcSs1j5uDxVxPWAMAEO+JnPhD8chHJ7lU/I7fNp
-XQT2RQ6u6DETsI/pzxJtETXAm/ikuH5KNThfvKBLmyWBBgR37ewKEjJEgdEIBCxl
-n5BLy0jKsaDjkNi6FIT3KA31rIc5Dhq72vb08ePkKhYN6DsIInY/qTT0z/ZOzSw2
-3r/9juMWIfxxAf4d2QO7WtQbVhAaRGPU0dV4g/Xp03iq0SwJv0t92eFjHLCTKQUV
-2WZ7K0NRcNwtlqTgWBj3BIl38IJC3w3baMl2QN+vgbfQZEarPFqfyPA39HDmz7HK
-m9NlRDSBAoGBAOyIbnQRTwHM9sEGQGXqxN2pKRNUDIOT3Hs9rMauXuf4+4ut2iXD
-fnNl5Gxr/N9E+y8JlygsINSretRcz+IPNWdU0BtHA8mieY4tozgMQghTTOGtOJbg
-4kD/ycWiQpl52HjOeZyr/SlMUGSDM4UulJelFDwgplWS0dlqCtMSQecnAoGBAM1g
-ua8v0evTkcmKAL9BQwqqAs7QPtGOWT0EWPfyBCrxV4xIHfX9xqqWbay6fOpn00dz
-fi/XzBRE11ZPxVUUg8CxCzLM1BWLCDUVJqq2gsxTjsjFVSZ3m/Uc3ta4vSEiIVMj
-P+xGbxanSuyHdEfECDaVVxnR5g4knGjUTMM1pwIPAoGBAKHMWbHvvRnAOPKk8hxF
-UdFGy6A7GCxRUrrC35Mw6B7KRFex3s2CQtdxVaWM4PdhWmk8qDeEqBiSDH2D13gN
-azx52bkvBLpC9993/HR0fh7vzGF7eoBK6LoJvt4ANoqBvMA2jR3M0GoVl9CJpw5t
-ZJuvAn523xEeU4njbfAgRxB1AoGBAJ0kkk8SFtwLkPQVTMcKB2MEfIuD2Vz8Pxmp
-1u04V6oH6cXjdFFBMR5bZWWRd3zfnlCAdR/kTTmBBwb6mHGl9b2deYUQJoKpU8lR
-+rkKVwBCOvEx1BpP0Jv20CaZSdUtSTRUt1dw+RTrQi/C4Nz8iJBDKlcOPKm7p5Wq
-Q2XmCnhPAoGBAI9jnZx0Tjvs7vHiBf/eEnlj0LRSiELVEmK/aZCo+VKrEpYBFubn
-jQla3tPPRi709WB3g7k7taMbPiOVypTWjo/hbfXqTI9QIDaWwCfqLlaohSjXKv7e
-fJyrBNaw4i9nDlJm8ncJABz3NwwJBXIRWUEjPlNvutGO92Smvzc1U5Ar
+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
 -----END RSA PRIVATE KEY-----
diff --git a/test/ssl/test-bad-root-ca.crt b/test/ssl/test-bad-root-ca.crt
index 10b0821..9666151 100644
--- a/test/ssl/test-bad-root-ca.crt
+++ b/test/ssl/test-bad-root-ca.crt
@@ -1,23 +1,23 @@
 -----BEGIN CERTIFICATE-----
-MIIDwDCCAqigAwIBAgIUPom8FiJ7Gc6+2UdE/a8wWxttZ3IwDQYJKoZIhvcNAQEL
+MIIDwDCCAqigAwIBAgIUZQzHgL/r7su5tQBXIZvwqj+HdqIwDQYJKoZIhvcNAQEL
 BQAwcTELMAkGA1UEBhMCR0IxEzARBgNVBAgMCkRlcmJ5c2hpcmUxDjAMBgNVBAcM
 BURlcmJ5MRUwEwYDVQQKDAxQYWhvIFByb2plY3QxEDAOBgNVBAsMB1Rlc3Rpbmcx
-FDASBgNVBAMMC0JhZCBSb290IENBMB4XDTIwMDcyODA5MTIxMFoXDTMwMDcyNjA5
-MTIxMFowcTELMAkGA1UEBhMCR0IxEzARBgNVBAgMCkRlcmJ5c2hpcmUxDjAMBgNV
+FDASBgNVBAMMC0JhZCBSb290IENBMB4XDTIxMDcwNzExMTQ0MloXDTMxMDcwNTEx
+MTQ0MlowcTELMAkGA1UEBhMCR0IxEzARBgNVBAgMCkRlcmJ5c2hpcmUxDjAMBgNV
 BAcMBURlcmJ5MRUwEwYDVQQKDAxQYWhvIFByb2plY3QxEDAOBgNVBAsMB1Rlc3Rp
 bmcxFDASBgNVBAMMC0JhZCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
-MIIBCgKCAQEA3Hh4kadQ46H4iHVSdXimBLkHkVrxuby/+PG5qLr3FiXnU8Q3XbBE
-hVacRJMtvsG+a8zDv7xthsCZlrhi8yvamWpvvYaVJJPsdpQ5ZrL5Ul8tqYJ9dJgD
-m6uC6fLfXYQiAgWAkc5p2MN/jWuVp5QTdl5/lbPtx/H03AP4IPuHMLxKu/HJ4WOC
-DMfkNA0/n8TlJM0VHQZeSLhxi/wUJbxXU/LGNmSBmb/Ryjf6BQvEqDkSicjYw27I
-Dw9zhiTkYj6EoSGTQ+9e/Z9MTMZWTP931j/83jDQJhTi0lS7fw0699vjtoYJ2vqE
-FUuFrg+C4JhV7bUZgNT4MuNaIUMJabbzawIDAQABo1AwTjAdBgNVHQ4EFgQUqb9z
-n8mhTvpSiCzWg7YlA7riIYAwHwYDVR0jBBgwFoAUqb9zn8mhTvpSiCzWg7YlA7ri
-IYAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAdrUdzuIiiM35IXTi
-ZoowONNAeOFefVGWXLPSuMJwbsT6Uy85cGFhdFNjKqykMnorrEeqzXtMWv6Zyr8t
-CjR2eATJiIUfm1o9LNZ8SdLigWBWfpdYRY+0AXnO+S1K7h+X9KpoHHT7nbzTyxiz
-E8cx+Vhjh0DZRe6VkIoG6JXEMdrvmWW9wTIrpqEAhBFKtS1TkPMBj7pUg8yEfk2I
-4zCwZR6vD6hjPhL6/niAimypSrBjQHW4DRAwLUAeu1DWnJiSq2bPHhrPKUdJOhcq
-g6vPEPe6ffj+5oAoceSgz0LttsYK4GPPrYeDN6O5rl6T/2UjkFf/+mqUyecP4Pxz
-JOnyDg==
+MIIBCgKCAQEA3DRlR+CK8ZBUfaZB4RzWErQ+lewTPu+FaQuCSBvBMKgd+S0r/mZ0
+dQsA2/mWymggxb1wIZt/TY9sz2v1pYmg2Cw/dld9AQvJaqMXPdn3ZAmsihYd3is8
+M4c0FlFowHv0LyWUOlRJfUrAPc4aorRK4Dqssl+s8W/ikyiKsMKBk0Z1LQBxUzst
+AAQ3voBJW7SVsRzYgcbyITW2IXYBjsIJRWK68+TCNCqlmVKEKvg6DYFJ+1HLE/z6
+jFmzb10lXgg4FKKkUtWruawkErUbb8k1le+rnjZ0Wi9FhSWdM3HL1l6NX0IMWRmC
+Jm7WvXBHo9KCarcp+MWnKEjP4b/gR0bEvQIDAQABo1AwTjAdBgNVHQ4EFgQUc9Tb
+8nwTWl+HI3JbYIQAFL3eZYgwHwYDVR0jBBgwFoAUc9Tb8nwTWl+HI3JbYIQAFL3e
+ZYgwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAnhmnsmFDcZ7b0YEQ
+XlIj760EUHe/G1Rtur2fjjP59qz/8msP9QtVAy5O/a22aCBehhOzcK2e3NFIKlBs
+D5xb7UE8RV0i9btP57+ZF6kP89sMB/DBHI+TDD93cms5OvTDCteKO++CpwnkNmav
+xRnvQGAAOA+zxVsPlYL1Wy9Z75LQWdZKS68/JTd7b2LOQnYD2qp4omPYEYGAFtFz
+38EMgRS/QyQjjiHx6rz/wU5hmQCrNOUUCw+bHumZL3mxJ/aSBNrGVBLQ2Hnofhsw
+1Ik2EyzMh3+nlf2ImlSZKfjfg8PrfmgbvXvNc8AWRCad7xwt9ZPzaxj05vKupcwO
+2tIkOw==
 -----END CERTIFICATE-----
diff --git a/test/ssl/test-bad-root-ca.key b/test/ssl/test-bad-root-ca.key
index 9353c0d..8162c2b 100644
--- a/test/ssl/test-bad-root-ca.key
+++ b/test/ssl/test-bad-root-ca.key
@@ -1,27 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
-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
+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
 -----END RSA PRIVATE KEY-----
diff --git a/test/ssl/test-fake-root-ca.crt b/test/ssl/test-fake-root-ca.crt
index eaf2c07..490d8e5 100644
--- a/test/ssl/test-fake-root-ca.crt
+++ b/test/ssl/test-fake-root-ca.crt
@@ -1,22 +1,22 @@
 -----BEGIN CERTIFICATE-----
-MIIDuDCCAqCgAwIBAgIUe49B4x+bcqcL+TkVVqSsR0cUMoUwDQYJKoZIhvcNAQEL
+MIIDuDCCAqCgAwIBAgIUcE+qUkqyZKFChp9j3+SuflCAAwgwDQYJKoZIhvcNAQEL
 BQAwbTELMAkGA1UEBhMCR0IxEzARBgNVBAgMCkRlcmJ5c2hpcmUxDjAMBgNVBAcM
 BURlcmJ5MRUwEwYDVQQKDAxQYWhvIFByb2plY3QxEDAOBgNVBAsMB1Rlc3Rpbmcx
-EDAOBgNVBAMMB1Jvb3QgQ0EwHhcNMjAwNzI4MDkxMjEwWhcNMzAwNzI2MDkxMjEw
+EDAOBgNVBAMMB1Jvb3QgQ0EwHhcNMjEwNzA3MTExNDQyWhcNMzEwNzA1MTExNDQy
 WjBtMQswCQYDVQQGEwJHQjETMBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwF
 RGVyYnkxFTATBgNVBAoMDFBhaG8gUHJvamVjdDEQMA4GA1UECwwHVGVzdGluZzEQ
 MA4GA1UEAwwHUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
-ALwdmfN6sTm66ynPE9zBZw1XsAnGC8InEOPIm1msBUPxTfJwvRf8nx/d7KcNML8b
-iFF0D5rtxCuBD+iOvgaceI0i7gO26NA+No9hXbOROk6+ye7hGRxDiiucE8lot+7C
-extOuZRHdXk6FwGx+M5Fzi1zyzCw9nzWxF1u3kBO6t/FHHYduA3qadY2T/kWBzb5
-rsDDAAbvaHu1MQNO1hNdt6ffSf54mQS8jNr1mIzE5Fz6wZnfLhzafzBMMjWVhtQX
-Bb7hoXO7a1zZ+pYCpwiU/WNP7JkR49cyqd1rOP2fA9yj+ArhUYADxHMDfDnwOzHL
-wtO6FF2BEZ2QNylMuFP9it0CAwEAAaNQME4wHQYDVR0OBBYEFOuu0LJ5ALxAbHkY
-/wnXccwH+xbeMB8GA1UdIwQYMBaAFOuu0LJ5ALxAbHkY/wnXccwH+xbeMAwGA1Ud
-EwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBALbRQwMta94JBYp4h0U31GWhr3xS
-ua4emh16vWNjiCdFN/YfksEe53ryykMVRZU7KNzur025EsjQitCIpA4j/wdRXgFq
-Ha8zL7oHxCU7kBlFLDYYnNRifWVRwOczvKmvmrchpT89nnWfbHSr5W3I7rMVVAH1
-QJIx9dJ87hD+1WmXtHp2aQrNUQKyhmABvsUIwinnL5kRGTLGMJrGgQOTejOg/Jn2
-fYu2Rs0Z7/Gp7aPY9MAsAQZa12O/D5GFYMFDbZGM1ISndfreVI//T00OjCPLMUtA
-+REDMXpxZOOjQJZBu99+KAL4goLOwO7ZKLF/ny8SAVg8sgmOr7b9ClfP4Ek=
+AJ9Oc5kDGXyzORIRKBNiQ0+p63LN4DUK8ux/PMD7SEuGKq+LIdieCjmExUq2nkIE
+052iYCM9aYJa8PuP+8UWl8UKE4/QWW6yWl28/O5n/Hwe28PfiiH1f5kxXt9khMjI
+oc3WCr8YkiDrrKiFyCGvF58b87woQFRMHHqus+o+Xd9YPKhsc/n/AhV4zl0S2wUC
+nnV+UF5c+/vlMh/SnD84yhMlySOC7fRNHziAJqqIpj44hQTdfjM6XDHOf3jSlHfv
+1JxKqyE8hAWxZVZhMBP1v14xQL5AbVhtSNZlIV/LzAGUbBztMKzPfE4GyQKIDCmi
+91A7nbXbkTBz/McL3kxmmAMCAwEAAaNQME4wHQYDVR0OBBYEFDZWdWv057drndtK
+9AYPQhTgHkUuMB8GA1UdIwQYMBaAFDZWdWv057drndtK9AYPQhTgHkUuMAwGA1Ud
+EwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAEuWZu5tSJyNZkzbFT0o/IP8zUgN
+lbfp9DxqqiOwUTx2ykOpMsXU94f+/HEACYQ773G/lXPDmMrz/j3mPYklhws07/SE
+q1jKM6ZXJh74nQekypvXtSY/Xd667JpRxU6GAedizi60owKPIUFpxkW70h4Of5j5
+Py7PGRGDZ7ItGtuk1fxcSCchfm0Q2bST8nOcD8D+MQcttNxGgelp2V6c0XckmijM
+oFUy/3Nm1B/qv4QWckmVX+gm+iBTBANItvcj+ie2c6diFwz7htDwOVm7/1Z/73wM
+YyM5Z27mVKR8FwK3jHHRQa5VWtTOdnqG3kHKmAKeOlXgO91wzCh8zq1Q76o=
 -----END CERTIFICATE-----
diff --git a/test/ssl/test-fake-root-ca.key b/test/ssl/test-fake-root-ca.key
index 6631305..8897d10 100644
--- a/test/ssl/test-fake-root-ca.key
+++ b/test/ssl/test-fake-root-ca.key
@@ -1,27 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
-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
+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
 -----END RSA PRIVATE KEY-----
diff --git a/test/ssl/test-root-ca.crt b/test/ssl/test-root-ca.crt
index 4e39cbf..c0f092b 100644
--- a/test/ssl/test-root-ca.crt
+++ b/test/ssl/test-root-ca.crt
@@ -1,22 +1,22 @@
 -----BEGIN CERTIFICATE-----
-MIIDuDCCAqCgAwIBAgIUfktixcRWQfrifKAqkK/KyuylnZkwDQYJKoZIhvcNAQEL
+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
-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
+EDAOBgNVBAMMB1Jvb3QgQ0EwHhcNMjEwNzA3MTExNDQyWhcNMzEwNzA1MTExNDQy
 WjBtMQswCQYDVQQGEwJHQjETMBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwF
 RGVyYnkxFTATBgNVBAoMDFBhaG8gUHJvamVjdDEQMA4GA1UECwwHVGVzdGluZzEQ
 MA4GA1UEAwwHUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
-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
+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
 -----END CERTIFICATE-----
diff --git a/test/ssl/test-root-ca.key b/test/ssl/test-root-ca.key
index 6c0044e..0d7b3be 100644
--- a/test/ssl/test-root-ca.key
+++ b/test/ssl/test-root-ca.key
@@ -1,27 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
-MIIEpAIBAAKCAQEA29qpF+kQ71MXDAH/mzK23Hxlp7Kr53TPS7JC5Y8hZunWJSvB
-NlNvuTAb/0d00LS3UpLB1vgSv0H/JxPrmVQROUgTcx/MK7qcj+qIgL7KGwIG115U
-At8HZ6x0JD5fYuyN9q+mwwOtuOtnV/IUJh0rQprgXdgzI1iPvkGPT8NtReyAZgvb
-RB7i5LbI0SVAaqjSZHbWUM6EoAjRtV+t1p/B8VkHkIh3HZ7SQCnABBA57fGCzeZp
-NHlVCUh0UECnHTJp+kkFbZcP/XZBYqc6E5tPDhAPc515eV0TyBeszipkiuvz/Mz8
-gixTFJIxvYUjExQewx4gvO825eODHp/BE5bkkQIDAQABAoIBAC+HXfblU9vpQpo9
-9FxA4ndYDUHrxm4LOqVKrFukTbxJ4Rm4E9UCxKxsUiEl+YMFDnyzbWRjzOm8AsUX
-8V78ZZzUkB/bR/BML3yNX2U12cts7L2yjsbwIFGxXopXUZe5PBeQcaTHLs9DVNwW
-YXS76ZZElSy0iJmjk6+1zJ2Zyg3LZFxWTpSLu+bayTI0DDeurJr1/Icf0vjCsnRP
-zMkQ0RTQ1NXwrQEEYpKXPyKS+g6EpI7udvNdCjcyyBI6UhQrl9Ml/EIc/at1GOjz
-KO7b8rAurfTu5Ub642LbsvDeLoYCQr3e6DO6IaWn9qg0NcNLVtOBOheHVVN4o8up
-eu1lVoECgYEA+oDN9jbDw1KfmP0bATomHRvJrs0DopHTr2Rsv46gmwZX0eBTn2OL
-mz6+s+lw66iZ6d7lW7IfBVLSvpxwPM3wP9GxIMC1IW2V0r/2z413nnXhDxcGduks
-L7HYO59/T+T+/hYlKq1POuQuSC+peaVvx/h5vzQXEoPaBt7nrmVQbIUCgYEA4K2v
-mo6K9i6pwr8hQk1eWSzgMKx6L4Y6Ip+LRJAGxkGOaAOoSSs7xNQkCqAmO5jFi5IA
-zUuj7n9wqec7KSTyceDvgH/nksOTc7rmcbcN1atiWcYGRkBlUfyIxPB9s94kT2Ax
-d5WCEyIx9tmNEBGLARf52qeboc15q5V8sB4CK50CgYEA64b9n9jf+R8PRfX8VQwK
-V+YPm9XQ7CJ2SPuAMB93LCsrmP51QXCEC6RUA66iaEBu7nCPb8aq2gOEeIl4EgOD
-N03+X5Aw/cweSTgKZB1b8dZdlCWPA3C7BeEOMQkG1+S0R8wkh9rJmtJ+HYsKqoB8
-+CTMu5TLeLIV5c3hQZyqoE0CgYEAqcEQ6InCXHatNMML8fhazK2nRZ9LQhT8X6SD
-qO3Y8Hofil8ZCw9rPS+7e3u851JqDyXlDTeVSGFpUjWu8UtFEt2Ml14MLUsvYUVZ
-T5mrDkFLMeD4ZKPK6cMP4xyBHSE99esL9Po1KRexAH0mo3lduRnQYXA5mmqQ/x8W
-8kn6AdECgYBFRElkbB/WcU2Iu8mGDfPliOIS/qY/VOL1cVybAHbnyGbs0dfbPG3S
-ihAP+4kEKT44zy84E/5eyvwYf0yMHQTn8xAkrev6c+cCB4BbKhwFSv0lRHfXIFt+
-REzDSMiV7H4P3pXKdtzPL7LZ6nFgdvk5s7b0u/Cpb+fQuPmSiIG2aQ==
+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
 -----END RSA PRIVATE KEY-----
diff --git a/test/ssl/test-signing-ca.crt b/test/ssl/test-signing-ca.crt
index fa138ef..396b7f5 100644
--- a/test/ssl/test-signing-ca.crt
+++ b/test/ssl/test-signing-ca.crt
@@ -5,75 +5,75 @@ Certificate:
         Signature Algorithm: sha256WithRSAEncryption
         Issuer: C=GB, ST=Derbyshire, L=Derby, O=Paho Project, OU=Testing, CN=Root CA
         Validity
-            Not Before: Jul 28 09:12:10 2020 GMT
-            Not After : Jul 27 09:12:10 2025 GMT
+            Not Before: Jul  7 11:14:42 2021 GMT
+            Not After : Jul  6 11:14:42 2026 GMT
         Subject: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA
         Subject Public Key Info:
             Public Key Algorithm: rsaEncryption
                 RSA Public-Key: (2048 bit)
                 Modulus:
-                    00:c2:33:17:27:8f:e6:b3:b3:96:d8:fc:cc:11:8d:
-                    5e:da:3d:a5:8e:3a:14:c7:e6:38:80:25:e6:27:c4:
-                    cf:6f:3c:b8:18:4a:6e:53:ee:81:dc:c6:e2:4a:29:
-                    18:d9:d3:d8:45:3f:19:2c:01:b5:61:ce:b9:f1:89:
-                    9b:0b:0c:af:f2:38:05:2e:cf:e4:57:c5:9f:4f:fc:
-                    42:a1:2f:cc:2a:59:fa:1a:4a:24:24:55:60:d3:25:
-                    d5:a7:0f:d8:5d:f3:9a:dc:d8:13:8c:37:ae:8d:27:
-                    bb:d2:2c:37:34:3e:11:16:02:46:03:b2:48:24:de:
-                    5e:d0:65:63:cd:0d:af:58:48:13:dc:93:d0:52:84:
-                    fa:05:69:4c:09:69:da:00:c2:af:8a:00:4c:1c:c2:
-                    1c:f5:8b:a9:f4:30:13:33:50:fd:6a:1e:8a:10:8d:
-                    cd:01:11:1b:cf:a3:30:80:4b:96:a3:b6:e1:ed:df:
-                    6e:69:5f:26:ed:75:9e:04:3f:49:cd:c9:6f:4a:05:
-                    6f:6d:45:09:09:a8:34:03:97:f2:77:ae:8e:96:7f:
-                    a8:52:7b:20:71:35:2d:11:17:52:cd:b1:b1:93:26:
-                    2f:9a:17:c1:48:3e:15:99:85:db:c6:bd:c3:35:0f:
-                    fd:d1:d9:2a:63:2b:96:59:35:93:c1:cf:5c:e1:72:
-                    3c:73
+                    00:cb:32:6c:8c:48:e8:44:58:36:18:70:36:42:3d:
+                    2d:29:47:3c:69:12:9e:7b:f7:45:62:ef:91:44:46:
+                    97:a0:ea:5f:da:fd:9f:98:d4:bf:43:02:e3:39:90:
+                    33:7b:13:13:d5:31:30:9c:07:fc:ca:1b:a9:e4:89:
+                    42:e5:d0:6e:f4:a2:e0:23:ee:9d:9a:cc:80:3b:78:
+                    bf:7e:27:a8:46:1b:28:9f:4a:64:53:7a:89:3e:ab:
+                    65:6f:af:0b:29:fa:4d:4f:04:f1:1e:10:2c:bf:2b:
+                    ea:fc:c5:fa:77:c9:1a:7a:78:29:f5:a2:cb:25:7c:
+                    02:bb:91:8d:76:4d:23:bc:9c:19:da:be:c5:20:04:
+                    ad:fe:bd:b9:d4:bb:29:2a:c3:e4:fc:4c:84:db:a3:
+                    55:9f:f0:70:7f:40:38:b5:c3:78:a5:db:06:36:b7:
+                    10:8e:ca:6c:1a:92:66:be:0e:1a:97:59:6b:18:f4:
+                    c2:b8:c9:31:7b:d1:b1:a1:00:78:7f:c0:09:f6:ef:
+                    b2:8f:94:87:5d:b1:a2:23:93:4d:ec:fa:95:09:a9:
+                    90:c4:02:f0:1e:d9:ab:a2:8b:7f:7f:54:95:e7:da:
+                    c3:c9:7d:a7:d7:04:89:59:db:88:9d:57:16:5d:b9:
+                    66:b0:d6:88:bb:e0:ee:43:e9:ab:02:78:fc:bd:e8:
+                    98:d9
                 Exponent: 65537 (0x10001)
         X509v3 extensions:
             X509v3 Subject Key Identifier: 
-                63:AD:89:C4:23:13:13:D1:EB:00:4D:01:0E:25:E3:5E:0C:9D:41:C9
+                C2:8F:09:9B:D5:F1:BA:C4:74:5E:97:50:BB:86:9D:A1:F1:FA:C4:36
             X509v3 Authority Key Identifier: 
-                keyid:C7:BD:C0:22:65:FF:A7:97:A7:CB:9D:7E:44:E5:13:77:39:7D:BE:BA
+                keyid:13:A0:B6:1F:F5:C7:64:C2:F9:FD:2E:08:F2:19:01:77:54:19:73:7F
 
             X509v3 Basic Constraints: 
                 CA:TRUE
     Signature Algorithm: sha256WithRSAEncryption
-         18:5c:f3:6a:e2:82:d1:ba:ee:de:00:29:4e:b3:f7:87:46:1b:
-         fa:7e:52:9d:87:d8:73:19:5b:38:7a:af:31:aa:4c:bd:fe:45:
-         95:25:03:48:15:d7:e5:38:4f:e3:39:93:99:10:b4:06:dc:5a:
-         87:af:22:b5:2f:82:da:ef:6c:b5:f3:9c:82:71:0f:f4:d0:65:
-         46:b7:23:b1:7d:51:8d:d7:7c:80:12:39:99:46:2b:d4:db:c7:
-         42:96:1a:b6:0f:b3:10:9e:ad:84:0e:87:38:e8:34:f8:04:3d:
-         a2:fc:3d:24:1e:09:d6:63:52:82:e4:35:ae:39:5e:f9:82:a7:
-         ac:23:87:0e:fb:2d:ae:08:da:2a:3f:51:ee:f6:85:3a:43:80:
-         b0:f1:96:0c:fa:10:80:18:7b:75:14:70:09:48:fc:0c:38:ee:
-         ab:e4:32:7d:80:77:a8:2a:63:15:c9:11:b7:a9:0c:77:7d:be:
-         d6:a3:48:37:1e:56:33:13:71:e1:12:90:ce:95:72:68:ae:d5:
-         01:82:00:67:1b:ae:9d:93:5b:5d:c1:3d:6e:30:e5:e1:35:ac:
-         59:3b:eb:ef:ee:83:0b:eb:e5:ea:9c:62:29:8c:73:a3:b5:45:
-         45:e8:53:ea:b8:48:16:7e:f8:c5:af:4b:89:b5:1b:94:a5:85:
-         d9:d5:f4:13
+         3e:70:76:69:37:e4:6e:e0:08:c6:8e:5b:2e:aa:26:fe:e9:ed:
+         ac:02:ce:2c:37:08:6a:8a:c3:0d:c0:ef:43:51:01:2e:e0:96:
+         76:23:1b:1f:75:98:df:7c:d1:b7:c1:67:aa:62:c1:bd:ef:84:
+         eb:d9:28:47:50:f2:1b:54:7f:ed:cb:52:f7:fc:c3:f8:62:22:
+         0c:b3:95:ed:bb:3f:74:91:bc:d2:eb:c0:81:7d:74:12:85:61:
+         a3:7e:fb:22:4a:25:99:0b:5d:ef:69:f2:5a:e6:d5:12:a3:95:
+         38:30:0c:c7:d9:da:28:30:10:b4:3d:3e:ad:20:85:31:e0:bf:
+         30:33:2e:0b:e3:07:3d:ed:22:dc:67:f8:93:64:89:ed:e7:08:
+         74:b5:0a:7a:01:3d:f9:44:62:71:cf:60:12:92:c3:95:9a:e5:
+         a5:f2:24:6a:22:64:d5:76:22:c9:03:1c:c5:d1:a5:85:4d:55:
+         f9:80:47:ca:12:20:df:05:fb:82:12:45:6f:e8:c0:20:a8:ae:
+         f7:17:c5:c3:b6:9c:51:bd:d8:84:e4:db:c7:03:44:d2:cb:75:
+         51:79:3f:86:33:3c:e4:34:1d:77:b2:60:24:5c:21:c5:c3:53:
+         36:08:2f:a7:14:0b:68:78:67:95:90:b9:06:0e:85:04:65:57:
+         b4:34:31:cf
 -----BEGIN CERTIFICATE-----
 MIIDmDCCAoCgAwIBAgIBATANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQGEwJHQjET
 MBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwFRGVyYnkxFTATBgNVBAoMDFBh
 aG8gUHJvamVjdDEQMA4GA1UECwwHVGVzdGluZzEQMA4GA1UEAwwHUm9vdCBDQTAe
-Fw0yMDA3MjgwOTEyMTBaFw0yNTA3MjcwOTEyMTBaMGAxCzAJBgNVBAYTAkdCMRMw
+Fw0yMTA3MDcxMTE0NDJaFw0yNjA3MDYxMTE0NDJaMGAxCzAJBgNVBAYTAkdCMRMw
 EQYDVQQIDApEZXJieXNoaXJlMRUwEwYDVQQKDAxQYWhvIFByb2plY3QxEDAOBgNV
 BAsMB1Rlc3RpbmcxEzARBgNVBAMMClNpZ25pbmcgQ0EwggEiMA0GCSqGSIb3DQEB
-AQUAA4IBDwAwggEKAoIBAQDCMxcnj+azs5bY/MwRjV7aPaWOOhTH5jiAJeYnxM9v
-PLgYSm5T7oHcxuJKKRjZ09hFPxksAbVhzrnxiZsLDK/yOAUuz+RXxZ9P/EKhL8wq
-WfoaSiQkVWDTJdWnD9hd85rc2BOMN66NJ7vSLDc0PhEWAkYDskgk3l7QZWPNDa9Y
-SBPck9BShPoFaUwJadoAwq+KAEwcwhz1i6n0MBMzUP1qHooQjc0BERvPozCAS5aj
-tuHt325pXybtdZ4EP0nNyW9KBW9tRQkJqDQDl/J3ro6Wf6hSeyBxNS0RF1LNsbGT
-Ji+aF8FIPhWZhdvGvcM1D/3R2SpjK5ZZNZPBz1zhcjxzAgMBAAGjUDBOMB0GA1Ud
-DgQWBBRjrYnEIxMT0esATQEOJeNeDJ1ByTAfBgNVHSMEGDAWgBTHvcAiZf+nl6fL
-nX5E5RN3OX2+ujAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAYXPNq
-4oLRuu7eAClOs/eHRhv6flKdh9hzGVs4eq8xqky9/kWVJQNIFdflOE/jOZOZELQG
-3FqHryK1L4La72y185yCcQ/00GVGtyOxfVGN13yAEjmZRivU28dClhq2D7MQnq2E
-Doc46DT4BD2i/D0kHgnWY1KC5DWuOV75gqesI4cO+y2uCNoqP1Hu9oU6Q4Cw8ZYM
-+hCAGHt1FHAJSPwMOO6r5DJ9gHeoKmMVyRG3qQx3fb7Wo0g3HlYzE3HhEpDOlXJo
-rtUBggBnG66dk1tdwT1uMOXhNaxZO+vv7oML6+XqnGIpjHOjtUVF6FPquEgWfvjF
-r0uJtRuUpYXZ1fQT
+AQUAA4IBDwAwggEKAoIBAQDLMmyMSOhEWDYYcDZCPS0pRzxpEp5790Vi75FERpeg
+6l/a/Z+Y1L9DAuM5kDN7ExPVMTCcB/zKG6nkiULl0G70ouAj7p2azIA7eL9+J6hG
+GyifSmRTeok+q2Vvrwsp+k1PBPEeECy/K+r8xfp3yRp6eCn1osslfAK7kY12TSO8
+nBnavsUgBK3+vbnUuykqw+T8TITbo1Wf8HB/QDi1w3il2wY2txCOymwakma+DhqX
+WWsY9MK4yTF70bGhAHh/wAn277KPlIddsaIjk03s+pUJqZDEAvAe2auii39/VJXn
+2sPJfafXBIlZ24idVxZduWaw1oi74O5D6asCePy96JjZAgMBAAGjUDBOMB0GA1Ud
+DgQWBBTCjwmb1fG6xHRel1C7hp2h8frENjAfBgNVHSMEGDAWgBQToLYf9cdkwvn9
+LgjyGQF3VBlzfzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA+cHZp
+N+Ru4AjGjlsuqib+6e2sAs4sNwhqisMNwO9DUQEu4JZ2IxsfdZjffNG3wWeqYsG9
+74Tr2ShHUPIbVH/ty1L3/MP4YiIMs5Xtuz90kbzS68CBfXQShWGjfvsiSiWZC13v
+afJa5tUSo5U4MAzH2dooMBC0PT6tIIUx4L8wMy4L4wc97SLcZ/iTZInt5wh0tQp6
+AT35RGJxz2ASksOVmuWl8iRqImTVdiLJAxzF0aWFTVX5gEfKEiDfBfuCEkVv6MAg
+qK73F8XDtpxRvdiE5NvHA0TSy3VReT+GMzzkNB13smAkXCHFw1M2CC+nFAtoeGeV
+kLkGDoUEZVe0NDHP
 -----END CERTIFICATE-----
diff --git a/test/ssl/test-signing-ca.csr b/test/ssl/test-signing-ca.csr
deleted file mode 100644
index 1ed9be8..0000000
--- a/test/ssl/test-signing-ca.csr
+++ /dev/null
@@ -1,17 +0,0 @@
------BEGIN CERTIFICATE REQUEST-----
-MIICtTCCAZ0CAQAwcDELMAkGA1UEBhMCR0IxEzARBgNVBAgMCkRlcmJ5c2hpcmUx
-DjAMBgNVBAcMBURlcmJ5MRUwEwYDVQQKDAxQYWhvIFByb2plY3QxEDAOBgNVBAsM
-B1Rlc3RpbmcxEzARBgNVBAMMClNpZ25pbmcgQ0EwggEiMA0GCSqGSIb3DQEBAQUA
-A4IBDwAwggEKAoIBAQDCMxcnj+azs5bY/MwRjV7aPaWOOhTH5jiAJeYnxM9vPLgY
-Sm5T7oHcxuJKKRjZ09hFPxksAbVhzrnxiZsLDK/yOAUuz+RXxZ9P/EKhL8wqWfoa
-SiQkVWDTJdWnD9hd85rc2BOMN66NJ7vSLDc0PhEWAkYDskgk3l7QZWPNDa9YSBPc
-k9BShPoFaUwJadoAwq+KAEwcwhz1i6n0MBMzUP1qHooQjc0BERvPozCAS5ajtuHt
-325pXybtdZ4EP0nNyW9KBW9tRQkJqDQDl/J3ro6Wf6hSeyBxNS0RF1LNsbGTJi+a
-F8FIPhWZhdvGvcM1D/3R2SpjK5ZZNZPBz1zhcjxzAgMBAAGgADANBgkqhkiG9w0B
-AQsFAAOCAQEAMU8s9pBSLFS5x/G4KO6bM8Ym9RBJJZatlJu/hg/Z2qYxcCjueGWr
-ureTKdsX+6Mir3upL7BMeBmazLW1Efnr3ZFpE3ZBIw1nW580V0uxjwninmSclax1
-WwPP68thxEExDDCy5jtjSX685s3cutLNhCHSxPkPVwfVAxJxjJ1XYrenrfWhgtUc
-T8GNWBamnUn8eJKiRvPy+kg93XX4ZgAkrtj/3Pyzfg19n8XZvAXkYx/KjMnpnPI4
-xXJMdvXJtzdKYnGuRRxJ2oq69xRoiYTaQRjF4DeU+3krfAWoV24xQI2nHEeXOs5g
-RUff1KcIXCu/nKD4rjCZ5iJofn6W4XZBTQ==
------END CERTIFICATE REQUEST-----
diff --git a/test/ssl/test-signing-ca.key b/test/ssl/test-signing-ca.key
index e10de01..5bb7838 100644
--- a/test/ssl/test-signing-ca.key
+++ b/test/ssl/test-signing-ca.key
@@ -1,27 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
-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
+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
 -----END RSA PRIVATE KEY-----
diff --git a/tests/test_client.py b/tests/test_client.py
index 7f424b8..688c948 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -1,10 +1,11 @@
+import inspect
 import os
 import sys
 import time
-import inspect
 import unicodedata
 
 import pytest
+
 import paho.mqtt.client as client
 
 # From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
diff --git a/tests/test_mqttv5.py b/tests/test_mqttv5.py
index 8e23144..bdc5664 100644
--- a/tests/test_mqttv5.py
+++ b/tests/test_mqttv5.py
@@ -3,7 +3,7 @@
   Copyright (c) 2013, 2019 IBM Corp.
 
   All rights reserved. This program and the accompanying materials
-  are made available under the terms of the Eclipse Public License v1.0
+  are made available under the terms of the Eclipse Public License v2.0
   and Eclipse Distribution License v1.0 which accompany this distribution.
 
   The Eclipse Public License is available at
@@ -16,20 +16,20 @@
 *******************************************************************
 """
 
-import unittest
-import time
-import threading
 import getopt
-import sys
 import logging
+import sys
+import threading
+import time
 import traceback
+import unittest
 
 import paho.mqtt
+import paho.mqtt.client
+from paho.mqtt.packettypes import PacketTypes
 from paho.mqtt.properties import Properties
 from paho.mqtt.reasoncodes import ReasonCodes
 from paho.mqtt.subscribeoptions import SubscribeOptions
-from paho.mqtt.packettypes import PacketTypes
-import paho.mqtt.client
 
 
 class Callbacks:
@@ -41,6 +41,7 @@ class Callbacks:
         self.unsubscribeds = []
         self.disconnecteds = []
         self.connecteds = []
+        self.conn_failures = []
 
     def __str__(self):
         return str(self.messages) + str(self.messagedicts) + str(self.publisheds) + \
@@ -54,6 +55,9 @@ class Callbacks:
         self.connecteds.append({"userdata": userdata, "flags": flags,
                                 "reasonCode": reasonCode, "properties": properties})
 
+    def on_connect_fail(self, client, userdata):
+        self.conn_failures.append({"userdata": userdata})
+
     def wait(self, alist, timeout=2):
         interval = 0.2
         total = 0
@@ -62,6 +66,9 @@ class Callbacks:
             total += interval
         return alist.pop(0)  # if len(alist) > 0 else None
 
+    def wait_connect_fail(self):
+        return self.wait(self.conn_failures, timeout=10)
+
     def wait_connected(self):
         return self.wait(self.connecteds)
 
@@ -105,6 +112,7 @@ class Callbacks:
         client.on_unsubscribe = self.unsubscribed
         client.on_message = self.on_message
         client.on_disconnect = self.on_disconnect
+        client.on_connect_fail = self.on_connect_fail
         client.on_log = self.on_log
 
 
@@ -224,6 +232,17 @@ class Test(unittest.TestCase):
         callback.clear()
         aclient.loop_stop()
 
+    def test_connect_fail(self):
+        clientid = "connection failure"
+
+        fclient, fcallback = self.new_client(clientid)
+
+        fclient.user_data_set(1)
+        fclient.connect_async("localhost", 1)
+        response = fcallback.wait_connect_fail()
+        self.assertEqual(response["userdata"], 1)
+        fclient.loop_stop()
+
     def test_retained_message(self):
         qos0topic = "fromb/qos 0"
         qos1topic = "fromb/qos 1"
diff --git a/tests/test_websocket_integration.py b/tests/test_websocket_integration.py
index 1fe0001..86388dc 100644
--- a/tests/test_websocket_integration.py
+++ b/tests/test_websocket_integration.py
@@ -1,14 +1,14 @@
 import base64
-import re
 import hashlib
+import re
 from collections import OrderedDict
 
-from six.moves import socketserver
 import pytest
-import paho.mqtt.client as client
+from six.moves import socketserver
+from testsupport.broker import fake_websocket_broker
 
+import paho.mqtt.client as client
 from paho.mqtt.client import WebsocketConnectionError
-from testsupport.broker import fake_websocket_broker
 
 
 @pytest.fixture
diff --git a/tests/test_websockets.py b/tests/test_websockets.py
index 8c814d8..6aa55f7 100644
--- a/tests/test_websockets.py
+++ b/tests/test_websockets.py
@@ -7,7 +7,8 @@ else:
     from unittest.mock import Mock
 
 import pytest
-from paho.mqtt.client import WebsocketWrapper, WebsocketConnectionError
+
+from paho.mqtt.client import WebsocketConnectionError, WebsocketWrapper
 
 
 class TestHeaders(object):
diff --git a/tests/testsupport/broker.py b/tests/testsupport/broker.py
index c8655dc..92b8045 100644
--- a/tests/testsupport/broker.py
+++ b/tests/testsupport/broker.py
@@ -1,9 +1,9 @@
+import contextlib
 import socket
-from six.moves import socketserver
 import threading
-import contextlib
 
 import pytest
+from six.moves import socketserver
 
 
 class FakeBroker:
diff --git a/tox.ini b/tox.ini
index ed14d7f..f5e3978 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py{27,35,36,37,38}
+envlist = py{27,35,36,37,38,39}
 
 [testenv:py27]
 setenv = EXCLUDE = --exclude=./.*,./examples/loop_asyncio.py,*/MQTTV5.py,*/MQTTV311.py