]> git.michaelhowe.org Git - packages/p/paho-mqtt.git/commitdiff
Initial contribution.
authorRoger Light <roger@atchoo.org>
Fri, 25 Jan 2013 10:31:19 +0000 (10:31 +0000)
committerRoger Light <roger@atchoo.org>
Mon, 3 Feb 2014 21:14:15 +0000 (21:14 +0000)
107 files changed:
.hgeol [new file with mode: 0644]
.hgignore [new file with mode: 0644]
LICENSE-3rd-party.txt [new file with mode: 0644]
LICENSE.txt [new file with mode: 0644]
Makefile [new file with mode: 0644]
lib/python/Makefile [new file with mode: 0644]
lib/python/mosquitto.py [new file with mode: 0755]
lib/python/setup.py [new file with mode: 0644]
lib/python/sub.py [new file with mode: 0755]
test/Makefile [new file with mode: 0644]
test/lib/01-con-discon-success.py [new file with mode: 0755]
test/lib/01-keepalive-pingreq.py [new file with mode: 0755]
test/lib/01-no-clean-session.py [new file with mode: 0755]
test/lib/01-unpwd-set.py [new file with mode: 0755]
test/lib/01-will-set.py [new file with mode: 0755]
test/lib/01-will-unpwd-set.py [new file with mode: 0755]
test/lib/02-subscribe-qos0.py [new file with mode: 0755]
test/lib/02-subscribe-qos1.py [new file with mode: 0755]
test/lib/02-subscribe-qos2.py [new file with mode: 0755]
test/lib/02-unsubscribe.py [new file with mode: 0755]
test/lib/03-publish-b2c-qos1.py [new file with mode: 0755]
test/lib/03-publish-b2c-qos2.py [new file with mode: 0755]
test/lib/03-publish-c2b-qos1-disconnect.py [new file with mode: 0755]
test/lib/03-publish-c2b-qos1-timeout.py [new file with mode: 0755]
test/lib/03-publish-c2b-qos2-disconnect.py [new file with mode: 0755]
test/lib/03-publish-c2b-qos2-timeout.py [new file with mode: 0755]
test/lib/03-publish-qos0-no-payload.py [new file with mode: 0755]
test/lib/03-publish-qos0.py [new file with mode: 0755]
test/lib/04-retain-qos0.py [new file with mode: 0755]
test/lib/08-ssl-bad-cacert.py [new file with mode: 0755]
test/lib/08-ssl-connect-cert-auth.py [new file with mode: 0755]
test/lib/08-ssl-connect-no-auth.py [new file with mode: 0755]
test/lib/09-util-topic-matching.py [new file with mode: 0755]
test/lib/Makefile [new file with mode: 0644]
test/lib/python/01-con-discon-success.test [new file with mode: 0755]
test/lib/python/01-keepalive-pingreq.test [new file with mode: 0755]
test/lib/python/01-no-clean-session.test [new file with mode: 0755]
test/lib/python/01-unpwd-set.test [new file with mode: 0755]
test/lib/python/01-will-set.test [new file with mode: 0755]
test/lib/python/01-will-unpwd-set.test [new file with mode: 0755]
test/lib/python/02-subscribe-qos0.test [new file with mode: 0755]
test/lib/python/02-subscribe-qos1.test [new file with mode: 0755]
test/lib/python/02-subscribe-qos2.test [new file with mode: 0755]
test/lib/python/02-unsubscribe.test [new file with mode: 0755]
test/lib/python/03-publish-b2c-qos1.test [new file with mode: 0755]
test/lib/python/03-publish-b2c-qos2.test [new file with mode: 0755]
test/lib/python/03-publish-c2b-qos1-disconnect.test [new file with mode: 0755]
test/lib/python/03-publish-c2b-qos1-timeout.test [new file with mode: 0755]
test/lib/python/03-publish-c2b-qos2-disconnect.test [new file with mode: 0755]
test/lib/python/03-publish-c2b-qos2-timeout.test [new file with mode: 0755]
test/lib/python/03-publish-qos0-no-payload.test [new file with mode: 0755]
test/lib/python/03-publish-qos0.test [new file with mode: 0755]
test/lib/python/04-retain-qos0.test [new file with mode: 0755]
test/lib/python/08-ssl-bad-cacert.test [new file with mode: 0755]
test/lib/python/08-ssl-connect-cert-auth.test [new file with mode: 0755]
test/lib/python/08-ssl-connect-no-auth.test [new file with mode: 0755]
test/lib/python/08-ssl-fake-cacert.test [new file with mode: 0755]
test/lib/python/09-util-topic-matching.test [new file with mode: 0755]
test/lib/python3/01-con-discon-success.test [new file with mode: 0755]
test/lib/python3/01-keepalive-pingreq.test [new file with mode: 0755]
test/lib/python3/01-no-clean-session.test [new file with mode: 0755]
test/lib/python3/01-unpwd-set.test [new file with mode: 0755]
test/lib/python3/01-will-set.test [new file with mode: 0755]
test/lib/python3/01-will-unpwd-set.test [new file with mode: 0755]
test/lib/python3/02-subscribe-qos0.test [new file with mode: 0755]
test/lib/python3/02-subscribe-qos1.test [new file with mode: 0755]
test/lib/python3/02-subscribe-qos2.test [new file with mode: 0755]
test/lib/python3/02-unsubscribe.test [new file with mode: 0755]
test/lib/python3/03-publish-b2c-qos1.test [new file with mode: 0755]
test/lib/python3/03-publish-b2c-qos2.test [new file with mode: 0755]
test/lib/python3/03-publish-c2b-qos1-disconnect.test [new file with mode: 0755]
test/lib/python3/03-publish-c2b-qos1-timeout.test [new file with mode: 0755]
test/lib/python3/03-publish-c2b-qos2-disconnect.test [new file with mode: 0755]
test/lib/python3/03-publish-c2b-qos2-timeout.test [new file with mode: 0755]
test/lib/python3/03-publish-qos0-no-payload.test [new file with mode: 0755]
test/lib/python3/03-publish-qos0.test [new file with mode: 0755]
test/lib/python3/04-retain-qos0.test [new file with mode: 0755]
test/lib/python3/08-ssl-bad-cacert.test [new file with mode: 0755]
test/lib/python3/08-ssl-connect-cert-auth.test [new file with mode: 0755]
test/lib/python3/08-ssl-connect-no-auth.test [new file with mode: 0755]
test/lib/python3/08-ssl-fake-cacert.test [new file with mode: 0755]
test/lib/python3/09-util-topic-matching.test [new file with mode: 0755]
test/mosq_test.py [new file with mode: 0644]
test/ssl/client-expired.crt [new file with mode: 0644]
test/ssl/client-revoked.crt [new file with mode: 0644]
test/ssl/client-revoked.csr [new file with mode: 0644]
test/ssl/client-revoked.key [new file with mode: 0644]
test/ssl/client.crt [new file with mode: 0644]
test/ssl/client.csr [new file with mode: 0644]
test/ssl/client.key [new file with mode: 0644]
test/ssl/crl.pem [new file with mode: 0644]
test/ssl/demoCA/crlnumber [new file with mode: 0644]
test/ssl/demoCA/index.txt [new file with mode: 0644]
test/ssl/demoCA/index.txt.attr [new file with mode: 0644]
test/ssl/demoCA/serial [new file with mode: 0644]
test/ssl/fake-ca.crt [new file with mode: 0644]
test/ssl/fake-ca.key [new file with mode: 0644]
test/ssl/readme.txt [new file with mode: 0644]
test/ssl/server-expired.crt [new file with mode: 0644]
test/ssl/server.crt [new file with mode: 0644]
test/ssl/server.csr [new file with mode: 0644]
test/ssl/server.key [new file with mode: 0644]
test/ssl/test-ca-alt.crt [new file with mode: 0644]
test/ssl/test-ca-alt.key [new file with mode: 0644]
test/ssl/test-ca.crt [new file with mode: 0644]
test/ssl/test-ca.key [new file with mode: 0644]
test/ssl/test-ca.srl [new file with mode: 0644]

diff --git a/.hgeol b/.hgeol
new file mode 100644 (file)
index 0000000..0ecd034
--- /dev/null
+++ b/.hgeol
@@ -0,0 +1,14 @@
+[patterns]
+**.c = native
+**.h = native
+**.pl = native
+**.py = native
+**.txt = native
+**.svg = native
+**.png = BIN
+**.xml = native
+**.nsi = native
+Makefile = LF
+config.mk = LF
+mosquitto.conf = native
+
diff --git a/.hgignore b/.hgignore
new file mode 100644 (file)
index 0000000..50c84b9
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,39 @@
+syntax: glob
+
+*.o
+*.exe
+*.db
+c/*.test
+cpp/*.test
+*.pyc
+
+client/mosquitto_pub
+client/mosquitto_sub
+examples/mysql_log/mosquitto_mysql_log
+examples/temperature_conversion/mqtt_temperature_conversion
+man/mosquitto.8
+man/mosquitto-tls.7
+man/mosquitto.conf.5
+man/libmosquitto.3
+man/mosquitto_passwd.1
+man/mosquitto_pub.1
+man/mosquitto_sub.1
+man/mqtt.7
+src/db_dump/mosquitto_db_dump
+src/mosquitto
+src/mosquitto_passwd
+test/broker/broker.pid
+test/test_client
+test/fake_user
+test/msgsps_pub
+test/msgsps_sub
+test/msgsps_pub.dat
+test/msgsps_sub.dat
+test/broker/c/auth_plugin.so
+
+lib/cpp/libmosquittopp.so*
+lib/libmosquitto.so*
+lib/libmosquitto.a
+
+build/
+dist/
diff --git a/LICENSE-3rd-party.txt b/LICENSE-3rd-party.txt
new file mode 100644 (file)
index 0000000..87cb45a
--- /dev/null
@@ -0,0 +1,666 @@
+============================================================================
+uthash.h license
+============================================================================
+
+Copyright (c) 2005-2010, Troy D. Hanson    http://uthash.sourceforge.net
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) 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 OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
+============================================================================
+OpenSSL license
+============================================================================
+
+  LICENSE ISSUES
+  ==============
+
+  The OpenSSL toolkit stays under a dual license, i.e. both the conditions of
+  the OpenSSL License and the original SSLeay license apply to the toolkit.
+  See below for the actual license texts. Actually both licenses are BSD-style
+  Open Source licenses. In case of any license issues related to OpenSSL
+  please contact openssl-core@openssl.org.
+
+  OpenSSL License
+  ---------------
+
+/* ====================================================================
+ * Copyright (c) 1998-2011 The OpenSSL Project.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer. 
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. All advertising materials mentioning features or use of this
+ *    software must display the following acknowledgment:
+ *    "This product includes software developed by the OpenSSL Project
+ *    for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+ *
+ * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please contact
+ *    openssl-core@openssl.org.
+ *
+ * 5. Products derived from this software may not be called "OpenSSL"
+ *    nor may "OpenSSL" appear in their names without prior written
+ *    permission of the OpenSSL Project.
+ *
+ * 6. Redistributions of any form whatsoever must retain the following
+ *    acknowledgment:
+ *    "This product includes software developed by the OpenSSL Project
+ *    for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+ * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OpenSSL PROJECT OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * 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 OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This product includes cryptographic software written by Eric Young
+ * (eay@cryptsoft.com).  This product includes software written by Tim
+ * Hudson (tjh@cryptsoft.com).
+ *
+ */
+
+ Original SSLeay License
+ -----------------------
+
+/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+ * All rights reserved.
+ *
+ * This package is an SSL implementation written
+ * by Eric Young (eay@cryptsoft.com).
+ * The implementation was written so as to conform with Netscapes SSL.
+ * 
+ * This library is free for commercial and non-commercial use as long as
+ * the following conditions are aheared to.  The following conditions
+ * apply to all code found in this distribution, be it the RC4, RSA,
+ * lhash, DES, etc., code; not just the SSL code.  The SSL documentation
+ * included with this distribution is covered by the same copyright terms
+ * except that the holder is Tim Hudson (tjh@cryptsoft.com).
+ * 
+ * Copyright remains Eric Young's, and as such any Copyright notices in
+ * the code are not to be removed.
+ * If this package is used in a product, Eric Young should be given attribution
+ * as the author of the parts of the library used.
+ * This can be in the form of a textual message at program startup or
+ * in documentation (online or textual) provided with the package.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *    "This product includes cryptographic software written by
+ *     Eric Young (eay@cryptsoft.com)"
+ *    The word 'cryptographic' can be left out if the rouines from the library
+ *    being used are not cryptographic related :-).
+ * 4. If you include any Windows specific code (or a derivative thereof) from 
+ *    the apps directory (application code) you must include an acknowledgement:
+ *    "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
+ * 
+ * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * 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 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * 
+ * The licence and distribution terms for any publically available version or
+ * derivative of this code cannot be changed.  i.e. this code cannot simply be
+ * copied and put under another distribution licence
+ * [including the GNU Public Licence.]
+ */
+
+
+
+============================================================================
+pthreads-win32 license
+============================================================================
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+\f
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+\f
+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+\f
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+\f
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+\f
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+\f
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+\f
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+\f
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644 (file)
index 0000000..cc09f28
--- /dev/null
@@ -0,0 +1,54 @@
+Copyright (c) 2009-2012 Roger Light <roger@atchoo.org>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. Neither the name of mosquitto nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) 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 OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+
+
+
+
+uthash.h license:
+
+Copyright (c) 2005-2010, Troy D. Hanson    http://uthash.sourceforge.net
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) 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 OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..008dd73
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,69 @@
+include config.mk
+
+DIRS=lib client src
+DOCDIRS=man
+DISTDIRS=man
+
+.PHONY : all mosquitto docs binary clean reallyclean test install uninstall dist sign copy
+
+all : mosquitto docs
+
+docs :
+       for d in ${DOCDIRS}; do $(MAKE) -C $${d}; done
+
+binary : mosquitto
+
+mosquitto :
+       for d in ${DIRS}; do $(MAKE) -C $${d}; done
+
+clean :
+       for d in ${DIRS}; do $(MAKE) -C $${d} clean; done
+       for d in ${DOCDIRS}; do $(MAKE) -C $${d} clean; done
+       $(MAKE) -C test clean
+
+reallyclean : 
+       for d in ${DIRS}; do $(MAKE) -C $${d} reallyclean; done
+       for d in ${DOCDIRS}; do $(MAKE) -C $${d} reallyclean; done
+       $(MAKE) -C test reallyclean
+       -rm -f *.orig
+
+test : mosquitto
+       $(MAKE) -C test test
+
+install : mosquitto
+       @for d in ${DIRS}; do $(MAKE) -C $${d} install; done
+       @for d in ${DOCDIRS}; do $(MAKE) -C $${d} install; done
+       $(INSTALL) -d ${DESTDIR}/etc/mosquitto
+       $(INSTALL) -m 644 mosquitto.conf ${DESTDIR}/etc/mosquitto/mosquitto.conf.example
+       $(INSTALL) -m 644 aclfile.example ${DESTDIR}/etc/mosquitto/aclfile.example
+       $(INSTALL) -m 644 pwfile.example ${DESTDIR}/etc/mosquitto/pwfile.example
+       $(INSTALL) -m 644 pskfile.example ${DESTDIR}/etc/mosquitto/pskfile.example
+
+uninstall :
+       @for d in ${DIRS}; do $(MAKE) -C $${d} uninstall; done
+       rm -f ${DESTDIR}/etc/mosquitto/mosquitto.conf
+       rm -f ${DESTDIR}/etc/mosquitto/aclfile.example
+       rm -f ${DESTDIR}/etc/mosquitto/pwfile.example
+       rm -f ${DESTDIR}/etc/mosquitto/pskfile.example
+
+dist : reallyclean
+       @for d in ${DISTDIRS}; do $(MAKE) -C $${d} dist; done
+       
+       mkdir -p dist/mosquitto-${VERSION}
+       cp -r client examples installer lib logo man misc security service src test ChangeLog.txt CMakeLists.txt LICENSE.txt LICENSE-3rd-party.txt Makefile compiling.txt config.h config.mk readme.txt readme-windows.txt mosquitto.conf aclfile.example pskfile.example pwfile.example dist/mosquitto-${VERSION}/
+       cd dist; tar -zcf mosquitto-${VERSION}.tar.gz mosquitto-${VERSION}/
+       for m in man/*.xml; \
+               do \
+               hfile=$$(echo $${m} | sed -e 's#man/\(.*\)\.xml#\1#' | sed -e 's/\./-/g'); \
+               $(XSLTPROC) $(DB_HTML_XSL) $${m} > dist/$${hfile}.html; \
+       done
+
+
+sign : dist
+       cd dist; gpg --detach-sign -a mosquitto-${VERSION}.tar.gz
+
+copy : sign
+       cd dist; scp mosquitto-${VERSION}.tar.gz mosquitto-${VERSION}.tar.gz.asc mosquitto:site/mosquitto.org/files/source/
+       cd dist; scp *.html mosquitto:site/mosquitto.org/man/
+       scp ChangeLog.txt mosquitto:site/mosquitto.org/
+
diff --git a/lib/python/Makefile b/lib/python/Makefile
new file mode 100644 (file)
index 0000000..a111355
--- /dev/null
@@ -0,0 +1,17 @@
+include ../../config.mk
+
+# Set DESTDIR if it isn't given
+DESTDIR?=/
+
+.PHONY : all clean install
+
+all : mosquitto.pyc
+
+install : all
+       python ./setup.py install --prefix=${prefix} --root=${DESTDIR}
+
+mosquitto.pyc : mosquitto.py
+       python ./setup.py build
+
+clean :
+       -rm -rf build mosquitto.pyc __pycache__
diff --git a/lib/python/mosquitto.py b/lib/python/mosquitto.py
new file mode 100755 (executable)
index 0000000..0a68446
--- /dev/null
@@ -0,0 +1,1767 @@
+# Copyright (c) 2012 Roger Light <roger@atchoo.org>
+# All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+# 
+# 1. Redistributions of source code must retain the above copyright notice,
+#    this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. Neither the name of mosquitto nor the names of its
+#    contributors may be used to endorse or promote products derived from
+#    this software without specific prior written permission.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) 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 OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+"""
+This is an MQTT v3.1 client module. MQTT is a lightweight pub/sub messaging
+protocol that is easy to implement and suitable for low powered devices.
+"""
+import errno
+import random
+import select
+import socket
+import ssl
+import struct
+import sys
+import threading
+import time
+
+if sys.version_info[0] < 3:
+    PROTOCOL_NAME = "MQIsdp"
+else:
+    PROTOCOL_NAME = b"MQIsdp"
+
+PROTOCOL_VERSION = 3
+
+# Message types 
+CONNECT = 0x10
+CONNACK = 0x20
+PUBLISH = 0x30
+PUBACK = 0x40
+PUBREC = 0x50
+PUBREL = 0x60
+PUBCOMP = 0x70
+SUBSCRIBE = 0x80
+SUBACK = 0x90
+UNSUBSCRIBE = 0xA0
+UNSUBACK = 0xB0
+PINGREQ = 0xC0
+PINGRESP = 0xD0
+DISCONNECT = 0xE0
+
+# Log levels
+MOSQ_LOG_INFO = 0x01
+MOSQ_LOG_NOTICE = 0x02
+MOSQ_LOG_WARNING = 0x04
+MOSQ_LOG_ERR = 0x08
+MOSQ_LOG_DEBUG = 0x10
+
+# CONNACK codes
+CONNACK_ACCEPTED = 0
+CONNACK_REFUSED_PROTOCOL_VERSION = 1
+CONNACK_REFUSED_IDENTIFIER_REJECTED = 2
+CONNACK_REFUSED_SERVER_UNAVAILABLE = 3
+CONNACK_REFUSED_BAD_USERNAME_PASSWORD = 4
+CONNACK_REFUSED_NOT_AUTHORIZED = 5
+
+# Connection state
+mosq_cs_new = 0
+mosq_cs_connected = 1
+mosq_cs_disconnecting = 2
+mosq_cs_connect_async = 3
+
+# Message direction
+mosq_md_invalid = 0
+mosq_md_in = 1
+mosq_md_out = 2
+
+# Message state
+mosq_ms_invalid = 0,
+mosq_ms_wait_puback = 1
+mosq_ms_wait_pubrec = 2
+mosq_ms_wait_pubrel = 3
+mosq_ms_wait_pubcomp = 4
+
+# Error values
+MOSQ_ERR_AGAIN = -1
+MOSQ_ERR_SUCCESS = 0
+MOSQ_ERR_NOMEM = 1
+MOSQ_ERR_PROTOCOL = 2
+MOSQ_ERR_INVAL = 3
+MOSQ_ERR_NO_CONN = 4
+MOSQ_ERR_CONN_REFUSED = 5
+MOSQ_ERR_NOT_FOUND = 6
+MOSQ_ERR_CONN_LOST = 7
+MOSQ_ERR_TLS = 8
+MOSQ_ERR_PAYLOAD_SIZE = 9
+MOSQ_ERR_NOT_SUPPORTED = 10
+MOSQ_ERR_AUTH = 11
+MOSQ_ERR_ACL_DENIED = 12
+MOSQ_ERR_UNKNOWN = 13
+MOSQ_ERR_ERRNO = 14
+
+def _fix_sub_topic(subtopic):
+    # Convert ////some////over/slashed///topic/etc/etc//
+    # into some/over/slashed/topic/etc/etc
+    if subtopic[0] == '/':
+        return '/'+'/'.join(filter(None, subtopic.split('/')))
+    else:
+        return '/'.join(filter(None, subtopic.split('/')))
+
+def error_string(mosq_errno):
+    """Return the error string associated with a mosquitto error number."""
+    if mosq_errno == MOSQ_ERR_SUCCESS:
+        return "No error."
+    elif mosq_errno == MOSQ_ERR_NOMEM:
+        return "Out of memory."
+    elif mosq_errno == MOSQ_ERR_PROTOCOL:
+        return "A network protocol error occurred when communicating with the broker."
+    elif mosq_errno == MOSQ_ERR_INVAL:
+        return "Invalid function arguments provided."
+    elif mosq_errno == MOSQ_ERR_NO_CONN:
+        return "The client is not currently connected."
+    elif mosq_errno == MOSQ_ERR_CONN_REFUSED:
+        return "The connection was refused."
+    elif mosq_errno == MOSQ_ERR_NOT_FOUND:
+        return "Message not found (internal error)."
+    elif mosq_errno == MOSQ_ERR_CONN_LOST:
+        return "The connection was lost."
+    elif mosq_errno == MOSQ_ERR_TLS:
+        return "A TLS error occurred."
+    elif mosq_errno == MOSQ_ERR_PAYLOAD_SIZE:
+        return "Payload too large."
+    elif mosq_errno == MOSQ_ERR_NOT_SUPPORTED:
+        return "This feature is not supported."
+    elif mosq_errno == MOSQ_ERR_AUTH:
+        return "Authorisation failed."
+    elif mosq_errno == MOSQ_ERR_ACL_DENIED:
+        return "Access denied by ACL."
+    elif mosq_errno == MOSQ_ERR_UNKNOWN:
+        return "Unknown error."
+    elif mosq_errno == MOSQ_ERR_ERRNO:
+        return "Error defined by errno."
+    else:
+        return "Unknown error."
+
+def connack_string(connack_code):
+    """Return the string associated with a CONNACK result."""
+    if connack_code == 0:
+        return "Connection Accepted."
+    elif connack_code == 1:
+        return "Connection Refused: unacceptable protocol version."
+    elif connack_code == 2:
+        return "Connection Refused: identifier rejected."
+    elif connack_code == 3:
+        return "Connection Refused: broker unavailable."
+    elif connack_code == 4:
+        return "Connection Refused: bad user name or password."
+    elif connack_code == 5:
+        return "Connection Refused: not authorised."
+    else:
+        return "Connection Refused: unknown reason."
+
+def topic_matches_sub(sub, topic):
+    """Check whether a topic matches a subscription.
+    
+    For example:
+    
+    foo/bar would match the subscription foo/# or +/bar
+    non/matching would not match the subscription non/+/+
+    """
+    result = True
+    local_sub = _fix_sub_topic(sub)
+    local_topic = _fix_sub_topic(topic)
+    multilevel_wildcard = False
+
+    slen = len(local_sub)
+    tlen = len(local_topic)
+
+    spos = 0;
+    tpos = 0;
+
+    while spos < slen and tpos < tlen:
+        if local_sub[spos] == local_topic[tpos]:
+            spos += 1
+            tpos += 1
+        else:
+            if local_sub[spos] == '+':
+                spos += 1
+                while tpos < tlen and local_topic[tpos] != '/':
+                    tpos += 1
+                if tpos == tlen and spos == slen:
+                    result = True
+                    break
+
+            elif local_sub[spos] == '#':
+                multilevel_wildcard = True
+                if spos+1 != slen:
+                    result = False
+                    break
+                else:
+                    result = True
+                    break
+
+            else:
+                result = False
+                break
+
+        if tpos == tlen-1:
+            # Check for e.g. foo matching foo/#
+            if spos == slen-3 and local_sub[spos+1] == '/' and local_sub[spos+2] == '#':
+                result = True
+                multilevel_wildcard = True
+                break
+
+    if multilevel_wildcard == False and (tpos < tlen or spos < slen):
+        result = False
+
+    return result
+
+
+class MosquittoMessage:
+    """ This is a class that describes an incoming message. It is passed to the
+    on_message callback as the message parameter.
+    
+    Members:
+
+    topic : String. topic that the message was published on.
+    payload : String/bytes 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.
+    """
+    def __init__(self):
+        self.timestamp = 0
+        self.direction = mosq_md_invalid
+        self.state = mosq_ms_invalid
+        self.dup = False
+        self.mid = 0
+        self.topic = ""
+        self.payload = None
+        self.qos = 0
+        self.retain = False
+
+class MosquittoInPacket:
+    """Internal datatype."""
+    def __init__(self):
+        self.command = 0
+        self.have_remaining = 0
+        self.remaining_count = []
+        self.remaining_mult = 1
+        self.remaining_length = 0
+        self.packet = b""
+        self.to_process = 0
+        self.pos = 0
+
+    def cleanup(self):
+        self.__init__()
+
+class MosquittoPacket:
+    """Internal datatype."""
+    def __init__(self, command, packet, mid, qos):
+        self.command = command
+        self.mid = mid
+        self.qos = qos
+        self.pos = 0
+        self.to_process = len(packet)
+        self.packet = packet
+
+class Mosquitto:
+    """MQTT version 3.1 client class.
+    
+    This is the main class for use communicating with an MQTT broker.
+
+    General usage flow:
+
+    * Use connect()/connect_async() to connect to a broker
+    * Call loop() frequently to maintain network traffic flow with the broker
+    * Or use loop_start() to set a thread running to call loop() for you.
+    * Or use loop_forever() to handle calling loop() for you in a blocking
+    * function.
+    * Use subscribe() to subscribe to a topic and receive messages
+    * Use publish() to send messages
+    * Use disconnect() to disconnect from the broker
+
+    Data returned from the broker is made available with the use of callback
+    functions as described below.
+
+    Callbacks
+    =========
+
+    A number of callback functions are available to receive data back from the
+    broker. To use a callback, define a function and then assign it to the
+    client:
+    
+    def on_connect(mosq, userdata, rc):
+        print("Connection returned " + str(rc))
+
+    client.on_connect = on_connect
+
+    All of the callbacks as described below have a "mosq" and an "userdata"
+    argument. "mosq" is the Mosquitto instance that is calling the callback.
+    "userdata" is user data of any type and can be set when creating a new client
+    instance or with user_data_set(userdata).
+    
+    The callbacks:
+
+    on_connect(mosq, userdata, rc): called when the broker responds to our connection
+      request. 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(mosq, userdata, rc): called when the client disconnects from the broker.
+      The rc parameter indicates the disconnection state. If MOSQ_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_message(mosq, userdata, message): called when a message has been received on a
+      topic that the client subscribes to. The message variable is a
+      MosquittoMessage that describes all of the message parameters.
+
+    on_publish(mosq, 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(mosq, userdata, mid, granted_qos): 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(mosq, 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(mosq, 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 MOSQ_LOG_INFO, MOSQ_LOG_NOTICE, MOSQ_LOG_WARNING,
+      MOSQ_LOG_ERR, and MOSQ_LOG_DEBUG. The message itself is in buf.
+
+    """
+    def __init__(self, client_id="", clean_session=True, userdata=None):
+        """client_id is the unique client id string used when connecting to the
+        broker. If client_id is zero length or None, then one will be randomly
+        generated. In this case, clean_session must be True. If this is not the
+        case a ValueError will be raised.
+
+        clean_session is a boolean that determines the client type. If True,
+        the broker will remove all information about this client when it
+        disconnects. If False, the client is a persistent client and
+        subscription information and queued messages will be retained when the
+        client disconnects. 
+        Note that a client will never discard its own outgoing messages on
+        disconnect. Calling connect() or reconnect() will cause the messages to
+        be resent.  Use reinitialise() to reset a client to its original state.
+
+        userdata is user defined data of any type that is passed as the "userdata"
+        parameter to callbacks. It may be updated at a later point with the
+        user_data_set() function.
+        """
+        if clean_session == False and (client_id == "" or client_id == None):
+            raise ValueError('A client id must be provided if clean session is False.')
+
+        self._userdata = userdata
+        self._sock = None
+        self._keepalive = 60
+        self._message_retry = 20
+        self._last_retry_check = 0
+        self._clean_session = clean_session
+        if client_id == "":
+            self._client_id = "mosq/" + "".join(random.choice("0123456789ADCDEF") for x in range(23-5))
+        else:
+            self._client_id = client_id
+
+        self._username = ""
+        self._password = ""
+        self._in_packet = MosquittoInPacket()
+        self._out_packet = []
+        self._current_out_packet = None
+        self._last_msg_in = time.time()
+        self._last_msg_out = time.time()
+        self._ping_t = 0
+        self._last_mid = 0
+        self._state = mosq_cs_new
+        self._messages = []
+        self._will = False
+        self._will_topic = ""
+        self._will_payload = None
+        self._will_qos = 0
+        self._will_retain = False
+        self.on_disconnect = None
+        self.on_connect = None
+        self.on_publish = None
+        self.on_message = None
+        self.on_subscribe = None
+        self.on_unsubscribe = None
+        self.on_log = None
+        self._host = ""
+        self._port = 1883
+        self._in_callback = False
+        self._strict_protocol = False
+        self._callback_mutex = threading.Lock()
+        self._state_mutex = threading.Lock()
+        self._out_packet_mutex = threading.Lock()
+        self._current_out_packet_mutex = threading.Lock()
+        self._msgtime_mutex = threading.Lock()
+        self._thread = None
+        self._thread_terminate = False
+        self._ssl = None
+        self._tls_certfile = None
+        self._tls_keyfile = None
+        self._tls_ca_certs = None
+        self._tls_cert_reqs = None
+        self._tls_ciphers = None
+
+    def __del__(self):
+        pass
+
+    def reinitialise(self, client_id="", clean_session=True, userdata=None):
+        if self._ssl:
+            self._ssl.close()
+            self._ssl = None
+            self._sock = None
+        elif self._sock:
+            self._sock.close()
+            self._sock = None
+        self.__init__(client_id, clean_session, userdata)
+
+    def tls_set(self, ca_certs, certfile=None, keyfile=None, cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1, ciphers=None):
+        """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.
+
+        certfile and keyfile are 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.
+        
+        cert_reqs allows the certificate requirements that the client imposes
+        on the broker to be changed. 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 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.
+
+        ciphers is a string specifying which encryption ciphers are allowable
+        for this connection, or None to use the defaults. See the ssl pydoc for
+        more information.
+
+        Must be called before connect() or connect_async()."""
+        if sys.version < '2.7':
+            raise ValueError('Python 2.7 is the minimum supported version for TLS.')
+
+        if ca_certs == None:
+            raise ValueError('ca_certs must not be None.')
+
+        try:
+            f = open(ca_certs, "r")
+        except IOError as err:
+            raise IOError(ca_certs+": "+err.strerror)
+        else:
+            f.close()
+        if certfile != None:
+            try:
+                f = open(certfile, "r")
+            except IOError as err:
+                raise IOError(certfile+": "+err.strerror)
+            else:
+                f.close()
+        if keyfile != None:
+            try:
+                f = open(keyfile, "r")
+            except IOError as err:
+                raise IOError(keyfile+": "+err.strerror)
+            else:
+                f.close()
+
+        self._tls_ca_certs = ca_certs
+        self._tls_certfile = certfile
+        self._tls_keyfile = keyfile
+        self._tls_cert_reqs = cert_reqs
+        self._tls_version = tls_version
+        self._tls_ciphers = ciphers
+
+    def connect(self, host, port=1883, keepalive=60):
+        """Connect to a remote broker.
+
+        host is the hostname or IP address of the remote broker.
+        port is the network port of the server host to connect to. Defaults to
+        1883. Note that the default port for MQTT over SSL/TLS is 8883 so if you
+        are using tls_set() the port may need providing.
+        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.
+        """
+        self.connect_async(host, port, keepalive)
+        return self.reconnect()
+
+    def connect_async(self, host, port=1883, keepalive=60):
+        """Connect to a remote broker asynchronously. This is a non-blocking
+        connect call that can be used with loop_start() to provide very quick
+        start.
+
+        host is the hostname or IP address of the remote broker.
+        port is the network port of the server host to connect to. Defaults to
+        1883. Note that the default port for MQTT over SSL/TLS is 8883 so if you
+        are using tls_set() the port may need providing.
+        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.
+        """
+        if host == None or len(host) == 0:
+            raise ValueError('Invalid host.')
+        if port <= 0:
+            raise ValueError('Invalid port number.')
+        if keepalive < 0:
+            raise ValueError('Keepalive must be >=0.')
+
+        self._host = host
+        self._port = port
+        self._keepalive = keepalive
+
+        self._state_mutex.acquire()
+        self._state = mosq_cs_connect_async
+        self._state_mutex.release()
+
+    def reconnect(self):
+        """Reconnect the client after a disconnect. Can only be called after
+        connect()/connect_async()."""
+        if len(self._host) == 0:
+            raise ValueError('Invalid host.')
+        if self._port <= 0:
+            raise ValueError('Invalid port number.')
+
+        self._in_packet.cleanup()
+        self._out_packet_mutex.acquire()
+        self._out_packet = []
+        self._out_packet_mutex.release()
+
+        self._current_out_packet_mutex.acquire()
+        self._current_out_packet = None
+        self._current_out_packet_mutex.release()
+
+        self._msgtime_mutex.acquire()
+        self._last_msg_in = time.time()
+        self._last_msg_out = time.time()
+        self._msgtime_mutex.release()
+
+        self._ping_t = 0
+        self._state_mutex.acquire()
+        self._state = mosq_cs_new
+        self._state_mutex.release()
+        if self._ssl:
+            self._ssl.close()
+            self._ssl = None
+            self._sock = None
+        elif self._sock:
+            self._sock.close()
+            self._sock = None
+
+        # Put messages in progress in a valid state.
+        self._messages_reconnect_reset()
+
+        try:
+            self._sock = socket.create_connection((self._host, self._port))
+        except socket.error as err:
+            (msg) = err
+            if msg.errno != errno.EINPROGRESS:
+                raise
+
+        if self._tls_ca_certs != None:
+            self._ssl = ssl.wrap_socket(self._sock,
+                    certfile=self._tls_certfile,
+                    keyfile=self._tls_keyfile,
+                    ca_certs=self._tls_ca_certs,
+                    cert_reqs=self._tls_cert_reqs,
+                    ssl_version=self._tls_version,
+                    ciphers=self._tls_ciphers)
+
+        self._sock.setblocking(0)
+
+        return self._send_connect(self._keepalive, self._clean_session)
+
+    def loop(self, timeout=1.0, max_packets=1):
+        """Process network events.
+
+        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
+        processed. Outgoing commands, from e.g. publish(), are normally sent
+        immediately that their function is called, but this is not always
+        possible. loop() will also attempt to send any remaining outgoing
+        messages, which also includes commands that are part of the flow for
+        messages with QoS>0.
+
+        timeout: The time in seconds to wait for incoming/outgoing network
+          traffic before timing out and returning. 
+        max_packets: Not currently used.
+
+        Returns MOSQ_ERR_SUCCESS on success.
+        Returns >0 on error.
+
+        A ValueError will be raised if timeout < 0"""
+        if timeout < 0.0:
+            raise ValueError('Invalid timeout.')
+
+        self._current_out_packet_mutex.acquire()
+        self._out_packet_mutex.acquire()
+        if self._current_out_packet == None and len(self._out_packet) > 0:
+            self._current_out_packet = self._out_packet.pop(0)
+
+        if self._current_out_packet:
+            wlist = [self.socket()]
+        else:
+            wlist = []
+        self._out_packet_mutex.release()
+        self._current_out_packet_mutex.release()
+
+        rlist = [self.socket()]
+        try:
+            socklist = select.select(rlist, wlist, [], timeout)
+        except TypeError:
+            # Socket isn't correct type, in likelihood connection is lost
+            return MOSQ_ERR_CONN_LOST
+
+        if self.socket() in socklist[0]:
+            rc = self.loop_read(max_packets)
+            if rc or (self._ssl == None and self._sock == None):
+                return rc
+
+        if self.socket() in socklist[1]:
+            rc = self.loop_write(max_packets)
+            if rc or (self._ssl == None and self._sock == None):
+                return rc
+
+        return self.loop_misc()
+
+    def publish(self, topic, payload=None, qos=0, retain=False):
+        """Publish a message on a topic.
+
+        This causes a message to be sent to the broker and subsequently from
+        the broker to any clients subscribing to matching topics.
+
+        topic: The topic that the message should be published on.
+        payload: The actual message to send. If not given, or set to None a
+        zero length message will be used. Passing an int or float will result
+        in the payload being converted to a string representing that number. If
+        you wish to send a true int/float, use struct.pack() to create the
+        payload you require.
+        qos: The quality of service level to use.
+        retain: If set to true, the message will be set as the "last known
+        good"/retained message for the topic.
+
+        Returns a tuple (result, mid), where result is MOSQ_ERR_SUCCESS to
+        indicate success or MOSQ_ERR_NO_CONN if the client is not currently
+        connected.  mid is the message ID for the publish request. The mid
+        value can be used to track the publish request by checking against the
+        mid argument in the on_publish() callback if it is defined.
+
+        A ValueError will be raised if topic == None, has zero length or is
+        invalid (contains a wildcard), if qos is not one of 0, 1 or 2, or if
+        the length of the payload is greater than 268435455 bytes."""
+        if topic == None or len(topic) == 0:
+            raise ValueError('Invalid topic.')
+        if qos<0 or qos>2:
+            raise ValueError('Invalid QoS level.')
+        if isinstance(payload, str) == True or isinstance(payload, bytearray) == True:
+            local_payload = payload
+        elif isinstance(payload, int) == True or isinstance(payload, float) == True:
+            local_payload = str(payload)
+        elif payload == None:
+            local_payload = None
+        else:
+            raise TypeError('payload must be a string, bytearray, int, float or None.')
+
+        if local_payload != None and len(local_payload) > 268435455:
+            raise ValueError('Payload too large.')
+
+        if self._topic_wildcard_len_check(topic) != MOSQ_ERR_SUCCESS:
+            raise ValueError('Publish topic cannot contain wildcards.')
+
+        local_mid = self._mid_generate()
+
+        if qos == 0:
+            rc = self._send_publish(local_mid, topic, local_payload, qos, retain, False)
+            return (rc, local_mid)
+        else:
+            message = MosquittoMessage()
+            message.timestamp = time.time()
+            message.direction = mosq_md_out
+            if qos == 1:
+                message.state = mosq_ms_wait_puback
+            elif qos == 2:
+                message.state = mosq_ms_wait_pubrec
+
+            message.mid = local_mid
+            message.topic = topic
+            if local_payload == None or len(local_payload) == 0:
+                message.payload = None
+            else:
+                message.payload = local_payload
+
+            message.qos = qos
+            message.retain = retain
+            message.dup = False
+
+            self._messages.append(message)
+            rc = self._send_publish(message.mid, message.topic, message.payload, message.qos, message.retain, message.dup)
+            return (rc, local_mid)
+
+    def username_pw_set(self, username, password=None):
+        """Set a username and optionally a password for broker authentication.
+
+        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.
+        password: The password to authenticate with. Optional, set to None if not required.
+        """
+        self._username = username
+        self._password = password
+
+    def disconnect(self):
+        """Disconnect a connected client from the broker."""
+        if self._sock == None and self._ssl == None:
+            return MOSQ_ERR_NO_CONN
+
+        self._state_mutex.acquire()
+        self._state = mosq_cs_disconnecting
+        self._state_mutex.release()
+
+        return self._send_disconnect()
+
+    def subscribe(self, topic, qos=0):
+        """Subscribe the client to a topic.
+
+        sub: The subscription topic to subscribe to.
+        qos: The desired quality of service level for the subscription.
+
+        Returns a tuple (result, mid), where result is MOSQ_ERR_SUCCESS
+        to indicate success or MOSQ_ERR_NO_CONN if the client is not currently connected.
+        mid is the message ID for the subscribe request. The mid value can be
+        used to track the subscribe request by checking against the mid
+        argument in the on_subscribe() callback if it is defined.
+        
+        Raises a ValueError if qos is not 0, 1 or 2, or if topic is None or has
+        zero string length.
+        """
+        if qos<0 or qos>2:
+            raise ValueError('Invalid QoS level.')
+        if topic == None or len(topic) == 0:
+            raise ValueError('Invalid topic.')
+        topic = _fix_sub_topic(topic)
+
+        if self._sock == None and self._ssl == None:
+            return MOSQ_ERR_NO_CONN
+
+        return self._send_subscribe(False, topic, qos)
+
+    def unsubscribe(self, topic):
+        """Unsubscribe the client from a topic.
+
+        sub: The subscription topic to unsubscribe from.
+
+        Returns a tuple (result, mid), where result is MOSQ_ERR_SUCCESS
+        to indicate success or MOSQ_ERR_NO_CONN if the client is not currently connected.
+        mid is the message ID for the unsubscribe request. The mid value can be
+        used to track the unsubscribe request by checking against the mid
+        argument in the on_unsubscribe() callback if it is defined.
+
+        Raises a ValueError if topic is None or has zero string length.
+        """
+        if topic == None or len(topic) == 0:
+            raise ValueError('Invalid topic.')
+        topic = _fix_sub_topic(topic)
+        if self._sock == None and self._ssl == None:
+            return MOSQ_ERR_NO_CONN
+
+        return self._send_unsubscribe(False, topic)
+
+    def loop_read(self, max_packets=1):
+        """Process read network events. Use in place of calling loop() if you
+        wish to handle your client reads as part of your own application.
+
+        Use socket() to obtain the client socket to call select() or equivalent
+        on.
+        
+        Do not use if you are using the threaded interface loop_start()."""
+        if self._sock == None and self._ssl == None:
+            return MOSQ_ERR_NO_CONN
+
+        max_packets = len(self._messages)
+        if max_packets < 1:
+            max_packets = 1
+
+        for i in range(0, max_packets):
+            rc = self._packet_read()
+            if rc > 0:
+                return self._loop_rc_handle(rc)
+            elif rc == MOSQ_ERR_AGAIN:
+                return MOSQ_ERR_SUCCESS
+        return MOSQ_ERR_SUCCESS
+
+    def loop_write(self, max_packets=1):
+        """Process read network events. Use in place of calling loop() if you
+        wish to handle your client reads as part of your own application.
+        
+        Use socket() to obtain the client socket to call select() or equivalent
+        on.
+
+        Use want_write() to determine if there is data waiting to be written.
+
+        Do not use if you are using the threaded interface loop_start()."""
+        if self._sock == None and self._ssl == None:
+            return MOSQ_ERR_NO_CONN
+
+        max_packets = len(self._messages)
+        if max_packets < 1:
+            max_packets = 1
+
+        for i in range(0, max_packets):
+            rc = self._packet_write()
+            if rc > 0:
+                return self._loop_rc_handle(rc)
+            elif rc == MOSQ_ERR_AGAIN:
+                return MOSQ_ERR_SUCCESS
+        return MOSQ_ERR_SUCCESS
+
+    def want_write(self):
+        """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:
+            return True
+        else:
+            return False
+
+    def loop_misc(self):
+        """Process miscellaneous network events. Use in place of calling loop() if you
+        wish to call select() or equivalent on.
+
+        Do not use if you are using the threaded interface loop_start()."""
+        if self._sock == None and self._ssl == None:
+            return MOSQ_ERR_NO_CONN
+
+        now = time.time()
+        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:
+            # mosq->ping_t != 0 means we are waiting for a pingresp.
+            # This hasn't happened in the keepalive time so we should disconnect.
+            if self._ssl:
+                self._ssl.close()
+                self._ssl = None
+            elif self._sock:
+                self._sock.close()
+                self._sock = None
+
+            self._callback_mutex.acquire()
+            if self._state == mosq_cs_disconnecting:
+                rc = MOSQ_ERR_SUCCESS
+            else:
+                rc = 1
+            if self.on_disconnect:
+                self._in_callback = True
+                self.on_disconnect(self, self._userdata, rc)
+                self._in_callback = False
+            self._callback_mutex.release()
+            return MOSQ_ERR_CONN_LOST
+
+        return MOSQ_ERR_SUCCESS
+
+    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
+
+    def user_data_set(self, userdata):
+        """Set the user data variable passed to callbacks. May be any data type."""
+        self._userdata = userdata
+
+    def will_set(self, topic, payload=None, qos=0, retain=False):
+        """Set a Will to be sent by the broker in case the client disconnects unexpectedly.
+
+        This must be called before connect() to have any effect.
+
+        topic: The topic that the will message should be published on.
+        payload: The message to send as a will. If not given, or set to None a
+        zero length message will be used as the will. Passing an int or float
+        will result in the payload being converted to a string representing
+        that number. If you wish to send a true int/float, use struct.pack() to
+        create the payload you require.
+        qos: The quality of service level to use for the will.
+        retain: If set to true, the will message will be set as the "last known
+        good"/retained message for the topic.
+
+        Raises a ValueError if qos is not 0, 1 or 2, or if topic is None or has
+        zero string length.
+        """
+        if topic == None or len(topic) == 0:
+            raise ValueError('Invalid topic.')
+        if qos<0 or qos>2:
+            raise ValueError('Invalid QoS level.')
+        if isinstance(payload, str) == True or isinstance(payload, bytearray) == True:
+            self._will_payload = payload
+        elif isinstance(payload, int) == True or isinstance(payload, float) == True:
+            self._will_payload = str(payload)
+        elif payload == None:
+            self._will_payload = None
+        else:
+            raise TypeError('payload must be a string, bytearray, int, float or None.')
+
+        self._will = True
+        self._will_topic = topic
+        self._will_qos = qos
+        self._will_retain = retain
+
+    def will_clear(self):
+        """ Removes a will that was previously configured with will_set().
+        
+        Must be called before connect() to have any effect."""
+        self._will = False
+        self._will_topic = ""
+        self._will_payload = None
+        self._will_qos = 0
+        self._will_retain = False
+
+    def socket(self):
+        """Return the socket or ssl object for this client."""
+        if self._ssl:
+            return self._ssl
+        else:
+            return self._sock
+
+    def loop_forever(self, timeout=1.0, max_packets=1):
+        """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.
+
+        loop_forever() will handle reconnecting for you. If you call
+        disconnect() in a callback it will return."""
+
+        run = True
+        if self._state == mosq_cs_connect_async:
+            self.reconnect()
+
+        while run == True:
+            rc = MOSQ_ERR_SUCCESS
+            while rc == MOSQ_ERR_SUCCESS:
+                rc = self.loop(timeout, max_packets)
+
+            if self._state == mosq_cs_disconnecting:
+                run = False
+            else:
+                time.sleep(1)
+                self.reconnect()
+        return rc
+
+    def loop_start(self):
+        """This is part of the threaded client interface. Call this once to
+        start a new thread to process network traffic. This provides an
+        alternative to repeatedly calling loop() yourself.
+        """
+        if self._thread != None:
+            return MOSQ_ERR_INVAL
+
+        self._thread = threading.Thread(target=self._thread_main)
+        self._thread.daemon = True
+        self._thread.start()
+
+    def loop_stop(self, force=False):
+        """This is part of the threaded client interface. Call this once to
+        stop the network thread previously created with loop_start(). This call
+        will block until the network thread finishes.
+
+        The force parameter is currently ignored.
+        """
+        if self._thread == None:
+            return MOSQ_ERR_INVAL
+
+        self._thread_terminate = True
+        self._thread.join()
+        self._thread = None
+
+    # ============================================================
+    # Private functions
+    # ============================================================
+
+    def _loop_rc_handle(self, rc):
+        if rc:
+            if self._ssl:
+                self._ssl.close()
+                self._ssl = None
+            elif self._sock:
+                self._sock.close()
+                self._sock = None
+
+            self._state_mutex.acquire()
+            if self._state == mosq_cs_disconnecting:
+                rc = MOSQ_ERR_SUCCESS
+            self._state_mutex.release()
+            self._callback_mutex.acquire()
+            if self.on_disconnect:
+                self._in_callback = True
+                self.on_disconnect(self, self._userdata, rc)
+                self._in_callback = False
+
+            self._callback_mutex.release()
+        return rc
+
+    def _packet_read(self):
+        # This gets called if pselect() indicates that there is network data
+        # available - ie. at least one byte.  What we do depends on what data we
+        # already have.
+        # If we've not got a command, attempt to read one and save it. This should
+        # always work because it's only a single byte.
+        # Then try to read the remaining length. This may fail because it is may
+        # be more than one byte - will need to save data pending next read if it
+        # does fail.
+        # Then try to read the remaining payload, where 'payload' here means the
+        # combined variable header and actual payload. This is the most likely to
+        # fail due to longer length, so save current data and current position.
+        # After all data is read, send to _mosquitto_handle_packet() to deal with.
+        # Finally, free the memory and reset everything to starting conditions.
+        if self._in_packet.command == 0:
+            try:
+                if self._ssl:
+                    command = self._ssl.read(1)
+                else:
+                    command = self._sock.recv(1)
+            except socket.error as err:
+                (msg) = err
+                if self._ssl and (msg.errno == ssl.SSL_ERROR_WANT_READ or msg.errno == ssl.SSL_ERROR_WANT_WRITE):
+                    return MOSQ_ERR_AGAIN
+                if msg.errno == errno.EAGAIN:
+                    return MOSQ_ERR_AGAIN
+                raise
+            else:
+                if len(command) == 0:
+                    return 1
+                command = struct.unpack("!B", command)
+                self._in_packet.command = command[0]
+
+        if self._in_packet.have_remaining == 0:
+            # Read remaining
+            # Algorithm for decoding taken from pseudo code at
+            # http://publib.boulder.ibm.com/infocenter/wmbhelp/v6r0m0/topic/com.ibm.etools.mft.doc/ac10870_.htm
+            while True:
+                try:
+                    if self._ssl:
+                        byte = self._ssl.read(1)
+                    else:
+                        byte = self._sock.recv(1)
+                except socket.error as err:
+                    (msg) = err
+                    if self._ssl and (msg.errno == ssl.SSL_ERROR_WANT_READ or msg.errno == ssl.SSL_ERROR_WANT_WRITE):
+                        return MOSQ_ERR_AGAIN
+                    if msg.errno == errno.EAGAIN:
+                        return MOSQ_ERR_AGAIN
+                    raise
+                else:
+                    byte = struct.unpack("!B", byte)
+                    byte = byte[0]
+                    self._in_packet.remaining_count.append(byte)
+                    # Max 4 bytes length for remaining length as defined by protocol.
+                     # Anything more likely means a broken/malicious client.
+                    if len(self._in_packet.remaining_count) > 4:
+                        return MOSQ_ERR_PROTOCOL
+
+                    self._in_packet.remaining_length = self._in_packet.remaining_length + (byte & 127)*self._in_packet.remaining_mult
+                    self._in_packet.remaining_mult = self._in_packet.remaining_mult * 128
+
+                if (byte & 128) == 0:
+                    break
+
+            self._in_packet.have_remaining = 1
+            self._in_packet.to_process = self._in_packet.remaining_length
+
+        while self._in_packet.to_process > 0:
+            try:
+                if self._ssl:
+                    data = self._ssl.read(self._in_packet.to_process)
+                else:
+                    data = self._sock.recv(self._in_packet.to_process)
+            except socket.error as err:
+                (msg) = err
+                if self._ssl and (msg.errno == ssl.SSL_ERROR_WANT_READ or msg.errno == ssl.SSL_ERROR_WANT_WRITE):
+                    return MOSQ_ERR_AGAIN
+                if msg.errno == errno.EAGAIN:
+                    return MOSQ_ERR_AGAIN
+                raise
+            else:
+                self._in_packet.to_process = self._in_packet.to_process - len(data)
+                self._in_packet.packet = self._in_packet.packet + data
+
+        # All data for this packet is read.
+        self._in_packet.pos = 0
+        rc = self._packet_handle()
+
+        # Free data and reset values 
+        self._in_packet.cleanup()
+
+        self._msgtime_mutex.acquire()
+        self._last_msg_in = time.time()
+        self._msgtime_mutex.release()
+        return rc
+
+    def _packet_write(self):
+        self._current_out_packet_mutex.acquire()
+
+        while self._current_out_packet:
+            packet = self._current_out_packet
+
+            try:
+                if self._ssl:
+                    write_length = self._ssl.write(packet.packet[packet.pos:])
+                else:
+                    write_length = self._sock.send(packet.packet[packet.pos:])
+            except AttributeError:
+                self._current_out_packet_mutex.release()
+                return MOSQ_ERR_SUCCESS
+            except socket.error as err:
+                self._current_out_packet_mutex.release()
+                (msg) = err
+                if self._ssl and (msg.errno == ssl.SSL_ERROR_WANT_READ or msg.errno == ssl.SSL_ERROR_WANT_WRITE):
+                    return MOSQ_ERR_AGAIN
+                if msg.errno == errno.EAGAIN:
+                    return MOSQ_ERR_AGAIN
+                raise
+
+            if write_length > 0:
+                packet.to_process = packet.to_process - write_length
+                packet.pos = packet.pos + write_length
+
+                if packet.to_process == 0:
+                    if (packet.command & 0xF0) == PUBLISH and packet.qos == 0:
+                        self._callback_mutex.acquire()
+                        if self.on_publish:
+                            self._in_callback = True
+                            self.on_publish(self, self._userdata, packet.mid)
+                            self._in_callback = False
+
+                        self._callback_mutex.release()
+
+                    self._out_packet_mutex.acquire()
+                    if len(self._out_packet) > 0:
+                        self._current_out_packet = self._out_packet.pop(0)
+                    else:
+                        self._current_out_packet = None
+                    self._out_packet_mutex.release()
+            else:
+                pass # FIXME
+        
+        self._current_out_packet_mutex.release()
+
+        self._msgtime_mutex.acquire()
+        self._last_msg_out = time.time()
+        self._msgtime_mutex.release()
+
+        return MOSQ_ERR_SUCCESS
+
+    def _easy_log(self, level, buf):
+        if self.on_log:
+            self.on_log(self, self._userdata, level, buf)
+
+    def _check_keepalive(self):
+        now = time.time()
+        self._msgtime_mutex.acquire()
+        last_msg_out = self._last_msg_out
+        last_msg_in = self._last_msg_in
+        self._msgtime_mutex.release()
+        if (self._sock != None or self._ssl != None) and (now - last_msg_out >= self._keepalive or now - last_msg_in >= self._keepalive):
+            if self._state == mosq_cs_connected and self._ping_t == 0:
+                self._send_pingreq()
+                self._msgtime_mutex.acquire()
+                self._last_msg_out = now
+                self._last_msg_in = now
+                self._msgtime_mutex.release()
+            else:
+                if self._ssl:
+                    self._ssl.close()
+                    self._ssl = None
+                elif self._sock:
+                    self._sock.close()
+                    self._sock = None
+
+                if self._state == mosq_cs_disconnecting:
+                    rc = MOSQ_ERR_SUCCESS
+                else:
+                    rc = 1
+                self._callback_mutex.acquire()
+                if self.on_disconnect:
+                    self._in_callback = True
+                    self.on_disconnect(self, self._userdata, rc)
+                    self._in_callback = False
+                self._callback_mutex.release()
+
+    def _mid_generate(self):
+        self._last_mid = self._last_mid + 1
+        if self._last_mid == 65536:
+            self._last_mid = 1
+        return self._last_mid
+
+    def _topic_wildcard_len_check(self, topic):
+        # Search for + or # in a topic. Return MOSQ_ERR_INVAL if found.
+         # Also returns MOSQ_ERR_INVAL if the topic string is too long.
+         # Returns MOSQ_ERR_SUCCESS if everything is fine.
+        if '+' in topic or '#' in topic or len(topic) == 0 or len(topic) > 65535:
+            return MOSQ_ERR_INVAL
+        else:
+            return MOSQ_ERR_SUCCESS
+
+    def _send_pingreq(self):
+        self._easy_log(MOSQ_LOG_DEBUG, "Sending PINGREQ")
+        rc = self._send_simple_command(PINGREQ)
+        if rc == MOSQ_ERR_SUCCESS:
+            self._ping_t = time.time()
+        return rc
+
+    def _send_pingresp(self):
+        self._easy_log(MOSQ_LOG_DEBUG, "Sending PINGRESP")
+        return self._send_simple_command(PINGRESP)
+
+    def _send_puback(self, mid):
+        self._easy_log(MOSQ_LOG_DEBUG, "Sending PUBACK (Mid: "+str(mid)+")")
+        return self._send_command_with_mid(PUBACK, mid, False)
+
+    def _send_pubcomp(self, mid):
+        self._easy_log(MOSQ_LOG_DEBUG, "Sending PUBCOMP (Mid: "+str(mid)+")")
+        return self._send_command_with_mid(PUBCOMP, mid, False)
+
+    def _pack_remaining_length(self, packet, remaining_length):
+        remaining_bytes = []
+        while True:
+            byte = remaining_length % 128
+            remaining_length = remaining_length // 128
+            # If there are more digits to encode, set the top bit of this digit
+            if remaining_length > 0:
+                byte = byte | 0x80
+
+            remaining_bytes.append(byte)
+            packet.extend(struct.pack("!B", byte))
+            if remaining_length == 0:
+                # FIXME - this doesn't deal with incorrectly large payloads
+                return packet
+
+    def _pack_str16(self, packet, data):
+        if sys.version_info[0] < 3:
+            if isinstance(data, bytearray):
+                packet.extend(struct.pack("!H", len(data)))
+                packet.extend(data)
+            elif isinstance(data, str):
+                pack_format = "!H" + str(len(data)) + "s"
+                packet.extend(struct.pack(pack_format, len(data), data))
+            elif isinstance(data, unicode):
+                udata = data.encode('utf-8')
+                pack_format = "!H" + str(len(udata)) + "s"
+                packet.extend(struct.pack(pack_format, len(udata), udata))
+            else:
+                raise TypeError
+        else:
+            if isinstance(data, bytearray):
+                packet.extend(struct.pack("!H", len(data)))
+                packet.extend(data)
+            elif isinstance(data, str):
+                udata = data.encode('utf-8')
+                pack_format = "!H" + str(len(udata)) + "s"
+                packet.extend(struct.pack(pack_format, len(udata), udata))
+            else:
+                raise TypeError
+
+    def _send_publish(self, mid, topic, payload=None, qos=0, retain=False, dup=False):
+        if self._sock == None and self._ssl == None:
+            return MOSQ_ERR_NO_CONN
+
+        command = PUBLISH | ((dup&0x1)<<3) | (qos<<1) | retain
+        packet = bytearray()
+        packet.extend(struct.pack("!B", command))
+        if payload == None:
+            remaining_length = 2+len(topic)
+            self._easy_log(MOSQ_LOG_DEBUG, "Sending PUBLISH (d"+str(dup)+", q"+str(qos)+", r"+str(retain)+", m"+str(mid)+", '"+topic+"' (NULL payload)")
+        else:
+            remaining_length = 2+len(topic) + len(payload)
+            self._easy_log(MOSQ_LOG_DEBUG, "Sending PUBLISH (d"+str(dup)+", q"+str(qos)+", r"+str(retain)+", m"+str(mid)+", '"+topic+"', ... ("+str(len(payload))+" bytes)")
+
+        if qos > 0:
+            # For message id
+            remaining_length = remaining_length + 2
+
+        self._pack_remaining_length(packet, remaining_length)
+        self._pack_str16(packet, topic)
+
+        if qos > 0:
+            # For message id
+            packet.extend(struct.pack("!H", mid))
+
+        if payload != None:
+            if isinstance(payload, str):
+                if sys.version_info[0] < 3:
+                    pack_format = str(len(payload)) + "s"
+                    packet.extend(struct.pack(pack_format, payload))
+                else:
+                    upayload = payload.encode('utf-8')
+                    pack_format = str(len(upayload)) + "s"
+                    packet.extend(struct.pack(pack_format, upayload))
+            elif isinstance(payload, bytearray):
+                packet.extend(payload)
+            elif isinstance(payload, unicode):
+                    upayload = payload.encode('utf-8')
+                    pack_format = str(len(upayload)) + "s"
+                    packet.extend(struct.pack(pack_format, upayload))
+            else:
+                raise TypeError('payload must be a string, unicode or a bytearray.')
+
+        return self._packet_queue(PUBLISH, packet, mid, qos)
+
+    def _send_pubrec(self, mid):
+        self._easy_log(MOSQ_LOG_DEBUG, "Sending PUBREC (Mid: "+str(mid)+")")
+        return self._send_command_with_mid(PUBREC, mid, False)
+
+    def _send_pubrel(self, mid, dup=False):
+        self._easy_log(MOSQ_LOG_DEBUG, "Sending PUBREL (Mid: "+str(mid)+")")
+        return self._send_command_with_mid(PUBREL|2, mid, dup)
+
+    def _send_command_with_mid(self, command, mid, dup):
+        # For PUBACK, PUBCOMP, PUBREC, and PUBREL
+        if dup:
+            command = command | 8
+
+        remaining_length = 2
+        packet = struct.pack('!BBH', command, remaining_length, mid)
+        return self._packet_queue(command, packet, mid, 1)
+
+    def _send_simple_command(self, command):
+        # For DISCONNECT, PINGREQ and PINGRESP
+        remaining_length = 0
+        packet = struct.pack('!BB', command, remaining_length)
+        return self._packet_queue(command, packet, 0, 0)
+
+    def _send_connect(self, keepalive, clean_session):
+        remaining_length = 12 + 2+len(self._client_id)
+        connect_flags = 0
+        if clean_session:
+            connect_flags = connect_flags | 0x02
+
+        if self._will:
+            remaining_length = remaining_length + 2+len(self._will_topic) + 2+len(self._will_payload)
+            connect_flags = connect_flags | 0x04 | ((self._will_qos&0x03) << 3) | ((self._will_retain&0x01) << 5)
+
+        if self._username:
+            remaining_length = remaining_length + 2+len(self._username)
+            connect_flags = connect_flags | 0x80
+            if self._password:
+                connect_flags = connect_flags | 0x40
+                remaining_length = remaining_length + 2+len(self._password)
+
+        command = CONNECT
+        packet = bytearray()
+        packet.extend(struct.pack("!B", command))
+        self._pack_remaining_length(packet, remaining_length)
+        packet.extend(struct.pack("!H6sBBH", len(PROTOCOL_NAME), PROTOCOL_NAME, PROTOCOL_VERSION, connect_flags, keepalive))
+
+        self._pack_str16(packet, self._client_id)
+
+        if self._will:
+            self._pack_str16(packet, self._will_topic)
+            if len(self._will_payload) > 0:
+                self._pack_str16(packet, self._will_payload)
+            else:
+                packet.extend(struct.pack("!H", 0))
+
+        if self._username:
+            self._pack_str16(packet, self._username)
+
+            if self._password:
+                self._pack_str16(packet, self._password)
+
+        self._keepalive = keepalive
+        return self._packet_queue(command, packet, 0, 0)
+
+    def _send_disconnect(self):
+        return self._send_simple_command(DISCONNECT)
+
+    def _send_subscribe(self, dup, topic, topic_qos):
+        remaining_length = 2 + 2+len(topic) + 1
+        command = SUBSCRIBE | (dup<<3) | (1<<1)
+        packet = bytearray()
+        packet.extend(struct.pack("!B", command))
+        self._pack_remaining_length(packet, remaining_length)
+        local_mid = self._mid_generate()
+        pack_format = "!HH" + str(len(topic)) + "sB"
+        packet.extend(struct.pack("!H", local_mid))
+        self._pack_str16(packet, topic)
+        packet.extend(struct.pack("B", topic_qos))
+        return (self._packet_queue(command, packet, local_mid, 1), local_mid)
+
+    def _send_unsubscribe(self, dup, topic):
+        remaining_length = 2 + 2+len(topic)
+        command = UNSUBSCRIBE | (dup<<3) | (1<<1)
+        packet = bytearray()
+        packet.extend(struct.pack("!B", command))
+        self._pack_remaining_length(packet, remaining_length)
+        local_mid = self._mid_generate()
+        pack_format = "!HH" + str(len(topic)) + "sB"
+        packet.extend(struct.pack("!H", local_mid))
+        self._pack_str16(packet, topic)
+        return (self._packet_queue(command, packet, local_mid, 1), local_mid)
+
+    def _message_update(self, mid, direction, state):
+        for m in self._messages:
+            if m.mid == mid and m.direction == direction:
+                m.state = state
+                m.timestamp = time.time()
+                return MOSQ_ERR_SUCCESS
+
+        return MOSQ_ERR_NOT_FOUND
+
+    def _message_retry_check(self):
+        now = time.time()
+        for m in self._messages:
+            if m.timestamp + self._message_retry < now:
+                if m.state == mosq_ms_wait_puback or m.state == mosq_ms_wait_pubrec:
+                    m.timestamp = now
+                    m.dup = True
+                    self._send_publish(m.mid, m.topic, m.payload, m.qos, m.retain, m.dup)
+                elif m.state == mosq_ms_wait_pubrel:
+                    m.timestamp = now
+                    m.dup = True
+                    self._send_pubrec(m.mid)
+                elif m.state == mosq_ms_wait_pubcomp:
+                    m.timestamp = now
+                    m.dup = True
+                    self._send_pubrel(m.mid, True)
+
+    def _messages_reconnect_reset(self):
+        for m in self._messages:
+            m.timestamp = 0
+            if m.direction == mosq_md_out:
+                if m.qos == 1:
+                    m.state = mosq_ms_wait_puback
+                elif m.qos == 2:
+                    m.state = mosq_ms_wait_pubrec
+            else:
+                self._messages.pop(self._messages.index(m))
+
+    def _packet_queue(self, command, packet, mid, qos):
+        mpkt = MosquittoPacket(command, packet, mid, qos)
+
+        self._out_packet_mutex.acquire()
+        self._out_packet.append(mpkt)
+        if self._current_out_packet_mutex.acquire(False) == True:
+            if self._current_out_packet == None and len(self._out_packet) > 0:
+                self._current_out_packet = self._out_packet.pop(0)
+            self._current_out_packet_mutex.release()
+        self._out_packet_mutex.release()
+
+        if self._in_callback == False:
+            return self.loop_write()
+        else:
+            return MOSQ_ERR_SUCCESS
+
+    def _packet_handle(self):
+        cmd = self._in_packet.command&0xF0
+        if cmd == PINGREQ:
+            return self._handle_pingreq()
+        elif cmd == PINGRESP:
+            return self._handle_pingresp()
+        elif cmd == PUBACK:
+            return self._handle_pubackcomp("PUBACK")
+        elif cmd == PUBCOMP:
+            return self._handle_pubackcomp("PUBCOMP")
+        elif cmd == PUBLISH:
+            return self._handle_publish()
+        elif cmd == PUBREC:
+            return self._handle_pubrec()
+        elif cmd == PUBREL:
+            return self._handle_pubrel()
+        elif cmd == CONNACK:
+            return self._handle_connack()
+        elif cmd == SUBACK:
+            return self._handle_suback()
+        elif cmd == UNSUBACK:
+            return self._handle_unsuback()
+        else:
+            # If we don't recognise the command, return an error straight away.
+            self._easy_log(MOSQ_LOG_ERR, "Error: Unrecognised command "+str(cmd))
+            return MOSQ_ERR_PROTOCOL
+
+    def _handle_pingreq(self):
+        if self._strict_protocol:
+            if self._in_packet.remaining_length != 0:
+                return MOSQ_ERR_PROTOCOL
+        
+        self._easy_log(MOSQ_LOG_DEBUG, "Received PINGREQ")
+        return self._send_pingresp()
+
+    def _handle_pingresp(self):
+        if self._strict_protocol:
+            if self._in_packet.remaining_length != 0:
+                return MOSQ_ERR_PROTOCOL
+        
+        # No longer waiting for a PINGRESP.
+        self._ping_t = 0
+        self._easy_log(MOSQ_LOG_DEBUG, "Received PINGRESP")
+        return MOSQ_ERR_SUCCESS
+
+    def _handle_connack(self):
+        if self._strict_protocol:
+            if self._in_packet.remaining_length != 2:
+                return MOSQ_ERR_PROTOCOL
+
+        if len(self._in_packet.packet) != 2:
+            return MOSQ_ERR_PROTOCOL
+
+        (resvd, result) = struct.unpack("!BB", self._in_packet.packet)
+        self._easy_log(MOSQ_LOG_DEBUG, "Received CONNACK ("+str(resvd)+", "+str(result)+")")
+        self._callback_mutex.acquire()
+        if self.on_connect:
+            self._in_callback = True
+            self.on_connect(self, self._userdata, result)
+            self._in_callback = False
+        self._callback_mutex.release()
+        if result == 0:
+            self._state = mosq_cs_connected
+            return MOSQ_ERR_SUCCESS
+        elif result > 0 and result < 6:
+            return MOSQ_ERR_CONN_REFUSED
+        else:
+            return MOSQ_ERR_PROTOCOL
+
+    def _handle_suback(self):
+        self._easy_log(MOSQ_LOG_DEBUG, "Received SUBACK")
+        pack_format = "!H" + str(len(self._in_packet.packet)-2) + 's'
+        (mid, packet) = struct.unpack(pack_format, self._in_packet.packet)
+        pack_format = "!" + "B"*len(packet)
+        granted_qos = struct.unpack(pack_format, packet)
+
+        self._callback_mutex.acquire()
+        if self.on_subscribe:
+            self._in_callback = True
+            self.on_subscribe(self, self._userdata, mid, granted_qos)
+            self._in_callback = False
+        self._callback_mutex.release()
+
+        return MOSQ_ERR_SUCCESS
+
+    def _handle_publish(self):
+        rc = 0
+
+        header = self._in_packet.command
+        message = MosquittoMessage()
+        message.direction = mosq_md_in
+        message.dup = (header & 0x08)>>3
+        message.qos = (header & 0x06)>>1
+        message.retain = (header & 0x01)
+
+        pack_format = "!H" + str(len(self._in_packet.packet)-2) + 's'
+        (slen, packet) = struct.unpack(pack_format, self._in_packet.packet)
+        pack_format = '!' + str(slen) + 's' + str(len(packet)-slen) + 's'
+        (message.topic, packet) = struct.unpack(pack_format, packet)
+
+        if len(message.topic) == 0:
+            return MOSQ_ERR_PROTOCOL
+
+        if sys.version_info[0] >= 3:
+            message.topic = message.topic.decode('utf-8')
+        message.topic = _fix_sub_topic(message.topic)
+
+        if message.qos > 0:
+            pack_format = "!H" + str(len(packet)-2) + 's'
+            (message.mid, packet) = struct.unpack(pack_format, packet)
+
+        message.payload = packet
+
+        self._easy_log(MOSQ_LOG_DEBUG, "Received PUBLISH (d"+str(message.dup)+
+                ", q"+str(message.qos)+", r"+str(message.retain)+
+                ", m"+str(message.mid)+", '"+message.topic+
+                "', ...  ("+str(len(message.payload))+" bytes)")
+
+        message.timestamp = time.time()
+        if message.qos == 0:
+            self._callback_mutex.acquire()
+            if self.on_message:
+                self._in_callback = True
+                self.on_message(self, self._userdata, message)
+                self._in_callback = False
+
+            self._callback_mutex.release()
+            return MOSQ_ERR_SUCCESS
+        elif message.qos == 1:
+            rc = self._send_puback(message.mid)
+            self._callback_mutex.acquire()
+            if self.on_message:
+                self._in_callback = True
+                self.on_message(self, self._userdata, message)
+                self._in_callback = False
+
+            self._callback_mutex.release()
+            return rc
+        elif message.qos == 2:
+            rc = self._send_pubrec(message.mid)
+            message.state = mosq_ms_wait_pubrel
+            self._messages.append(message)
+            return rc
+        else:
+            return MOSQ_ERR_PROTOCOL
+
+    def _handle_pubrel(self):
+        if self._strict_protocol:
+            if self._in_packet.remaining_length != 2:
+                return MOSQ_ERR_PROTOCOL
+        
+        if len(self._in_packet.packet) != 2:
+            return MOSQ_ERR_PROTOCOL
+
+        mid = struct.unpack("!H", self._in_packet.packet)
+        mid = mid[0]
+        self._easy_log(MOSQ_LOG_DEBUG, "Received PUBREL (Mid: "+str(mid)+")")
+        
+        for i in range(len(self._messages)):
+            if self._messages[i].direction == mosq_md_in and self._messages[i].mid == mid:
+
+                # Only pass the message on if we have removed it from the queue - this
+                # prevents multiple callbacks for the same message.
+                self._callback_mutex.acquire()
+                if self.on_message:
+                    self._in_callback = True
+                    self.on_message(self, self._userdata, self._messages[i])
+                    self._in_callback = False
+                self._callback_mutex.release()
+                self._messages.pop(i)
+
+                return self._send_pubcomp(mid)
+
+        return MOSQ_ERR_SUCCESS
+
+    def _handle_pubrec(self):
+        if self._strict_protocol:
+            if self._in_packet.remaining_length != 2:
+                return MOSQ_ERR_PROTOCOL
+        
+        mid = struct.unpack("!H", self._in_packet.packet)
+        mid = mid[0]
+        self._easy_log(MOSQ_LOG_DEBUG, "Received PUBREC (Mid: "+str(mid)+")")
+        
+        for i in range(len(self._messages)):
+            if self._messages[i].direction == mosq_md_out and self._messages[i].mid == mid:
+                self._messages[i].state = mosq_ms_wait_pubcomp
+                self._messages[i].timestamp = time.time()
+                return self._send_pubrel(mid, False)
+        
+        return MOSQ_ERR_SUCCESS
+
+    def _handle_unsuback(self):
+        if self._strict_protocol:
+            if self._in_packet.remaining_length != 2:
+                return MOSQ_ERR_PROTOCOL
+        
+        mid = struct.unpack("!H", self._in_packet.packet)
+        mid = mid[0]
+        self._easy_log(MOSQ_LOG_DEBUG, "Received UNSUBACK (Mid: "+str(mid)+")")
+        self._callback_mutex.acquire()
+        if self.on_unsubscribe:
+            self._in_callback = True
+            self.on_unsubscribe(self, self._userdata, mid)
+            self._in_callback = False
+        self._callback_mutex.release()
+        return MOSQ_ERR_SUCCESS
+
+    def _handle_pubackcomp(self, cmd):
+        if self._strict_protocol:
+            if self._in_packet.remaining_length != 2:
+                return MOSQ_ERR_PROTOCOL
+        
+        mid = struct.unpack("!H", self._in_packet.packet)
+        mid = mid[0]
+        self._easy_log(MOSQ_LOG_DEBUG, "Received "+cmd+" (Mid: "+str(mid)+")")
+        
+        for i in range(len(self._messages)):
+            try:
+                if self._messages[i].direction == mosq_md_out and self._messages[i].mid == mid:
+                    # Only inform the client the message has been sent once.
+                    self._callback_mutex.acquire()
+                    if self.on_publish:
+                        self._in_callback = True
+                        self.on_publish(self, self._userdata, mid)
+                        self._in_callback = False
+
+                    self._callback_mutex.release()
+                    self._messages.pop(i)
+            except IndexError:
+                # Have removed item so i>count.
+                # Not really an error.
+                pass
+
+        return MOSQ_ERR_SUCCESS
+
+    def _thread_main(self):
+        run = True
+        self._thread_terminate = False
+        self._state_mutex.acquire()
+        if self._state == mosq_cs_connect_async:
+            self._state_mutex.release()
+            self.reconnect()
+        else:
+            self._state_mutex.release()
+
+        while run == True:
+            rc = MOSQ_ERR_SUCCESS
+            while rc == MOSQ_ERR_SUCCESS:
+                rc = self.loop()
+                if self._thread_terminate == True:
+                    rc = 1
+                    run = False
+
+            self._state_mutex.acquire()
+            if self._state == mosq_cs_disconnecting:
+                run = False
+                self._state_mutex.release()
+            else:
+                self._state_mutex.release()
+                time.sleep(1)
+                self.reconnect()
+
diff --git a/lib/python/setup.py b/lib/python/setup.py
new file mode 100644 (file)
index 0000000..7b97bfa
--- /dev/null
@@ -0,0 +1,28 @@
+from sys import version
+
+from distutils.core import setup
+setup(name='mosquitto',
+       version='1.1.90',
+       description='MQTT version 3.1 client class',
+       author='Roger Light',
+       author_email='roger@atchoo.org',
+       url='http://mosquitto.org/',
+       download_url='http://mosquitto.org/files/',
+       license='BSD License',
+       py_modules=['mosquitto'],
+
+    classifiers=[
+        'Development Status :: 4 - Beta',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: BSD License',
+        'Operating System :: MacOS :: MacOS X',
+        'Operating System :: Microsoft :: Windows',
+        'Operating System :: POSIX',
+        'Programming Language :: Python',
+        'Programming Language :: Python :: 2.6',
+        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3',
+        'Topic :: Communications',
+        'Topic :: Internet',
+        ]
+       )
diff --git a/lib/python/sub.py b/lib/python/sub.py
new file mode 100755 (executable)
index 0000000..7af946d
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/python
+
+# Copyright (c) 2010,2011 Roger Light <roger@atchoo.org>
+# All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+# 
+# 1. Redistributions of source code must retain the above copyright notice,
+#   this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+# 3. Neither the name of mosquitto nor the names of its
+#   contributors may be used to endorse or promote products derived from
+#   this software without specific prior written permission.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) 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 OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import mosquitto
+
+def on_connect(mosq, obj, rc):
+    print("rc: "+str(rc))
+
+def on_message(mosq, obj, msg):
+    print(msg.topic+" "+str(msg.qos)+" "+str(msg.payload))
+
+def on_publish(mosq, obj, mid):
+    print("mid: "+str(mid))
+
+def on_subscribe(mosq, obj, mid, granted_qos):
+    print("Subscribed: "+str(mid)+" "+str(granted_qos))
+
+def on_log(mosq, obj, level, string):
+    print(string)
+
+mqttc = mosquitto.Mosquitto()
+mqttc.on_message = on_message
+mqttc.on_connect = on_connect
+mqttc.on_publish = on_publish
+mqttc.on_subscribe = on_subscribe
+# Uncomment to enable debug messages
+#mqttc.on_log = on_log
+mqttc.connect("test.mosquitto.org", 1883, 60)
+mqttc.subscribe("$SYS/#", 0)
+
+
+rc = 0
+while rc == 0:
+    rc = mqttc.loop()
+
+print("rc: "+str(rc))
diff --git a/test/Makefile b/test/Makefile
new file mode 100644 (file)
index 0000000..8c9a44d
--- /dev/null
@@ -0,0 +1,91 @@
+include ../config.mk
+
+CC=cc
+CFLAGS=-I../src -I../lib -I. -I.. -Wall -ggdb -DDEBUG -DWITH_CLIENT
+LDFLAGS=
+OBJS=context.o database.o logging.o memory.o net.o raw_send.o raw_send_client.o read_handle.o read_handle_client.o util.o
+SOVERSION=1
+
+.PHONY: all test clean reallyclean
+
+all : fake_user msgsps_pub msgsps_sub 
+#packet-gen qos
+
+test :
+       $(MAKE) -C broker test
+       $(MAKE) -C lib test
+
+fake_user : fake_user.o
+       ${CC} $^ -o $@ ../lib/libmosquitto.so.${SOVERSION}
+       #${CC} $^ -o $@ -lmosquitto
+
+fake_user.o : fake_user.c
+       ${CC} $(CFLAGS) -c $< -o $@
+
+msgsps_pub : msgsps_pub.o
+       ${CC} $^ -o $@ ../lib/libmosquitto.so.${SOVERSION}
+
+msgsps_pub.o : msgsps_pub.c msgsps_common.h
+       ${CC} $(CFLAGS) -c $< -o $@
+
+msgsps_sub : msgsps_sub.o
+       ${CC} $^ -o $@ ../lib/libmosquitto.so.${SOVERSION}
+
+msgsps_sub.o : msgsps_sub.c msgsps_common.h
+       ${CC} $(CFLAGS) -c $< -o $@
+
+packet-gen : packet-gen.o
+       ${CC} $^ -o $@ ../lib/libmosquitto.so.${SOVERSION}
+
+packet-gen.o : packet-gen.c
+       ${CC} $(CFLAGS) -c $< -o $@
+
+qos : qos.o
+       ${CC} $^ -o $@ ../lib/libmosquitto.so.${SOVERSION}
+
+qos.o : qos.c
+       ${CC} $(CFLAGS) -c $< -o $@
+
+random_client : random_client.o ${OBJS}
+       ${CC} $^ -o $@ ${LDFLAGS}
+
+random_client.o : random_client.c ../src/mqtt3.h
+       ${CC} $(CFLAGS) -c $< -o $@
+
+context.o : ../src/context.c ../src/mqtt3.h
+       ${CC} $(CFLAGS) -c $< -o $@
+
+database.o : ../src/database.c ../src/mqtt3.h
+       ${CC} $(CFLAGS) -c $< -o $@
+
+logging.o : ../src/logging.c ../src/mqtt3.h
+       ${CC} $(CFLAGS) -c $< -o $@
+
+memory.o : ../src/memory.c ../src/mqtt3.h
+       ${CC} $(CFLAGS) -c $< -o $@
+
+net.o : ../src/net.c ../src/mqtt3.h
+       ${CC} $(CFLAGS) -c $< -o $@
+
+raw_send.o : ../src/raw_send.c ../src/mqtt3.h
+       ${CC} $(CFLAGS) -c $< -o $@
+
+raw_send_client.o : ../src/raw_send_client.c ../src/mqtt3.h
+       ${CC} $(CFLAGS) -c $< -o $@
+
+read_handle.o : ../src/read_handle.c ../src/mqtt3.h
+       ${CC} $(CFLAGS) -c $< -o $@
+
+read_handle_client.o : ../src/read_handle_client.c ../src/mqtt3.h
+       ${CC} $(CFLAGS) -c $< -o $@
+
+util.o : ../src/util.c ../src/mqtt3.h
+       ${CC} $(CFLAGS) -c $< -o $@
+
+reallyclean : clean
+       -rm -f *.orig
+
+clean : 
+       -rm -f *.o random_client qos msgsps_pub msgsps_sub fake_user test_client *.pyc
+       $(MAKE) -C lib clean
+       $(MAKE) -C broker clean
diff --git a/test/lib/01-con-discon-success.py b/test/lib/01-con-discon-success.py
new file mode 100755 (executable)
index 0000000..51cec8f
--- /dev/null
@@ -0,0 +1,65 @@
+#!/usr/bin/python
+
+# Test whether a client produces a correct connect and subsequent disconnect.
+
+# The client should connect to port 1888 with keepalive=60, clean session set,
+# and client id 01-con-discon-success
+# 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 inspect
+import os
+import subprocess
+import socket
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+rc = 1
+keepalive = 60
+connect_packet = mosq_test.gen_connect("01-con-discon-success", keepalive=keepalive)
+connack_packet = mosq_test.gen_connack(rc=0)
+
+disconnect_packet = mosq_test.gen_disconnect()
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.settimeout(10)
+sock.bind(('', 1888))
+sock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        conn.send(connack_packet)
+
+        if mosq_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
new file mode 100755 (executable)
index 0000000..612a802
--- /dev/null
@@ -0,0 +1,69 @@
+#!/usr/bin/python
+
+# Test whether a client sends a pingreq after the keepalive time
+
+# The client should connect to port 1888 with keepalive=4, clean session set,
+# and client id 01-keepalive-pingreq
+# The client should send a PINGREQ message after the appropriate amount of time
+# (4 seconds after no traffic).
+
+import inspect
+import os
+import subprocess
+import socket
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+rc = 1
+keepalive = 4
+connect_packet = mosq_test.gen_connect("01-keepalive-pingreq", keepalive=keepalive)
+connack_packet = mosq_test.gen_connack(rc=0)
+
+pingreq_packet = mosq_test.gen_pingreq()
+pingresp_packet = mosq_test.gen_pingresp()
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.settimeout(10)
+sock.bind(('', 1888))
+sock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(keepalive+10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        conn.send(connack_packet)
+
+        if mosq_test.expect_packet(conn, "pingreq", pingreq_packet):
+            time.sleep(1.0)
+            conn.send(pingresp_packet)
+
+            if mosq_test.expect_packet(conn, "pingreq", pingreq_packet):
+                rc = 0
+
+    conn.close()
+finally:
+    client.terminate()
+    client.wait()
+    sock.close()
+
+exit(rc)
+
diff --git a/test/lib/01-no-clean-session.py b/test/lib/01-no-clean-session.py
new file mode 100755 (executable)
index 0000000..b495e85
--- /dev/null
@@ -0,0 +1,56 @@
+#!/usr/bin/python
+
+# Test whether a client produces a correct connect with clean session not set.
+
+# The client should connect to port 1888 with keepalive=60, clean session not
+# set, and client id 01-no-clean-session.
+
+import inspect
+import os
+import subprocess
+import socket
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+rc = 1
+keepalive = 60
+connect_packet = mosq_test.gen_connect("01-no-clean-session", clean_session=False, keepalive=keepalive)
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.settimeout(10)
+sock.bind(('', 1888))
+sock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        rc = 0
+
+    conn.close()
+finally:
+    client.terminate()
+    client.wait()
+    sock.close()
+
+exit(rc)
+
diff --git a/test/lib/01-unpwd-set.py b/test/lib/01-unpwd-set.py
new file mode 100755 (executable)
index 0000000..f5ae278
--- /dev/null
@@ -0,0 +1,56 @@
+#!/usr/bin/python
+
+# Test whether a client produces a correct connect with a username and password.
+
+# The client should connect to port 1888 with keepalive=60, clean session set,
+# client id 01-unpwd-set, username set to uname and password set to ;'[08gn=#
+
+import inspect
+import os
+import subprocess
+import socket
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+rc = 1
+keepalive = 60
+connect_packet = mosq_test.gen_connect("01-unpwd-set", keepalive=keepalive, username="uname", password=";'[08gn=#")
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.settimeout(10)
+sock.bind(('', 1888))
+sock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        rc = 0
+
+    conn.close()
+finally:
+    client.terminate()
+    client.wait()
+    sock.close()
+
+exit(rc)
+
diff --git a/test/lib/01-will-set.py b/test/lib/01-will-set.py
new file mode 100755 (executable)
index 0000000..586adf7
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/python
+
+# Test whether a client produces a correct connect with a will.
+# Will QoS=1, will retain=1.
+
+# The client should connect to port 1888 with keepalive=60, clean session set,
+# client id 01-will-set will topic set to topic/on/unexpected/disconnect , will
+# payload set to "will message", will qos set to 1 and will retain set.
+
+import inspect
+import os
+import subprocess
+import socket
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+rc = 1
+keepalive = 60
+connect_packet = mosq_test.gen_connect("01-will-set", keepalive=keepalive, will_topic="topic/on/unexpected/disconnect", will_qos=1, will_retain=True, will_payload="will message")
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.settimeout(10)
+sock.bind(('', 1888))
+sock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        rc = 0
+
+    conn.close()
+finally:
+    client.terminate()
+    client.wait()
+    sock.close()
+
+exit(rc)
+
diff --git a/test/lib/01-will-unpwd-set.py b/test/lib/01-will-unpwd-set.py
new file mode 100755 (executable)
index 0000000..77eb0aa
--- /dev/null
@@ -0,0 +1,60 @@
+#!/usr/bin/python
+
+# Test whether a client produces a correct connect with a will, username and password.
+
+# The client should connect to port 1888 with keepalive=60, clean session set,
+# client id 01-will-unpwd-set , will topic set to "will-topic", will payload
+# set to "will message", will qos=2, will retain not set, username set to
+# "oibvvwqw" and password set to "#'^2hg9a&nm38*us".
+
+import inspect
+import os
+import subprocess
+import socket
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+rc = 1
+keepalive = 60
+connect_packet = mosq_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")
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.settimeout(10)
+sock.bind(('', 1888))
+sock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        rc = 0
+
+    conn.close()
+finally:
+    client.terminate()
+    client.wait()
+    sock.close()
+
+exit(rc)
+
diff --git a/test/lib/02-subscribe-qos0.py b/test/lib/02-subscribe-qos0.py
new file mode 100755 (executable)
index 0000000..8e24440
--- /dev/null
@@ -0,0 +1,75 @@
+#!/usr/bin/python
+
+# Test whether a client sends a correct SUBSCRIBE to a topic with QoS 0.
+
+# The client should connect to port 1888 with keepalive=60, clean session set,
+# and client id subscribe-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 SUBSCRIBE
+# message to subscribe to topic "qos0/test" with QoS=0. If rc!=0, the client
+# should exit with an error.
+# Upon receiving the correct SUBSCRIBE message, the test will reply with a
+# SUBACK message with the accepted QoS set to 0. On receiving the SUBACK
+# message, the client should send a DISCONNECT message.
+
+import inspect
+import os
+import subprocess
+import socket
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+rc = 1
+keepalive = 60
+connect_packet = mosq_test.gen_connect("subscribe-qos0-test", keepalive=keepalive)
+connack_packet = mosq_test.gen_connack(rc=0)
+
+disconnect_packet = mosq_test.gen_disconnect()
+
+mid = 1
+subscribe_packet = mosq_test.gen_subscribe(mid, "qos0/test", 0)
+suback_packet = mosq_test.gen_suback(mid, 0)
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.settimeout(10)
+sock.bind(('', 1888))
+sock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        conn.send(connack_packet)
+
+        if mosq_test.expect_packet(conn, "subscribe", subscribe_packet):
+            conn.send(suback_packet)
+        
+            if mosq_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/02-subscribe-qos1.py b/test/lib/02-subscribe-qos1.py
new file mode 100755 (executable)
index 0000000..eeac5df
--- /dev/null
@@ -0,0 +1,75 @@
+#!/usr/bin/python
+
+# Test whether a client sends a correct SUBSCRIBE to a topic with QoS 1.
+
+# The client should connect to port 1888 with keepalive=60, clean session set,
+# and client id subscribe-qos1-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 SUBSCRIBE
+# message to subscribe to topic "qos1/test" with QoS=1. If rc!=0, the client
+# should exit with an error.
+# Upon receiving the correct SUBSCRIBE message, the test will reply with a
+# SUBACK message with the accepted QoS set to 1. On receiving the SUBACK
+# message, the client should send a DISCONNECT message.
+
+import inspect
+import os
+import subprocess
+import socket
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+rc = 1
+keepalive = 60
+connect_packet = mosq_test.gen_connect("subscribe-qos1-test", keepalive=keepalive)
+connack_packet = mosq_test.gen_connack(rc=0)
+
+disconnect_packet = mosq_test.gen_disconnect()
+
+mid = 1
+subscribe_packet = mosq_test.gen_subscribe(mid, "qos1/test", 1)
+suback_packet = mosq_test.gen_suback(mid, 1)
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.settimeout(10)
+sock.bind(('', 1888))
+sock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        conn.send(connack_packet)
+
+        if mosq_test.expect_packet(conn, "subscribe", subscribe_packet):
+            conn.send(suback_packet)
+        
+            if mosq_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/02-subscribe-qos2.py b/test/lib/02-subscribe-qos2.py
new file mode 100755 (executable)
index 0000000..580fece
--- /dev/null
@@ -0,0 +1,75 @@
+#!/usr/bin/python
+
+# Test whether a client sends a correct SUBSCRIBE to a topic with QoS 2.
+
+# The client should connect to port 1888 with keepalive=60, clean session set,
+# and client id subscribe-qos2-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 SUBSCRIBE
+# message to subscribe to topic "qos2/test" with QoS=2. If rc!=0, the client
+# should exit with an error.
+# Upon receiving the correct SUBSCRIBE message, the test will reply with a
+# SUBACK message with the accepted QoS set to 2. On receiving the SUBACK
+# message, the client should send a DISCONNECT message.
+
+import inspect
+import os
+import subprocess
+import socket
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+rc = 1
+keepalive = 60
+connect_packet = mosq_test.gen_connect("subscribe-qos2-test", keepalive=keepalive)
+connack_packet = mosq_test.gen_connack(rc=0)
+
+disconnect_packet = mosq_test.gen_disconnect()
+
+mid = 1
+subscribe_packet = mosq_test.gen_subscribe(mid, "qos2/test", 2)
+suback_packet = mosq_test.gen_suback(mid, 2)
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.settimeout(10)
+sock.bind(('', 1888))
+sock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        conn.send(connack_packet)
+
+        if mosq_test.expect_packet(conn, "subscribe", subscribe_packet):
+            conn.send(suback_packet)
+        
+            if mosq_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/02-unsubscribe.py b/test/lib/02-unsubscribe.py
new file mode 100755 (executable)
index 0000000..1d107b4
--- /dev/null
@@ -0,0 +1,65 @@
+#!/usr/bin/python
+
+# Test whether a client sends a correct UNSUBSCRIBE packet.
+
+import inspect
+import os
+import subprocess
+import socket
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+rc = 1
+keepalive = 60
+connect_packet = mosq_test.gen_connect("unsubscribe-test", keepalive=keepalive)
+connack_packet = mosq_test.gen_connack(rc=0)
+
+disconnect_packet = mosq_test.gen_disconnect()
+
+mid = 1
+unsubscribe_packet = mosq_test.gen_unsubscribe(mid, "unsubscribe/test")
+unsuback_packet = mosq_test.gen_unsuback(mid)
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.settimeout(10)
+sock.bind(('', 1888))
+sock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        conn.send(connack_packet)
+
+        if mosq_test.expect_packet(conn, "unsubscribe", unsubscribe_packet):
+            conn.send(unsuback_packet)
+            if mosq_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-b2c-qos1.py b/test/lib/03-publish-b2c-qos1.py
new file mode 100755 (executable)
index 0000000..8f47f07
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/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-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.
+# The test will send the client a PUBLISH message with topic
+# "pub/qos1/receive", payload of "message", QoS=1 and mid=123. The client
+# should handle this as per the spec by sending a PUBACK message.
+# The client should then exit with return code==0.
+
+import inspect
+import os
+import subprocess
+import socket
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+rc = 1
+keepalive = 60
+connect_packet = mosq_test.gen_connect("publish-qos1-test", keepalive=keepalive)
+connack_packet = mosq_test.gen_connack(rc=0)
+
+disconnect_packet = mosq_test.gen_disconnect()
+
+mid = 123
+publish_packet = mosq_test.gen_publish("pub/qos1/receive", qos=1, mid=mid, payload="message")
+puback_packet = mosq_test.gen_puback(mid)
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.settimeout(10)
+sock.bind(('', 1888))
+sock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        conn.send(connack_packet)
+        conn.send(publish_packet)
+
+        if mosq_test.expect_packet(conn, "puback", puback_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-b2c-qos2.py b/test/lib/03-publish-b2c-qos2.py
new file mode 100755 (executable)
index 0000000..51e64fe
--- /dev/null
@@ -0,0 +1,91 @@
+#!/usr/bin/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 10 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 inspect
+import os
+import subprocess
+import socket
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+rc = 1
+keepalive = 60
+connect_packet = mosq_test.gen_connect("publish-qos2-test", keepalive=keepalive)
+connack_packet = mosq_test.gen_connack(rc=0)
+
+disconnect_packet = mosq_test.gen_disconnect()
+
+mid = 13423
+publish_packet = mosq_test.gen_publish("pub/qos2/receive", qos=2, mid=mid, payload="message")
+pubrec_packet = mosq_test.gen_pubrec(mid)
+pubrel_packet = mosq_test.gen_pubrel(mid)
+pubcomp_packet = mosq_test.gen_pubcomp(mid)
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.settimeout(10)
+sock.bind(('', 1888))
+sock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        conn.send(connack_packet)
+        conn.send(publish_packet)
+
+        if mosq_test.expect_packet(conn, "pubrec", pubrec_packet):
+            # Should be repeated due to timeout
+            if mosq_test.expect_packet(conn, "pubrec", pubrec_packet):
+                conn.send(pubrel_packet)
+
+                if mosq_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
new file mode 100755 (executable)
index 0000000..33db504
--- /dev/null
@@ -0,0 +1,77 @@
+#!/usr/bin/python
+
+# Test whether a client sends a correct PUBLISH to a topic with QoS 1, then responds correctly to a disconnect.
+
+import inspect
+import os
+import subprocess
+import socket
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+rc = 1
+keepalive = 60
+connect_packet = mosq_test.gen_connect("publish-qos1-test", keepalive=keepalive)
+connack_packet = mosq_test.gen_connack(rc=0)
+
+disconnect_packet = mosq_test.gen_disconnect()
+
+mid = 1
+publish_packet = mosq_test.gen_publish("pub/qos1/test", qos=1, mid=mid, payload="message")
+publish_packet_dup = mosq_test.gen_publish("pub/qos1/test", qos=1, mid=mid, payload="message", dup=True)
+puback_packet = mosq_test.gen_puback(mid)
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.settimeout(10)
+sock.bind(('', 1888))
+sock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(15)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        conn.send(connack_packet)
+
+        if mosq_test.expect_packet(conn, "publish", publish_packet):
+            # Disconnect client. It should reconnect.
+            conn.close()
+
+            (conn, address) = sock.accept()
+            conn.settimeout(15)
+
+            if mosq_test.expect_packet(conn, "connect", connect_packet):
+                conn.send(connack_packet)
+
+                if mosq_test.expect_packet(conn, "retried publish", publish_packet_dup):
+                    conn.send(puback_packet)
+
+                    if mosq_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-qos1-timeout.py b/test/lib/03-publish-c2b-qos1-timeout.py
new file mode 100755 (executable)
index 0000000..46d37ed
--- /dev/null
@@ -0,0 +1,83 @@
+#!/usr/bin/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 10 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 inspect
+import os
+import subprocess
+import socket
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+rc = 1
+keepalive = 60
+connect_packet = mosq_test.gen_connect("publish-qos1-test", keepalive=keepalive)
+connack_packet = mosq_test.gen_connack(rc=0)
+
+disconnect_packet = mosq_test.gen_disconnect()
+
+mid = 1
+publish_packet = mosq_test.gen_publish("pub/qos1/test", qos=1, mid=mid, payload="message")
+publish_packet_dup = mosq_test.gen_publish("pub/qos1/test", qos=1, mid=mid, payload="message", dup=True)
+puback_packet = mosq_test.gen_puback(mid)
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.settimeout(10)
+sock.bind(('', 1888))
+sock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        conn.send(connack_packet)
+
+        if mosq_test.expect_packet(conn, "publish", publish_packet):
+            # Delay for > 3 seconds (message retry time)
+
+            if mosq_test.expect_packet(conn, "dup publish", publish_packet_dup):
+                conn.send(puback_packet)
+
+                if mosq_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
new file mode 100755 (executable)
index 0000000..d92b482
--- /dev/null
@@ -0,0 +1,96 @@
+#!/usr/bin/python
+
+# Test whether a client sends a correct PUBLISH to a topic with QoS 2 and responds to a disconnect.
+
+import inspect
+import os
+import subprocess
+import socket
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+rc = 1
+keepalive = 60
+connect_packet = mosq_test.gen_connect("publish-qos2-test", keepalive=keepalive)
+connack_packet = mosq_test.gen_connack(rc=0)
+
+disconnect_packet = mosq_test.gen_disconnect()
+
+mid = 1
+publish_packet = mosq_test.gen_publish("pub/qos2/test", qos=2, mid=mid, payload="message")
+publish_dup_packet = mosq_test.gen_publish("pub/qos2/test", qos=2, mid=mid, payload="message", dup=True)
+pubrec_packet = mosq_test.gen_pubrec(mid)
+pubrel_packet = mosq_test.gen_pubrel(mid)
+pubrel_dup_packet = mosq_test.gen_pubrel(mid, dup=True)
+pubcomp_packet = mosq_test.gen_pubcomp(mid)
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.settimeout(10)
+sock.bind(('', 1888))
+sock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        conn.send(connack_packet)
+
+        if mosq_test.expect_packet(conn, "publish", publish_packet):
+            # Disconnect client. It should reconnect.
+            conn.close()
+
+            (conn, address) = sock.accept()
+            conn.settimeout(15)
+
+            if mosq_test.expect_packet(conn, "connect", connect_packet):
+                conn.send(connack_packet)
+
+                if mosq_test.expect_packet(conn, "retried publish", publish_dup_packet):
+                    conn.send(pubrec_packet)
+
+                    if mosq_test.expect_packet(conn, "pubrel", pubrel_packet):
+                        # Disconnect client. It should reconnect.
+                        conn.close()
+
+                        (conn, address) = sock.accept()
+                        conn.settimeout(15)
+
+                        # Complete connection and message flow.
+                        if mosq_test.expect_packet(conn, "connect", connect_packet):
+                            conn.send(connack_packet)
+
+                            if mosq_test.expect_packet(conn, "2nd retried publish", publish_dup_packet):
+                                conn.send(pubrec_packet)
+
+                                if mosq_test.expect_packet(conn, "pubrel", pubrel_packet):
+                                    conn.send(pubcomp_packet)
+
+                                    if mosq_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-timeout.py b/test/lib/03-publish-c2b-qos2-timeout.py
new file mode 100755 (executable)
index 0000000..8b0129c
--- /dev/null
@@ -0,0 +1,94 @@
+#!/usr/bin/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 10 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 inspect
+import os
+import subprocess
+import socket
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+rc = 1
+keepalive = 60
+connect_packet = mosq_test.gen_connect("publish-qos2-test", keepalive=keepalive)
+connack_packet = mosq_test.gen_connack(rc=0)
+
+disconnect_packet = mosq_test.gen_disconnect()
+
+mid = 1
+publish_packet = mosq_test.gen_publish("pub/qos2/test", qos=2, mid=mid, payload="message")
+publish_dup_packet = mosq_test.gen_publish("pub/qos2/test", qos=2, mid=mid, payload="message", dup=True)
+pubrec_packet = mosq_test.gen_pubrec(mid)
+pubrel_packet = mosq_test.gen_pubrel(mid)
+pubrel_dup_packet = mosq_test.gen_pubrel(mid, dup=True)
+pubcomp_packet = mosq_test.gen_pubcomp(mid)
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.settimeout(10)
+sock.bind(('', 1888))
+sock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        conn.send(connack_packet)
+
+        if mosq_test.expect_packet(conn, "publish", publish_packet):
+            # Delay for > 3 seconds (message retry time)
+
+            if mosq_test.expect_packet(conn, "dup publish", publish_dup_packet):
+                conn.send(pubrec_packet)
+                
+                if mosq_test.expect_packet(conn, "pubrel", pubrel_packet):
+                    if mosq_test.expect_packet(conn, "dup pubrel", pubrel_dup_packet):
+                        conn.send(pubcomp_packet)
+
+                        if mosq_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-qos0-no-payload.py b/test/lib/03-publish-qos0-no-payload.py
new file mode 100755 (executable)
index 0000000..6b16663
--- /dev/null
@@ -0,0 +1,69 @@
+#!/usr/bin/python
+
+# Test whether a client sends a correct PUBLISH to a topic with QoS 0 and no payload.
+
+# The client should connect to port 1888 with keepalive=60, clean session set,
+# and client id publish-qos0-test-np
+# 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/no-payload/test" with zero length payload 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 inspect
+import os
+import subprocess
+import socket
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+rc = 1
+keepalive = 60
+connect_packet = mosq_test.gen_connect("publish-qos0-test-np", keepalive=keepalive)
+connack_packet = mosq_test.gen_connack(rc=0)
+
+publish_packet = mosq_test.gen_publish("pub/qos0/no-payload/test", qos=0)
+
+disconnect_packet = mosq_test.gen_disconnect()
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.settimeout(10)
+sock.bind(('', 1888))
+sock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        conn.send(connack_packet)
+
+        if mosq_test.expect_packet(conn, "publish", publish_packet):
+            if mosq_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-qos0.py b/test/lib/03-publish-qos0.py
new file mode 100755 (executable)
index 0000000..db7e578
--- /dev/null
@@ -0,0 +1,69 @@
+#!/usr/bin/python
+
+# Test whether a client sends a correct PUBLISH to a topic with QoS 0.
+
+# The client should connect to port 1888 with keepalive=60, clean session set,
+# and client id publish-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 inspect
+import os
+import subprocess
+import socket
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+rc = 1
+keepalive = 60
+connect_packet = mosq_test.gen_connect("publish-qos0-test", keepalive=keepalive)
+connack_packet = mosq_test.gen_connack(rc=0)
+
+publish_packet = mosq_test.gen_publish("pub/qos0/test", qos=0, payload="message")
+
+disconnect_packet = mosq_test.gen_disconnect()
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.settimeout(10)
+sock.bind(('', 1888))
+sock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        conn.send(connack_packet)
+
+        if mosq_test.expect_packet(conn, "publish", publish_packet):
+            if mosq_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/04-retain-qos0.py b/test/lib/04-retain-qos0.py
new file mode 100755 (executable)
index 0000000..b0c6e24
--- /dev/null
@@ -0,0 +1,59 @@
+#!/usr/bin/python
+
+# Test whether a client sends a correct retained PUBLISH to a topic with QoS 0.
+
+import inspect
+import os
+import subprocess
+import socket
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+rc = 1
+keepalive = 60
+mid = 16
+connect_packet = mosq_test.gen_connect("retain-qos0-test", keepalive=keepalive)
+connack_packet = mosq_test.gen_connack(rc=0)
+
+publish_packet = mosq_test.gen_publish("retain/qos0/test", qos=0, payload="retained message", retain=True)
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.settimeout(10)
+sock.bind(('', 1888))
+sock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = sock.accept()
+    conn.settimeout(10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        conn.send(connack_packet)
+
+        if mosq_test.expect_packet(conn, "publish", publish_packet):
+            rc = 0
+        
+    conn.close()
+finally:
+    client.terminate()
+    client.wait()
+    sock.close()
+
+exit(rc)
diff --git a/test/lib/08-ssl-bad-cacert.py b/test/lib/08-ssl-bad-cacert.py
new file mode 100755 (executable)
index 0000000..13fed1c
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/python
+
+import inspect
+import os
+import subprocess
+import socket
+import ssl
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+if sys.version < '2.7':
+    print("WARNING: SSL not supported on Python 2.6")
+    exit(0)
+
+rc = 1
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+
+client = subprocess.Popen(client_args, env=env)
+client.wait()
+
+rc = client.returncode
+
+exit(rc)
diff --git a/test/lib/08-ssl-connect-cert-auth.py b/test/lib/08-ssl-connect-cert-auth.py
new file mode 100755 (executable)
index 0000000..8554713
--- /dev/null
@@ -0,0 +1,73 @@
+#!/usr/bin/python
+
+# Test whether a client produces a correct connect and subsequent disconnect when using SSL.
+# Client must provide a certificate.
+
+# 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/test-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 inspect
+import os
+import subprocess
+import socket
+import ssl
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+if sys.version < '2.7':
+    print("WARNING: SSL not supported on Python 2.6")
+    exit(0)
+
+rc = 1
+keepalive = 60
+connect_packet = mosq_test.gen_connect("08-ssl-connect-crt-auth", keepalive=keepalive)
+connack_packet = mosq_test.gen_connack(rc=0)
+disconnect_packet = mosq_test.gen_disconnect()
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ssock = ssl.wrap_socket(sock, ca_certs="../ssl/test-ca.crt",
+        keyfile="../ssl/server.key", certfile="../ssl/server.crt",
+        server_side=True, ssl_version=ssl.PROTOCOL_TLSv1, cert_reqs=ssl.CERT_REQUIRED)
+ssock.settimeout(10)
+ssock.bind(('', 1888))
+ssock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = ssock.accept()
+    conn.settimeout(10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        conn.send(connack_packet)
+
+        if mosq_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-no-auth.py b/test/lib/08-ssl-connect-no-auth.py
new file mode 100755 (executable)
index 0000000..716c59d
--- /dev/null
@@ -0,0 +1,70 @@
+#!/usr/bin/python
+
+# Test whether a client produces a correct connect and subsequent disconnect when using SSL.
+
+# The client should connect to port 1888 with keepalive=60, clean session set,
+# and client id 08-ssl-connect-no-auth
+# It should use the CA certificate ssl/test-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 inspect
+import os
+import subprocess
+import socket
+import ssl
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+import mosq_test
+
+if sys.version < '2.7':
+    print("WARNING: SSL not supported on Python 2.6")
+    exit(0)
+
+rc = 1
+keepalive = 60
+connect_packet = mosq_test.gen_connect("08-ssl-connect-no-auth", keepalive=keepalive)
+connack_packet = mosq_test.gen_connack(rc=0)
+disconnect_packet = mosq_test.gen_disconnect()
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ssock = ssl.wrap_socket(sock, ca_certs="../ssl/test-ca.crt", keyfile="../ssl/server.key", certfile="../ssl/server.crt", server_side=True, ssl_version=ssl.PROTOCOL_TLSv1)
+ssock.settimeout(10)
+ssock.bind(('', 1888))
+ssock.listen(5)
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+client = subprocess.Popen(client_args, env=env)
+
+try:
+    (conn, address) = ssock.accept()
+    conn.settimeout(10)
+
+    if mosq_test.expect_packet(conn, "connect", connect_packet):
+        conn.send(connack_packet)
+
+        if mosq_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/09-util-topic-matching.py b/test/lib/09-util-topic-matching.py
new file mode 100755 (executable)
index 0000000..57c989e
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/python
+
+import inspect
+import os
+import subprocess
+import sys
+import time
+
+# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"..")))
+if cmd_subfolder not in sys.path:
+    sys.path.insert(0, cmd_subfolder)
+
+rc = 1
+
+client_args = sys.argv[1:]
+env = dict(os.environ)
+env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp'
+try:
+    pp = env['PYTHONPATH']
+except KeyError:
+    pp = ''
+env['PYTHONPATH'] = '../../lib/python:'+pp
+
+client = subprocess.Popen(client_args, env=env)
+client.wait()
+exit(client.returncode)
diff --git a/test/lib/Makefile b/test/lib/Makefile
new file mode 100644 (file)
index 0000000..a78b68b
--- /dev/null
@@ -0,0 +1,45 @@
+.PHONY: all test test-compile test-compile-c test-compile-cpp c cpp python python3
+.NOTPARALLEL:
+
+LD_LIBRARY_PATH=../../lib
+
+all :
+
+test : c cpp python python3
+
+test-compile : test-compile-c test-compile-cpp
+
+test-compile-c : 
+       $(MAKE) -C c
+
+test-compile-cpp : 
+       $(MAKE) -C cpp
+
+c cpp python python3 : test-compile
+       ./01-con-discon-success.py $@/01-con-discon-success.test
+       ./01-will-set.py $@/01-will-set.test
+       ./01-unpwd-set.py $@/01-unpwd-set.test
+       ./01-will-unpwd-set.py $@/01-will-unpwd-set.test
+       ./01-no-clean-session.py $@/01-no-clean-session.test
+       ./01-keepalive-pingreq.py $@/01-keepalive-pingreq.test
+       ./02-subscribe-qos0.py $@/02-subscribe-qos0.test
+       ./02-subscribe-qos1.py $@/02-subscribe-qos1.test
+       ./02-subscribe-qos2.py $@/02-subscribe-qos2.test
+       ./02-unsubscribe.py $@/02-unsubscribe.test
+       ./03-publish-qos0.py $@/03-publish-qos0.test
+       ./03-publish-qos0-no-payload.py $@/03-publish-qos0-no-payload.test
+       ./03-publish-c2b-qos1-timeout.py $@/03-publish-c2b-qos1-timeout.test
+       ./03-publish-c2b-qos1-disconnect.py $@/03-publish-c2b-qos1-disconnect.test
+       ./03-publish-c2b-qos2-timeout.py $@/03-publish-c2b-qos2-timeout.test
+       ./03-publish-c2b-qos2-disconnect.py $@/03-publish-c2b-qos2-disconnect.test
+       ./03-publish-b2c-qos1.py $@/03-publish-b2c-qos1.test
+       ./03-publish-b2c-qos2.py $@/03-publish-b2c-qos2.test
+       ./04-retain-qos0.py $@/04-retain-qos0.test
+       ./08-ssl-connect-no-auth.py $@/08-ssl-connect-no-auth.test
+       ./08-ssl-connect-cert-auth.py $@/08-ssl-connect-cert-auth.test
+       ./08-ssl-bad-cacert.py $@/08-ssl-bad-cacert.test
+       ./09-util-topic-matching.py $@/09-util-topic-matching.test
+
+clean : 
+       $(MAKE) -C c clean
+       $(MAKE) -C cpp clean
diff --git a/test/lib/python/01-con-discon-success.test b/test/lib/python/01-con-discon-success.test
new file mode 100755 (executable)
index 0000000..50a45f3
--- /dev/null
@@ -0,0 +1,33 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+    else:
+        mosq.disconnect()
+
+def on_disconnect(mosq, obj, rc):
+    mosq.loop()
+    obj = rc
+
+
+run = -1
+mosq = mosquitto.Mosquitto("01-con-discon-success", run)
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python/01-keepalive-pingreq.test b/test/lib/python/01-keepalive-pingreq.test
new file mode 100755 (executable)
index 0000000..9aa289b
--- /dev/null
@@ -0,0 +1,25 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+
+run = -1
+mosq = mosquitto.Mosquitto("01-keepalive-pingreq")
+mosq.on_connect = on_connect
+
+mosq.connect("localhost", 1888, 4)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python/01-no-clean-session.test b/test/lib/python/01-no-clean-session.test
new file mode 100755 (executable)
index 0000000..b006dc7
--- /dev/null
@@ -0,0 +1,12 @@
+#!/usr/bin/python
+
+import mosquitto
+
+mosq = mosquitto.Mosquitto("01-no-clean-session", False)
+
+run = -1
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python/01-unpwd-set.test b/test/lib/python/01-unpwd-set.test
new file mode 100755 (executable)
index 0000000..b55dab8
--- /dev/null
@@ -0,0 +1,13 @@
+#!/usr/bin/python
+
+import mosquitto
+
+mosq = mosquitto.Mosquitto("01-unpwd-set")
+
+run = -1
+mosq.username_pw_set("uname", ";'[08gn=#")
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python/01-will-set.test b/test/lib/python/01-will-set.test
new file mode 100755 (executable)
index 0000000..75803a3
--- /dev/null
@@ -0,0 +1,13 @@
+#!/usr/bin/python
+
+import mosquitto
+
+mosq = mosquitto.Mosquitto("01-will-set")
+
+run = -1
+mosq.will_set("topic/on/unexpected/disconnect", "will message", 1, True)
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python/01-will-unpwd-set.test b/test/lib/python/01-will-unpwd-set.test
new file mode 100755 (executable)
index 0000000..0379b36
--- /dev/null
@@ -0,0 +1,14 @@
+#!/usr/bin/python
+
+import mosquitto
+
+mosq = mosquitto.Mosquitto("01-will-unpwd-set")
+
+run = -1
+mosq.username_pw_set("oibvvwqw", "#'^2hg9a&nm38*us")
+mosq.will_set("will-topic", "will message", 2, False)
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python/02-subscribe-qos0.test b/test/lib/python/02-subscribe-qos0.test
new file mode 100755 (executable)
index 0000000..2f65055
--- /dev/null
@@ -0,0 +1,35 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+    else:
+        mosq.subscribe("qos0/test", 0)
+
+def on_disconnect(mosq, obj, rc):
+    obj = rc
+
+def on_subscribe(mosq, obj, mid, granted_qos):
+    mosq.disconnect()
+
+run = -1
+mosq = mosquitto.Mosquitto("subscribe-qos0-test", run)
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+mosq.on_subscribe = on_subscribe
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python/02-subscribe-qos1.test b/test/lib/python/02-subscribe-qos1.test
new file mode 100755 (executable)
index 0000000..d00cc13
--- /dev/null
@@ -0,0 +1,35 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+    else:
+        mosq.subscribe("qos1/test", 1)
+
+def on_disconnect(mosq, obj, rc):
+    obj = rc
+
+def on_subscribe(mosq, obj, mid, granted_qos):
+    mosq.disconnect()
+
+run = -1
+mosq = mosquitto.Mosquitto("subscribe-qos1-test", run)
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+mosq.on_subscribe = on_subscribe
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python/02-subscribe-qos2.test b/test/lib/python/02-subscribe-qos2.test
new file mode 100755 (executable)
index 0000000..83a60b9
--- /dev/null
@@ -0,0 +1,35 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+    else:
+        mosq.subscribe("qos2/test", 2)
+
+def on_disconnect(mosq, obj, rc):
+    obj = rc
+
+def on_subscribe(mosq, obj, mid, granted_qos):
+    mosq.disconnect()
+
+run = -1
+mosq = mosquitto.Mosquitto("subscribe-qos2-test", run)
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+mosq.on_subscribe = on_subscribe
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python/02-unsubscribe.test b/test/lib/python/02-unsubscribe.test
new file mode 100755 (executable)
index 0000000..e3b5ecc
--- /dev/null
@@ -0,0 +1,35 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+    else:
+        mosq.unsubscribe("unsubscribe/test")
+
+def on_disconnect(mosq, obj, rc):
+    obj = rc
+
+def on_unsubscribe(mosq, obj, mid):
+    mosq.disconnect()
+
+run = -1
+mosq = mosquitto.Mosquitto("unsubscribe-test", run)
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+mosq.on_unsubscribe = on_unsubscribe
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python/03-publish-b2c-qos1.test b/test/lib/python/03-publish-b2c-qos1.test
new file mode 100755 (executable)
index 0000000..3b93c3a
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+def on_message(mosq, obj, msg):
+    if msg.mid != 123:
+        print("Invalid mid: ("+str(msg.mid)+")")
+        exit(1)
+    if msg.topic != "pub/qos1/receive":
+        print("Invalid topic: ("+str(msg.topic)+")")
+        exit(1)
+    if msg.payload != "message":
+        print("Invalid payload: ("+str(msg.payload)+")")
+        exit(1)
+    if msg.qos != 1:
+        print("Invalid qos: ("+str(msg.qos)+")")
+        exit(1)
+    if msg.retain != False:
+        print("Invalid retain: ("+str(msg.retain)+")")
+        exit(1)
+    exit(0)
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        print("Connect failed ("+str(rc)+")")
+        exit(rc)
+
+mosq = mosquitto.Mosquitto("publish-qos1-test")
+mosq.message_retry_set(3)
+mosq.on_connect = on_connect
+mosq.on_message = on_message
+
+mosq.connect("localhost", 1888)
+rc = 0
+while rc == 0:
+    rc = mosq.loop()
+print("rc: "+str(rc))
+exit(1)
diff --git a/test/lib/python/03-publish-b2c-qos2.test b/test/lib/python/03-publish-b2c-qos2.test
new file mode 100755 (executable)
index 0000000..74e9df0
--- /dev/null
@@ -0,0 +1,48 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_message(mosq, obj, msg):
+    global run
+    if msg.mid != 13423:
+        print("Invalid mid ("+str(msg.mid)+")")
+        exit(1)
+    if msg.topic != "pub/qos2/receive":
+        print("Invalid topic ("+str(msg.topic)+")")
+        exit(1)
+    if msg.payload != "message":
+        print("Invalid payload ("+str(msg.payload)+")")
+        exit(1)
+    if msg.qos != 2:
+        print("Invalid qos ("+str(msg.qos)+")")
+        exit(1)
+    if msg.retain != False:
+        print("Invalid retain ("+str(msg.retain)+")")
+        exit(1)
+
+    run = 0
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+
+run = -1
+mosq = mosquitto.Mosquitto("publish-qos2-test", run)
+mosq.message_retry_set(3)
+mosq.on_connect = on_connect
+mosq.on_message = on_message
+
+mosq.connect("localhost", 1888)
+rc = 0
+while run == -1 and rc == 0:
+    rc = mosq.loop(0.3)
+
+exit(run)
diff --git a/test/lib/python/03-publish-c2b-qos1-disconnect.test b/test/lib/python/03-publish-c2b-qos1-disconnect.test
new file mode 100755 (executable)
index 0000000..5da5623
--- /dev/null
@@ -0,0 +1,48 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+sent_mid = -1
+
+def on_connect(mosq, obj, rc):
+    global sent_mid
+    if rc != 0:
+        exit(rc)
+    else:
+        if sent_mid == -1:
+            res = mosq.publish("pub/qos1/test", "message", 1)
+            sent_mid = res[1]
+
+def on_disconnect(mosq, obj, rc):
+    if rc == 1:
+        mosq.reconnect()
+    else:
+        run = 0
+
+def on_publish(mosq, obj, mid):
+    global sent_mid
+    if mid == sent_mid:
+        mosq.disconnect()
+    else:
+        exit(1)
+
+run = -1
+mosq = mosquitto.Mosquitto("publish-qos1-test", run)
+mosq.message_retry_set(3)
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+mosq.on_publish = on_publish
+
+mosq.connect("localhost", 1888)
+rc = 0
+while run == -1:
+    rc = mosq.loop()
+
+exit(run)
diff --git a/test/lib/python/03-publish-c2b-qos1-timeout.test b/test/lib/python/03-publish-c2b-qos1-timeout.test
new file mode 100755 (executable)
index 0000000..30f1b33
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+sent_mid = -1
+
+def on_connect(mosq, obj, rc):
+    global sent_mid
+    if rc != 0:
+        exit(rc)
+    else:
+        res = mosq.publish("pub/qos1/test", "message", 1)
+        sent_mid = res[1]
+
+def on_disconnect(mosq, obj, rc):
+    run = 0
+
+def on_publish(mosq, obj, mid):
+    global sent_mid
+    if mid == sent_mid:
+        mosq.disconnect()
+    else:
+        exit(1)
+
+run = -1
+mosq = mosquitto.Mosquitto("publish-qos1-test", run)
+mosq.message_retry_set(3)
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+mosq.on_publish = on_publish
+
+mosq.connect("localhost", 1888)
+rc = 0
+while run == -1 and rc == 0:
+    rc = mosq.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
new file mode 100755 (executable)
index 0000000..f394c61
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+first_connection = 1
+
+def on_connect(mosq, obj, rc):
+    global first_connection
+    if rc != 0:
+        exit(rc)
+    else:
+        if first_connection == 1:
+            mosq.publish("pub/qos2/test", "message", 2)
+            first_connection = 0
+
+def on_disconnect(mosq, obj, rc):
+    if rc == 1:
+        mosq.reconnect()
+    else:
+        run = 0
+
+def on_publish(mosq, obj, mid):
+    mosq.disconnect()
+
+run = -1
+mosq = mosquitto.Mosquitto("publish-qos2-test", run)
+mosq.message_retry_set(3)
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+mosq.on_publish = on_publish
+
+mosq.connect("localhost", 1888)
+rc = 0
+while run == -1:
+    rc = mosq.loop()
+
+exit(run)
diff --git a/test/lib/python/03-publish-c2b-qos2-timeout.test b/test/lib/python/03-publish-c2b-qos2-timeout.test
new file mode 100755 (executable)
index 0000000..940c594
--- /dev/null
@@ -0,0 +1,37 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+    else:
+        mosq.publish("pub/qos2/test", "message", 2)
+
+def on_disconnect(mosq, obj, rc):
+    run = 0
+
+def on_publish(mosq, obj, mid):
+    mosq.disconnect()
+
+run = -1
+mosq = mosquitto.Mosquitto("publish-qos2-test", run)
+mosq.message_retry_set(3)
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+mosq.on_publish = on_publish
+
+mosq.connect("localhost", 1888)
+rc = 0
+while run == -1 and rc == 0:
+    rc = mosq.loop()
+
+exit(run)
diff --git a/test/lib/python/03-publish-qos0-no-payload.test b/test/lib/python/03-publish-qos0-no-payload.test
new file mode 100755 (executable)
index 0000000..7730e52
--- /dev/null
@@ -0,0 +1,36 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+sent_mid = -1
+
+def on_connect(mosq, obj, rc):
+    global sent_mid
+    if rc != 0:
+        exit(rc)
+    else:
+        (res, sent_mid) = mosq.publish("pub/qos0/no-payload/test")
+
+def on_publish(mosq, obj, mid):
+    global sent_mid, run
+    if sent_mid == mid:
+        mosq.disconnect()
+        run = 0
+
+run = -1
+mosq = mosquitto.Mosquitto("publish-qos0-test-np", run)
+mosq.on_connect = on_connect
+mosq.on_publish = on_publish
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python/03-publish-qos0.test b/test/lib/python/03-publish-qos0.test
new file mode 100755 (executable)
index 0000000..c446a08
--- /dev/null
@@ -0,0 +1,36 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+sent_mid = -1
+
+def on_connect(mosq, obj, rc):
+    global sent_mid
+    if rc != 0:
+        exit(rc)
+    else:
+        res = mosq.publish("pub/qos0/test", "message")
+        sent_mid = res[1]
+
+def on_publish(mosq, obj, mid):
+    global sent_mid, run
+    if sent_mid == mid:
+        mosq.disconnect()
+
+run = -1
+mosq = mosquitto.Mosquitto("publish-qos0-test", run)
+mosq.on_connect = on_connect
+mosq.on_publish = on_publish
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python/04-retain-qos0.test b/test/lib/python/04-retain-qos0.test
new file mode 100755 (executable)
index 0000000..516538d
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+    else:
+        mosq.publish("retain/qos0/test", "retained message", 0, True)
+
+run = -1
+mosq = mosquitto.Mosquitto("retain-qos0-test", run)
+mosq.on_connect = on_connect
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python/08-ssl-bad-cacert.test b/test/lib/python/08-ssl-bad-cacert.test
new file mode 100755 (executable)
index 0000000..5b8bfb2
--- /dev/null
@@ -0,0 +1,23 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+if sys.version < '2.7':
+    print("WARNING: SSL/TLS not supported on Python 2.6")
+    exit(0)
+
+rc = 1
+mosq = mosquitto.Mosquitto("08-ssl-bad-cacert")
+try:
+    mosq.tls_set("this/file/doesnt/exist")
+except IOError as err:
+    rc = 0
+
+exit(rc)
diff --git a/test/lib/python/08-ssl-connect-cert-auth.test b/test/lib/python/08-ssl-connect-cert-auth.test
new file mode 100755 (executable)
index 0000000..d2c59c6
--- /dev/null
@@ -0,0 +1,37 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+if sys.version < '2.7':
+    print("WARNING: SSL/TLS not supported on Python 2.6")
+    exit(0)
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+    else:
+        mosq.disconnect()
+
+def on_disconnect(mosq, obj, rc):
+    mosq.loop()
+    obj = rc
+
+
+run = -1
+mosq = mosquitto.Mosquitto("08-ssl-connect-crt-auth", run)
+mosq.tls_set("../ssl/test-ca.crt", "../ssl/client.crt", "../ssl/client.key")
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python/08-ssl-connect-no-auth.test b/test/lib/python/08-ssl-connect-no-auth.test
new file mode 100755 (executable)
index 0000000..6baea37
--- /dev/null
@@ -0,0 +1,37 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+if sys.version < '2.7':
+    print("WARNING: SSL/TLS not supported on Python 2.6")
+    exit(0)
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+    else:
+        mosq.disconnect()
+
+def on_disconnect(mosq, obj, rc):
+    mosq.loop()
+    obj = rc
+
+
+run = -1
+mosq = mosquitto.Mosquitto("08-ssl-connect-no-auth", run)
+mosq.tls_set("../ssl/test-ca.crt")
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python/08-ssl-fake-cacert.test b/test/lib/python/08-ssl-fake-cacert.test
new file mode 100755 (executable)
index 0000000..1c475ba
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+import ssl
+
+import mosquitto
+
+if sys.version < '2.7':
+    print("WARNING: SSL/TLS not supported on Python 2.6")
+    exit(0)
+
+def on_connect(mosq, obj, rc):
+    exit(1)
+
+mosq = mosquitto.Mosquitto("08-ssl-fake-cacert")
+mosq.tls_set("../ssl/fake-ca.crt", "../ssl/client.crt", "../ssl/client.key")
+mosq.on_connect = on_connect
+
+try:
+    mosq.connect("localhost", 1888)
+except ssl.SSLError as msg:
+    if msg.errno == 1 and "certificate verify failed" in msg.strerror:
+        exit(0)
+    else:
+        exit(1)
+else:
+    exit(1)
diff --git a/test/lib/python/09-util-topic-matching.test b/test/lib/python/09-util-topic-matching.test
new file mode 100755 (executable)
index 0000000..11fd81b
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/python
+
+# Copyright (c) 2012 Roger Light <roger@atchoo.org>
+# All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+# 
+# 1. Redistributions of source code must retain the above copyright notice,
+#   this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+# 3. Neither the name of mosquitto nor the names of its
+#   contributors may be used to endorse or promote products derived from
+#   this software without specific prior written permission.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) 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 OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import mosquitto
+import sys
+
+if mosquitto.topic_matches_sub("foo/bar", "foo/bar") == False:
+    print("ERROR: foo/bar : foo/bar")
+    sys.exit(1)
+if mosquitto.topic_matches_sub("foo/+", "foo/bar") == False:
+    print("ERROR: foo/+ : foo/bar")
+    sys.exit(1)
+if mosquitto.topic_matches_sub("foo/+/baz", "foo/bar/baz") == False:
+    print("ERROR: foo/+/baz : foo/bar/baz")
+    sys.exit(1)
+if mosquitto.topic_matches_sub("foo/+/#", "foo/bar/baz") == False:
+    print("ERROR: foo/+/# : foo/bar/baz")
+    sys.exit(1)
+if mosquitto.topic_matches_sub("#", "foo/bar/baz") == False:
+    print("ERROR: # : foo/bar/baz")
+    sys.exit(1)
+
+if mosquitto.topic_matches_sub("foo/bar", "foo") == True:
+    print("ERROR: foo/bar : foo")
+    sys.exit(1)
+if mosquitto.topic_matches_sub("foo/+", "foo/bar/baz") == True:
+    print("ERROR: foo/+ : foo/bar/baz")
+    sys.exit(1)
+if mosquitto.topic_matches_sub("foo/+/baz", "foo/bar/bar") == True:
+    print("ERROR: foo/+/baz : foo/bar/bar")
+    sys.exit(1)
+if mosquitto.topic_matches_sub("foo/+/#", "fo2/bar/baz") == True:
+    print("ERROR: foo/+/# : foo/bar/baz")
+    sys.exit(1)
+
+sys.exit(0)
diff --git a/test/lib/python3/01-con-discon-success.test b/test/lib/python3/01-con-discon-success.test
new file mode 100755 (executable)
index 0000000..e517856
--- /dev/null
@@ -0,0 +1,33 @@
+#!/usr/bin/python3
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+    else:
+        mosq.disconnect()
+
+def on_disconnect(mosq, obj, rc):
+    mosq.loop()
+    obj = rc
+
+
+run = -1
+mosq = mosquitto.Mosquitto("01-con-discon-success", run)
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python3/01-keepalive-pingreq.test b/test/lib/python3/01-keepalive-pingreq.test
new file mode 100755 (executable)
index 0000000..f7b0f30
--- /dev/null
@@ -0,0 +1,25 @@
+#!/usr/bin/python3
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+
+run = -1
+mosq = mosquitto.Mosquitto("01-keepalive-pingreq")
+mosq.on_connect = on_connect
+
+mosq.connect("localhost", 1888, 4)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python3/01-no-clean-session.test b/test/lib/python3/01-no-clean-session.test
new file mode 100755 (executable)
index 0000000..17d1aad
--- /dev/null
@@ -0,0 +1,12 @@
+#!/usr/bin/python3
+
+import mosquitto
+
+mosq = mosquitto.Mosquitto("01-no-clean-session", False)
+
+run = -1
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python3/01-unpwd-set.test b/test/lib/python3/01-unpwd-set.test
new file mode 100755 (executable)
index 0000000..593636f
--- /dev/null
@@ -0,0 +1,13 @@
+#!/usr/bin/python3
+
+import mosquitto
+
+mosq = mosquitto.Mosquitto("01-unpwd-set")
+
+run = -1
+mosq.username_pw_set("uname", ";'[08gn=#")
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python3/01-will-set.test b/test/lib/python3/01-will-set.test
new file mode 100755 (executable)
index 0000000..2aae62f
--- /dev/null
@@ -0,0 +1,13 @@
+#!/usr/bin/python3
+
+import mosquitto
+
+mosq = mosquitto.Mosquitto("01-will-set")
+
+run = -1
+mosq.will_set("topic/on/unexpected/disconnect", "will message", 1, True)
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python3/01-will-unpwd-set.test b/test/lib/python3/01-will-unpwd-set.test
new file mode 100755 (executable)
index 0000000..290fe77
--- /dev/null
@@ -0,0 +1,14 @@
+#!/usr/bin/python3
+
+import mosquitto
+
+mosq = mosquitto.Mosquitto("01-will-unpwd-set")
+
+run = -1
+mosq.username_pw_set("oibvvwqw", "#'^2hg9a&nm38*us")
+mosq.will_set("will-topic", "will message", 2, False)
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python3/02-subscribe-qos0.test b/test/lib/python3/02-subscribe-qos0.test
new file mode 100755 (executable)
index 0000000..2d5b721
--- /dev/null
@@ -0,0 +1,35 @@
+#!/usr/bin/python3
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+    else:
+        mosq.subscribe("qos0/test", 0)
+
+def on_disconnect(mosq, obj, rc):
+    obj = rc
+
+def on_subscribe(mosq, obj, mid, granted_qos):
+    mosq.disconnect()
+
+run = -1
+mosq = mosquitto.Mosquitto("subscribe-qos0-test", run)
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+mosq.on_subscribe = on_subscribe
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python3/02-subscribe-qos1.test b/test/lib/python3/02-subscribe-qos1.test
new file mode 100755 (executable)
index 0000000..aebda7d
--- /dev/null
@@ -0,0 +1,35 @@
+#!/usr/bin/python3
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+    else:
+        mosq.subscribe("qos1/test", 1)
+
+def on_disconnect(mosq, obj, rc):
+    obj = rc
+
+def on_subscribe(mosq, obj, mid, granted_qos):
+    mosq.disconnect()
+
+run = -1
+mosq = mosquitto.Mosquitto("subscribe-qos1-test", run)
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+mosq.on_subscribe = on_subscribe
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python3/02-subscribe-qos2.test b/test/lib/python3/02-subscribe-qos2.test
new file mode 100755 (executable)
index 0000000..1fb5e2c
--- /dev/null
@@ -0,0 +1,35 @@
+#!/usr/bin/python3
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+    else:
+        mosq.subscribe("qos2/test", 2)
+
+def on_disconnect(mosq, obj, rc):
+    obj = rc
+
+def on_subscribe(mosq, obj, mid, granted_qos):
+    mosq.disconnect()
+
+run = -1
+mosq = mosquitto.Mosquitto("subscribe-qos2-test", run)
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+mosq.on_subscribe = on_subscribe
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python3/02-unsubscribe.test b/test/lib/python3/02-unsubscribe.test
new file mode 100755 (executable)
index 0000000..12a85b6
--- /dev/null
@@ -0,0 +1,35 @@
+#!/usr/bin/python3
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+    else:
+        mosq.unsubscribe("unsubscribe/test")
+
+def on_disconnect(mosq, obj, rc):
+    obj = rc
+
+def on_unsubscribe(mosq, obj, mid):
+    mosq.disconnect()
+
+run = -1
+mosq = mosquitto.Mosquitto("unsubscribe-test", run)
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+mosq.on_unsubscribe = on_unsubscribe
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python3/03-publish-b2c-qos1.test b/test/lib/python3/03-publish-b2c-qos1.test
new file mode 100755 (executable)
index 0000000..56252ed
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/python3
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+def on_message(mosq, obj, msg):
+    if msg.mid != 123:
+        print("Invalid mid: ("+str(msg.mid)+")")
+        exit(1)
+    if msg.topic != "pub/qos1/receive":
+        print("Invalid topic: ("+str(msg.topic)+")")
+        exit(1)
+    if msg.payload != b"message":
+        print("Invalid payload: ("+str(msg.payload)+")")
+        exit(1)
+    if msg.qos != 1:
+        print("Invalid qos: ("+str(msg.qos)+")")
+        exit(1)
+    if msg.retain != False:
+        print("Invalid retain: ("+str(msg.retain)+")")
+        exit(1)
+    exit(0)
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        print("Connect failed ("+str(rc)+")")
+        exit(rc)
+
+mosq = mosquitto.Mosquitto("publish-qos1-test")
+mosq.message_retry_set(3)
+mosq.on_connect = on_connect
+mosq.on_message = on_message
+
+mosq.connect("localhost", 1888)
+rc = 0
+while rc == 0:
+    rc = mosq.loop()
+print("rc: "+str(rc))
+exit(1)
diff --git a/test/lib/python3/03-publish-b2c-qos2.test b/test/lib/python3/03-publish-b2c-qos2.test
new file mode 100755 (executable)
index 0000000..be0ff08
--- /dev/null
@@ -0,0 +1,48 @@
+#!/usr/bin/python3
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_message(mosq, obj, msg):
+    global run
+    if msg.mid != 13423:
+        print("Invalid mid ("+str(msg.mid)+")")
+        exit(1)
+    if msg.topic != "pub/qos2/receive":
+        print("Invalid topic ("+str(msg.topic)+")")
+        exit(1)
+    if msg.payload != b"message":
+        print("Invalid payload ("+str(msg.payload)+")")
+        exit(1)
+    if msg.qos != 2:
+        print("Invalid qos ("+str(msg.qos)+")")
+        exit(1)
+    if msg.retain != False:
+        print("Invalid retain ("+str(msg.retain)+")")
+        exit(1)
+
+    run = 0
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+
+run = -1
+mosq = mosquitto.Mosquitto("publish-qos2-test", run)
+mosq.message_retry_set(3)
+mosq.on_connect = on_connect
+mosq.on_message = on_message
+
+mosq.connect("localhost", 1888)
+rc = 0
+while run == -1 and rc == 0:
+    rc = mosq.loop(0.3)
+
+exit(run)
diff --git a/test/lib/python3/03-publish-c2b-qos1-disconnect.test b/test/lib/python3/03-publish-c2b-qos1-disconnect.test
new file mode 100755 (executable)
index 0000000..5da5623
--- /dev/null
@@ -0,0 +1,48 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+sent_mid = -1
+
+def on_connect(mosq, obj, rc):
+    global sent_mid
+    if rc != 0:
+        exit(rc)
+    else:
+        if sent_mid == -1:
+            res = mosq.publish("pub/qos1/test", "message", 1)
+            sent_mid = res[1]
+
+def on_disconnect(mosq, obj, rc):
+    if rc == 1:
+        mosq.reconnect()
+    else:
+        run = 0
+
+def on_publish(mosq, obj, mid):
+    global sent_mid
+    if mid == sent_mid:
+        mosq.disconnect()
+    else:
+        exit(1)
+
+run = -1
+mosq = mosquitto.Mosquitto("publish-qos1-test", run)
+mosq.message_retry_set(3)
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+mosq.on_publish = on_publish
+
+mosq.connect("localhost", 1888)
+rc = 0
+while run == -1:
+    rc = mosq.loop()
+
+exit(run)
diff --git a/test/lib/python3/03-publish-c2b-qos1-timeout.test b/test/lib/python3/03-publish-c2b-qos1-timeout.test
new file mode 100755 (executable)
index 0000000..3283c74
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/python3
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+sent_mid = -1
+
+def on_connect(mosq, obj, rc):
+    global sent_mid
+    if rc != 0:
+        exit(rc)
+    else:
+        res = mosq.publish("pub/qos1/test", "message", 1)
+        sent_mid = res[1]
+
+def on_disconnect(mosq, obj, rc):
+    run = 0
+
+def on_publish(mosq, obj, mid):
+    global sent_mid
+    if mid == sent_mid:
+        mosq.disconnect()
+    else:
+        exit(1)
+
+run = -1
+mosq = mosquitto.Mosquitto("publish-qos1-test", run)
+mosq.message_retry_set(3)
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+mosq.on_publish = on_publish
+
+mosq.connect("localhost", 1888)
+rc = 0
+while run == -1 and rc == 0:
+    rc = mosq.loop()
+
+exit(run)
diff --git a/test/lib/python3/03-publish-c2b-qos2-disconnect.test b/test/lib/python3/03-publish-c2b-qos2-disconnect.test
new file mode 100755 (executable)
index 0000000..0d97ec5
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/python3
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+first_connection = 1
+
+def on_connect(mosq, obj, rc):
+    global first_connection
+    if rc != 0:
+        exit(rc)
+    else:
+        if first_connection == 1:
+            mosq.publish("pub/qos2/test", "message", 2)
+            first_connection = 0
+
+def on_disconnect(mosq, obj, rc):
+    if rc == 1:
+        mosq.reconnect()
+    else:
+        run = 0
+
+def on_publish(mosq, obj, mid):
+    mosq.disconnect()
+
+run = -1
+mosq = mosquitto.Mosquitto("publish-qos2-test", run)
+mosq.message_retry_set(3)
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+mosq.on_publish = on_publish
+
+mosq.connect("localhost", 1888)
+rc = 0
+while run == -1:
+    rc = mosq.loop()
+
+exit(run)
diff --git a/test/lib/python3/03-publish-c2b-qos2-timeout.test b/test/lib/python3/03-publish-c2b-qos2-timeout.test
new file mode 100755 (executable)
index 0000000..13c7baa
--- /dev/null
@@ -0,0 +1,37 @@
+#!/usr/bin/python3
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+    else:
+        mosq.publish("pub/qos2/test", "message", 2)
+
+def on_disconnect(mosq, obj, rc):
+    run = 0
+
+def on_publish(mosq, obj, mid):
+    mosq.disconnect()
+
+run = -1
+mosq = mosquitto.Mosquitto("publish-qos2-test", run)
+mosq.message_retry_set(3)
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+mosq.on_publish = on_publish
+
+mosq.connect("localhost", 1888)
+rc = 0
+while run == -1 and rc == 0:
+    rc = mosq.loop()
+
+exit(run)
diff --git a/test/lib/python3/03-publish-qos0-no-payload.test b/test/lib/python3/03-publish-qos0-no-payload.test
new file mode 100755 (executable)
index 0000000..874988c
--- /dev/null
@@ -0,0 +1,36 @@
+#!/usr/bin/python3
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+sent_mid = -1
+
+def on_connect(mosq, obj, rc):
+    global sent_mid
+    if rc != 0:
+        exit(rc)
+    else:
+        (res, sent_mid) = mosq.publish("pub/qos0/no-payload/test")
+
+def on_publish(mosq, obj, mid):
+    global sent_mid, run
+    if sent_mid == mid:
+        mosq.disconnect()
+        run = 0
+
+run = -1
+mosq = mosquitto.Mosquitto("publish-qos0-test-np", run)
+mosq.on_connect = on_connect
+mosq.on_publish = on_publish
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python3/03-publish-qos0.test b/test/lib/python3/03-publish-qos0.test
new file mode 100755 (executable)
index 0000000..f3474fd
--- /dev/null
@@ -0,0 +1,36 @@
+#!/usr/bin/python3
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+sent_mid = -1
+
+def on_connect(mosq, obj, rc):
+    global sent_mid
+    if rc != 0:
+        exit(rc)
+    else:
+        res = mosq.publish("pub/qos0/test", "message")
+        sent_mid = res[1]
+
+def on_publish(mosq, obj, mid):
+    global sent_mid, run
+    if sent_mid == mid:
+        mosq.disconnect()
+
+run = -1
+mosq = mosquitto.Mosquitto("publish-qos0-test", run)
+mosq.on_connect = on_connect
+mosq.on_publish = on_publish
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python3/04-retain-qos0.test b/test/lib/python3/04-retain-qos0.test
new file mode 100755 (executable)
index 0000000..7bf6c85
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/python3
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+    else:
+        mosq.publish("retain/qos0/test", "retained message", 0, True)
+
+run = -1
+mosq = mosquitto.Mosquitto("retain-qos0-test", run)
+mosq.on_connect = on_connect
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python3/08-ssl-bad-cacert.test b/test/lib/python3/08-ssl-bad-cacert.test
new file mode 100755 (executable)
index 0000000..5b8bfb2
--- /dev/null
@@ -0,0 +1,23 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+if sys.version < '2.7':
+    print("WARNING: SSL/TLS not supported on Python 2.6")
+    exit(0)
+
+rc = 1
+mosq = mosquitto.Mosquitto("08-ssl-bad-cacert")
+try:
+    mosq.tls_set("this/file/doesnt/exist")
+except IOError as err:
+    rc = 0
+
+exit(rc)
diff --git a/test/lib/python3/08-ssl-connect-cert-auth.test b/test/lib/python3/08-ssl-connect-cert-auth.test
new file mode 100755 (executable)
index 0000000..f7dbadf
--- /dev/null
@@ -0,0 +1,34 @@
+#!/usr/bin/python3
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+    else:
+        mosq.disconnect()
+
+def on_disconnect(mosq, obj, rc):
+    mosq.loop()
+    obj = rc
+
+
+run = -1
+mosq = mosquitto.Mosquitto("08-ssl-connect-crt-auth", run)
+mosq.tls_set("../ssl/test-ca.crt", "../ssl/client.crt", "../ssl/client.key")
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python3/08-ssl-connect-no-auth.test b/test/lib/python3/08-ssl-connect-no-auth.test
new file mode 100755 (executable)
index 0000000..1fd5aec
--- /dev/null
@@ -0,0 +1,34 @@
+#!/usr/bin/python3
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+
+import mosquitto
+
+
+def on_connect(mosq, obj, rc):
+    if rc != 0:
+        exit(rc)
+    else:
+        mosq.disconnect()
+
+def on_disconnect(mosq, obj, rc):
+    mosq.loop()
+    obj = rc
+
+
+run = -1
+mosq = mosquitto.Mosquitto("08-ssl-connect-no-auth", run)
+mosq.tls_set("../ssl/test-ca.crt")
+mosq.on_connect = on_connect
+mosq.on_disconnect = on_disconnect
+
+mosq.connect("localhost", 1888)
+while run == -1:
+    mosq.loop()
+
+exit(run)
diff --git a/test/lib/python3/08-ssl-fake-cacert.test b/test/lib/python3/08-ssl-fake-cacert.test
new file mode 100755 (executable)
index 0000000..e740b60
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/bin/python3
+
+import os
+import subprocess
+import socket
+import sys
+import time
+from struct import *
+import ssl
+
+import mosquitto
+
+if sys.version < '2.7':
+    print("WARNING: SSL/TLS not supported on Python 2.6")
+    exit(0)
+
+def on_connect(mosq, obj, rc):
+    exit(1)
+
+mosq = mosquitto.Mosquitto("08-ssl-fake-cacert")
+mosq.tls_set("../ssl/fake-ca.crt", "../ssl/client.crt", "../ssl/client.key")
+mosq.on_connect = on_connect
+
+try:
+    mosq.connect("localhost", 1888)
+except ssl.SSLError as msg:
+    if msg.errno == 1 and "certificate verify failed" in msg.strerror:
+        exit(0)
+    else:
+        exit(1)
+else:
+    exit(1)
diff --git a/test/lib/python3/09-util-topic-matching.test b/test/lib/python3/09-util-topic-matching.test
new file mode 100755 (executable)
index 0000000..11fd81b
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/python
+
+# Copyright (c) 2012 Roger Light <roger@atchoo.org>
+# All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+# 
+# 1. Redistributions of source code must retain the above copyright notice,
+#   this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+# 3. Neither the name of mosquitto nor the names of its
+#   contributors may be used to endorse or promote products derived from
+#   this software without specific prior written permission.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) 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 OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import mosquitto
+import sys
+
+if mosquitto.topic_matches_sub("foo/bar", "foo/bar") == False:
+    print("ERROR: foo/bar : foo/bar")
+    sys.exit(1)
+if mosquitto.topic_matches_sub("foo/+", "foo/bar") == False:
+    print("ERROR: foo/+ : foo/bar")
+    sys.exit(1)
+if mosquitto.topic_matches_sub("foo/+/baz", "foo/bar/baz") == False:
+    print("ERROR: foo/+/baz : foo/bar/baz")
+    sys.exit(1)
+if mosquitto.topic_matches_sub("foo/+/#", "foo/bar/baz") == False:
+    print("ERROR: foo/+/# : foo/bar/baz")
+    sys.exit(1)
+if mosquitto.topic_matches_sub("#", "foo/bar/baz") == False:
+    print("ERROR: # : foo/bar/baz")
+    sys.exit(1)
+
+if mosquitto.topic_matches_sub("foo/bar", "foo") == True:
+    print("ERROR: foo/bar : foo")
+    sys.exit(1)
+if mosquitto.topic_matches_sub("foo/+", "foo/bar/baz") == True:
+    print("ERROR: foo/+ : foo/bar/baz")
+    sys.exit(1)
+if mosquitto.topic_matches_sub("foo/+/baz", "foo/bar/bar") == True:
+    print("ERROR: foo/+/baz : foo/bar/bar")
+    sys.exit(1)
+if mosquitto.topic_matches_sub("foo/+/#", "fo2/bar/baz") == True:
+    print("ERROR: foo/+/# : foo/bar/baz")
+    sys.exit(1)
+
+sys.exit(0)
diff --git a/test/mosq_test.py b/test/mosq_test.py
new file mode 100644 (file)
index 0000000..111e524
--- /dev/null
@@ -0,0 +1,322 @@
+import struct
+
+def expect_packet(sock, name, expected):
+    if len(expected) > 0:
+        rlen = len(expected)
+    else:
+        rlen = 1
+
+    packet_recvd = sock.recv(rlen)
+    return packet_matches(name, packet_recvd, expected)
+
+def packet_matches(name, recvd, expected):
+    if recvd != expected:
+        print("FAIL: Received incorrect "+name+".")
+        try:
+            print("Received: "+to_string(recvd))
+        except struct.error:
+            print("Received (not decoded): "+recvd)
+        try:
+            print("Expected: "+to_string(expected))
+        except struct.error:
+            print("Expected (not decoded): "+expected)
+
+        return 0
+    else:
+        return 1
+
+def remaining_length(packet):
+    l = min(5, len(packet))
+    all_bytes = struct.unpack("!"+"B"*l, packet[:l])
+    mult = 1
+    rl = 0
+    for i in range(1,l-1):
+        byte = all_bytes[i]
+
+        rl += (byte & 127) * mult
+        mult *= 128
+        if byte & 128 == 0:
+            packet = packet[i+1:]
+            break
+
+    return (packet, rl)
+
+
+def to_string(packet):
+    if len(packet) == 0:
+        return ""
+
+    packet0 = struct.unpack("!B", packet[0])
+    packet0 = packet0[0]
+    cmd = packet0 & 0xF0
+    if cmd == 0x00:
+        # Reserved
+        return "0x00"
+    elif cmd == 0x10:
+        # CONNECT
+        (packet, rl) = remaining_length(packet)
+        pack_format = "!H" + str(len(packet)-2) + 's'
+        (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)
+        if flags&2:
+            s = s+", clean-session"
+        else:
+            s = s+", durable"
+
+        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'
+        (client_id, packet) = struct.unpack(pack_format, packet)
+        s = s+", id="+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
+
+            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_message, packet) = struct.unpack(pack_format, packet)
+            s = s+", will-message="+will_message
+
+            s = s+", will-qos="+str((flags&24)>>3)
+            s = s+", will-retain="+str((flags&32)>>5)
+
+        if flags&128:
+            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'
+            (username, packet) = struct.unpack(pack_format, packet)
+            s = s+", username="+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
+
+        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)
+    elif cmd == 0x30:
+        # PUBLISH
+        dup = (packet0 & 0x08)>>3
+        qos = (packet0 & 0x06)>>1
+        retain = (packet0 & 0x01)
+        (packet, rl) = remaining_length(packet)
+        pack_format = "!H" + str(len(packet)-2) + 's'
+        (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)
+        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
+        return s
+    elif cmd == 0x40:
+        # PUBACK
+        (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)
+    elif cmd == 0x60:
+        # PUBREL
+        dup = (packet0 & 0x08)>>3
+        (cmd, rl, mid) = struct.unpack('!BBH', packet)
+        return "PUBREL, rl="+str(rl)+", mid="+str(mid)+", dup="+str(dup)
+    elif cmd == 0x70:
+        # PUBCOMP
+        (cmd, rl, mid) = struct.unpack('!BBH', packet)
+        return "PUBCOMP, rl="+str(rl)+", mid="+str(mid)
+    elif cmd == 0x80:
+        # SUBSCRIBE
+        (packet, rl) = remaining_length(packet)
+        pack_format = "!H" + str(len(packet)-2) + 's'
+        (mid, packet) = struct.unpack(pack_format, packet)
+        s = "SUBSCRIBE, rl="+str(rl)+", mid="+str(mid)
+        topic_index = 0
+        while len(packet) > 0:
+            pack_format = "!H" + str(len(packet)-2) + 's'
+            (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)
+        return s
+    elif cmd == 0x90:
+        # SUBACK
+        (packet, rl) = remaining_length(packet)
+        pack_format = "!H" + str(len(packet)-2) + 's'
+        (mid, packet) = struct.unpack(pack_format, packet)
+        pack_format = "!" + "B"*len(packet)
+        granted_qos = struct.unpack(pack_format, packet)
+        
+        s = "SUBACK, rl="+str(rl)+", mid="+str(mid)+", granted_qos="+str(granted_qos[0])
+        for i in range(1, len(granted_qos)-1):
+            s = s+", "+str(granted_qos[i])
+        return s
+    elif cmd == 0xA0:
+        # UNSUBSCRIBE
+        (packet, rl) = remaining_length(packet)
+        pack_format = "!H" + str(len(packet)-2) + 's'
+        (mid, packet) = struct.unpack(pack_format, packet)
+        s = "UNSUBSCRIBE, rl="+str(rl)+", mid="+str(mid)
+        topic_index = 0
+        while len(packet) > 0:
+            pack_format = "!H" + str(len(packet)-2) + 's'
+            (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
+        return s
+    elif cmd == 0xB0:
+        # UNSUBACK
+        (cmd, rl, mid) = struct.unpack('!BBH', packet)
+        return "UNSUBACK, rl="+str(rl)+", mid="+str(mid)
+    elif cmd == 0xC0:
+        # PINGREQ
+        (cmd, rl) = struct.unpack('!BB', packet)
+        return "PINGREQ, rl="+str(rl)
+    elif cmd == 0xD0:
+        # PINGRESP
+        (cmd, rl) = struct.unpack('!BB', packet)
+        return "PINGRESP, rl="+str(rl)
+    elif cmd == 0xE0:
+        # DISCONNECT
+        (cmd, rl) = struct.unpack('!BB', packet)
+        return "DISCONNECT, rl="+str(rl)
+    elif cmd == 0xF0:
+        # Reserved
+        return "0xF0"
+
+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_ver=3):
+    if client_id == None:
+        remaining_length = 12
+    else:
+        remaining_length = 12 + 2+len(client_id)
+    connect_flags = 0
+    if clean_session:
+        connect_flags = connect_flags | 0x02
+
+    if will_topic != None:
+        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 username != None:
+        remaining_length = remaining_length + 2+len(username)
+        connect_flags = connect_flags | 0x80
+        if password != None:
+            connect_flags = connect_flags | 0x40
+            remaining_length = remaining_length + 2+len(password)
+
+    rl = pack_remaining_length(remaining_length)
+    packet = struct.pack("!B"+str(len(rl))+"s", 0x10, rl)
+    packet = packet + struct.pack("!H6sBBH", len("MQIsdp"), "MQIsdp", proto_ver, connect_flags, keepalive)
+    if client_id != None:
+        packet = packet + struct.pack("!H"+str(len(client_id))+"s", len(client_id), client_id)
+
+    if will_topic != None:
+        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)
+        else:
+            packet = packet + struct.pack("!H", 0)
+
+    if username != None:
+        packet = packet + struct.pack("!H"+str(len(username))+"s", len(username), username)
+        if password != None:
+            packet = packet + struct.pack("!H"+str(len(password))+"s", len(password), password)
+    return packet
+
+def gen_connack(resv=0, rc=0):
+    return struct.pack('!BBBB', 32, 2, resv, rc);
+
+def gen_publish(topic, qos, payload=None, retain=False, dup=False, mid=0):
+    rl = 2+len(topic)
+    pack_format = "!BBH"+str(len(topic))+"s"
+    if qos > 0:
+        rl = rl + 2
+        pack_format = pack_format + "H"
+    if payload != None:
+        rl = rl + len(payload)
+        pack_format = pack_format + str(len(payload))+"s"
+    else:
+        payload = ""
+        pack_format = pack_format + "0s"
+
+    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)
+    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)
+
+def gen_pubrel(mid, dup=False):
+    if dup:
+        cmd = 96+8+2
+    else:
+        cmd = 96+2
+    return struct.pack('!BBH', cmd, 2, mid)
+
+def gen_pubcomp(mid):
+    return struct.pack('!BBH', 112, 2, mid)
+
+def gen_subscribe(mid, topic, qos):
+    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_suback(mid, qos):
+    return struct.pack('!BBHB', 144, 2+1, mid, qos)
+
+def gen_unsubscribe(mid, topic):
+    pack_format = "!BBHH"+str(len(topic))+"s"
+    return struct.pack(pack_format, 162, 2+2+len(topic), mid, len(topic), topic)
+
+def gen_unsuback(mid):
+    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 pack_remaining_length(remaining_length):
+    s = ""
+    while True:
+        byte = remaining_length % 128
+        remaining_length = remaining_length // 128
+        # If there are more digits to encode, set the top bit of this digit
+        if remaining_length > 0:
+            byte = byte | 0x80
+
+        s = s + struct.pack("!B", byte)
+        if remaining_length == 0:
+            return s
diff --git a/test/ssl/client-expired.crt b/test/ssl/client-expired.crt
new file mode 100644 (file)
index 0000000..d6df974
--- /dev/null
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICpDCCAg0CCQDNrg5WSiiRqDANBgkqhkiG9w0BAQUFADCBoDELMAkGA1UEBhMC
+R0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTEdMBsG
+A1UECgwUTW9zcXVpdHRvIFRlc3QgU3VpdGUxDzANBgNVBAsMBkJyb2tlcjEXMBUG
+A1UEAwwOYnJva2VyLXRlc3QtY2ExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBs
+ZS5jb20wHhcNMTIwNzAzMTQ0MDA3WhcNMTIwNzA0MTQ0MDA3WjCBizELMAkGA1UE
+BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTEd
+MBsGA1UECgwUTW9zcXVpdHRvIFRlc3QgU3VpdGUxFDASBgNVBAsMC0Jyb2tlciBU
+ZXN0MR4wHAYDVQQDDBVsb2NhbGhvc3QtY2xpZW50LXRlc3QwgZ8wDQYJKoZIhvcN
+AQEBBQADgY0AMIGJAoGBAM1kzx27D7TooydBNi6tTIMvstPZqFviwN5JGCKxs0wI
+ZAdVP1HNeECioww4HiMO6J6IfcLppLDuWr1pteSG471MjGLPc0Z6UKNejKntM30p
+7649fBZ9DIomVKTJHya/jtU8hIJfSpY29FKGOe3gVjg99nFZtB1dGnycQys5FRVZ
+AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEACkRM53UdHTXhDNv+xZk6DJyNneEm0BWG
+u9IExujL1YKbEIxlOncacDEzAYe3YbcnzN+1rgYel2l8Oq7Esb4OhlM5ftzfNLw5
+p2uM1QWzB1N1pwJ7BMtlkFuL+JI+VikaVdqvV9YhCDxIUUujoMsXLYzVOVMQbToJ
+YedFIjsMj6s=
+-----END CERTIFICATE-----
diff --git a/test/ssl/client-revoked.crt b/test/ssl/client-revoked.crt
new file mode 100644 (file)
index 0000000..ac70249
--- /dev/null
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICpDCCAg0CCQDNrg5WSiiRpzANBgkqhkiG9w0BAQUFADCBoDELMAkGA1UEBhMC
+R0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTEdMBsG
+A1UECgwUTW9zcXVpdHRvIFRlc3QgU3VpdGUxDzANBgNVBAsMBkJyb2tlcjEXMBUG
+A1UEAwwOYnJva2VyLXRlc3QtY2ExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBs
+ZS5jb20wHhcNMTIwNzAzMTQ0MDAwWhcNMzkxMTE4MTQ0MDAwWjCBizELMAkGA1UE
+BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTEd
+MBsGA1UECgwUTW9zcXVpdHRvIFRlc3QgU3VpdGUxFDASBgNVBAsMC0Jyb2tlciBU
+ZXN0MR4wHAYDVQQDDBVsb2NhbGhvc3QtY2xpZW50LXRlc3QwgZ8wDQYJKoZIhvcN
+AQEBBQADgY0AMIGJAoGBAM1kzx27D7TooydBNi6tTIMvstPZqFviwN5JGCKxs0wI
+ZAdVP1HNeECioww4HiMO6J6IfcLppLDuWr1pteSG471MjGLPc0Z6UKNejKntM30p
+7649fBZ9DIomVKTJHya/jtU8hIJfSpY29FKGOe3gVjg99nFZtB1dGnycQys5FRVZ
+AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAXB40zdyBFQ7BjDbDFV/vcx4E2rpVxnQ4
+vtJ8TE7aaBqS9QmxlWYnx8ys/q51mVmOxbA/aIFllaSyR+P0MrgZfbWFtb/PK2IV
+VnCciP7dfwqbnsW3ziRUq+mTaaNDPtT+YJrLJyTYNZPRvGIBHOt0NKzNCyvO37v3
+op7ELGt0I+E=
+-----END CERTIFICATE-----
diff --git a/test/ssl/client-revoked.csr b/test/ssl/client-revoked.csr
new file mode 100644 (file)
index 0000000..1314812
--- /dev/null
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBzDCCATUCAQAwgYsxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5Vbml0ZWQgS2lu
+Z2RvbTEOMAwGA1UEBwwFRGVyYnkxHTAbBgNVBAoMFE1vc3F1aXR0byBUZXN0IFN1
+aXRlMRQwEgYDVQQLDAtCcm9rZXIgVGVzdDEeMBwGA1UEAwwVbG9jYWxob3N0LWNs
+aWVudC10ZXN0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNZM8duw+06KMn
+QTYurUyDL7LT2ahb4sDeSRgisbNMCGQHVT9RzXhAoqMMOB4jDuieiH3C6aSw7lq9
+abXkhuO9TIxiz3NGelCjXoyp7TN9Ke+uPXwWfQyKJlSkyR8mv47VPISCX0qWNvRS
+hjnt4FY4PfZxWbQdXRp8nEMrORUVWQIDAQABoAAwDQYJKoZIhvcNAQEFBQADgYEA
+uzoEdsl3JkiNIviQzDtr67k6L/vogtVSgRnCFCel16Q0i1W+mVAwUqYArwf/7fwp
+UhZPd8NLSVT1pn4Nj2a2Q7S3GMpMguiQlhCol7hZOrNpc0fDGg1JSmYjCEmm3TOl
+Z49eyqmE8r4xdDEAyPk3u21HYrZ5RshyS/8vfPwqR9o=
+-----END CERTIFICATE REQUEST-----
diff --git a/test/ssl/client-revoked.key b/test/ssl/client-revoked.key
new file mode 100644 (file)
index 0000000..479a528
--- /dev/null
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDNZM8duw+06KMnQTYurUyDL7LT2ahb4sDeSRgisbNMCGQHVT9R
+zXhAoqMMOB4jDuieiH3C6aSw7lq9abXkhuO9TIxiz3NGelCjXoyp7TN9Ke+uPXwW
+fQyKJlSkyR8mv47VPISCX0qWNvRShjnt4FY4PfZxWbQdXRp8nEMrORUVWQIDAQAB
+AoGAdcKxuUMSG1AykwQhk5uKvcBwUGR/0cbte8T+0I1/1j0NVOL8feNHag+VWiEm
+rkUS/CoXqNQat9LBNc5RGmh4U35orG2xi/EqcBnp/Mse2UqnOTYO3xjeP+JQBtR9
+EiutMTabnaOIXox2bfb3olKA5b6phTt9Y0v8Li/jbVAhw3ECQQDslDu6ZvkKoljU
+VICOteQMMPESsrXVPs5brtxyK2LQn+GBwXvy7d655Ql9jUkyops546aTB6JgYOMs
+zDD3oJ7FAkEA3kE1wwap7NxtYSEbtwyIa7r+IKezG9IPwG27EHjTjPBgclOk0ZOf
+W51ZD/CYNbA7fYAbqREeBwzhe5u0jfHFhQJBAOccL/T6nxMqYYibPDMtsSfPr9FK
+T6OQBVs/SQ8nHxMa/NsbPpCkm04SVuEV4onam7VDlPhRHujz/TlICBYADNkCQEaA
+XwJ3ea2mGphF/VmqgxfRYE2RhNJdZxu+cyl9enXpxl5dxBmq/1D7b8YLpuzY83YT
+DjMqN+E6p8gjEzo3qFUCQGaSni6qTT9pT22uT3QwLthOPdVacV6a55Ci6g4XaFUR
+/Es/nQdkZTbCI1ufGV2Usodsqas+lNGqnClGVHqcUg8=
+-----END RSA PRIVATE KEY-----
diff --git a/test/ssl/client.crt b/test/ssl/client.crt
new file mode 100644 (file)
index 0000000..7b73d73
--- /dev/null
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICxjCCAi8CCQDNrg5WSiiRqjANBgkqhkiG9w0BAQUFADCBoDELMAkGA1UEBhMC
+R0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTEdMBsG
+A1UECgwUTW9zcXVpdHRvIFRlc3QgU3VpdGUxDzANBgNVBAsMBkJyb2tlcjEXMBUG
+A1UEAwwOYnJva2VyLXRlc3QtY2ExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBs
+ZS5jb20wHhcNMTMwMjIyMjEzMDIyWhcNMjMwMjIwMjEzMDIyWjCBrTELMAkGA1UE
+BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTEd
+MBsGA1UECgwUTW9zcXVpdHRvIFRlc3QgU3VpdGUxFDASBgNVBAsMC0Jyb2tlciBU
+ZXN0MR4wHAYDVQQDDBVsb2NhbGhvc3Qtbm9uLXJldm9rZWQxIDAeBgkqhkiG9w0B
+CQEWEXRlc3QxQGV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
+gQClAM49bIqhRQdDO+0Dtomi1EeH43ECWq+r9747kQ5RUJyA1m4mNHV6nQR+HMzc
+amFd7XVuP4o5AAO5Q0KeyXRNVfvrljk39XqbkzmXeaTxNiMzk01MsFYlDP7i8Xt/
+IvAQ6J39UN8lzY/SdqW55nAfMZmoQCIP3h+gvt4DHjAyRQIDAQABMA0GCSqGSIb3
+DQEBBQUAA4GBAEhL1QRF2mrpbErgPK4zX+2UXKMQQVon2XHvI9iETk7O/e7yYSWB
+N9V7oS4rMDEo6qebnnjtBRJ/bDtSkHm+I1y4Xg3nPUTPeYpvtyTzcLORr7rV336O
+05xmaOCeRBZXKohcCvdbV8KA8/EjC3BI04S4pkehjgARpY/DL026VbGp
+-----END CERTIFICATE-----
diff --git a/test/ssl/client.csr b/test/ssl/client.csr
new file mode 100644 (file)
index 0000000..982d45c
--- /dev/null
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIB7jCCAVcCAQAwga0xCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5Vbml0ZWQgS2lu
+Z2RvbTEOMAwGA1UEBwwFRGVyYnkxHTAbBgNVBAoMFE1vc3F1aXR0byBUZXN0IFN1
+aXRlMRQwEgYDVQQLDAtCcm9rZXIgVGVzdDEeMBwGA1UEAwwVbG9jYWxob3N0LW5v
+bi1yZXZva2VkMSAwHgYJKoZIhvcNAQkBFhF0ZXN0MUBleGFtcGxlLmNvbTCBnzAN
+BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEApQDOPWyKoUUHQzvtA7aJotRHh+NxAlqv
+q/e+O5EOUVCcgNZuJjR1ep0EfhzM3GphXe11bj+KOQADuUNCnsl0TVX765Y5N/V6
+m5M5l3mk8TYjM5NNTLBWJQz+4vF7fyLwEOid/VDfJc2P0nalueZwHzGZqEAiD94f
+oL7eAx4wMkUCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4GBADIUaGKO+LK8MDXTGQjZ
+Wr5wpJC67xgjXSdaMcx6wHicrd8wKoeKBk2ibbbNJIcIcYgdPmmNp96C0RacmtlM
+dL9sIIVVO/vzsV3SVozVHZZLLtvByYV3KZu/bXpEwSa8LOK35fBNkHS2/5DJhwpR
+E6pFDre+HBoEUM+h1ZKLb4Ba
+-----END CERTIFICATE REQUEST-----
diff --git a/test/ssl/client.key b/test/ssl/client.key
new file mode 100644 (file)
index 0000000..e98e2ce
--- /dev/null
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICWwIBAAKBgQClAM49bIqhRQdDO+0Dtomi1EeH43ECWq+r9747kQ5RUJyA1m4m
+NHV6nQR+HMzcamFd7XVuP4o5AAO5Q0KeyXRNVfvrljk39XqbkzmXeaTxNiMzk01M
+sFYlDP7i8Xt/IvAQ6J39UN8lzY/SdqW55nAfMZmoQCIP3h+gvt4DHjAyRQIDAQAB
+AoGAOgsUgcsC4UQ1D9OuxpWZySu/le/OOzPHhEj8JFwcm0IuK6xCxzHA+cbuY6ah
+8g+B/NahbozvhKmapbshXlYjkNgWI0OY6HcLGKeW8gTduIEC69TPTRdTiZY6INhi
+vsdvwkIUnzaSkm0e+NHBWY3HgcT4ziGqsb2kIQujVcRV5IECQQDY15UmhMg9ujmQ
+epIAuWquIWruTCoeD/Qsm74RCvdJq8CZQmZ3TQisQn9hM2LJ98lpcGj+5AktaQdc
+8/QLzucxAkEAwszA7h1wRgaFeG9GMM+pGiWTKz5dmF/mA3VBJIk8bQP6eZx4Asrj
+8i6LPdeqBqvopJOMSqv89YohMQGmscafVQJADslcpVzGntb1F/ITxLLgIHGs7gUJ
+ljU2TFhudJEP3dk1b/4EKLx7C+wY7ZLoeKdJ98OvlToNmiggVWlZSfjY8QJAOpyv
+BDZZXMiDilw4y2EAKXzazi0irkFUjP/PzPV59/c1ezfoKDbx5SG6Ba6JWx6IjlRS
+5vje0OZx3DP5+w2fTQJAAYd/wwaA9M9hIrL4LXmnznZcoHgfQ6ImNoi5TcMoZoNk
+z7PaMNx+26Q+gJ1+/ILn6mdDUHVl2j1Xg6piuVOZQw==
+-----END RSA PRIVATE KEY-----
diff --git a/test/ssl/crl.pem b/test/ssl/crl.pem
new file mode 100644 (file)
index 0000000..650b350
--- /dev/null
@@ -0,0 +1,11 @@
+-----BEGIN X509 CRL-----
+MIIBmDCCAQECAQEwDQYJKoZIhvcNAQEFBQAwgaAxCzAJBgNVBAYTAkdCMRcwFQYD
+VQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwGA1UEBwwFRGVyYnkxHTAbBgNVBAoMFE1v
+c3F1aXR0byBUZXN0IFN1aXRlMQ8wDQYDVQQLDAZCcm9rZXIxFzAVBgNVBAMMDmJy
+b2tlci10ZXN0LWNhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tFw0x
+MzAyMjIyMTE0MjhaFw0yMzAyMjAyMTE0MjhaMBwwGgIJAM2uDlZKKJGnFw0xMjA3
+MDMxNTU4NDZaoA4wDDAKBgNVHRQEAwIBBDANBgkqhkiG9w0BAQUFAAOBgQB1eSbU
+qouEY+28nNc7QkoDJqK/VgfVYSIgbkuaM8RP4TalndoA/eP4VJbOycZ6HbU10315
+sfYhoARx8xXtz/OIU470PSUKrbeNQMXoebp5YGZHl1Gd+FvClVgzAPoJCn68rd4N
+mzkDJrdpBZCTt4yxv+vkLwdBmAetcLKj/lhpbg==
+-----END X509 CRL-----
diff --git a/test/ssl/demoCA/crlnumber b/test/ssl/demoCA/crlnumber
new file mode 100644 (file)
index 0000000..eeee65e
--- /dev/null
@@ -0,0 +1 @@
+05
diff --git a/test/ssl/demoCA/index.txt b/test/ssl/demoCA/index.txt
new file mode 100644 (file)
index 0000000..ced7051
--- /dev/null
@@ -0,0 +1 @@
+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
new file mode 100644 (file)
index 0000000..3a7e39e
--- /dev/null
@@ -0,0 +1 @@
+unique_subject = no
diff --git a/test/ssl/demoCA/serial b/test/ssl/demoCA/serial
new file mode 100644 (file)
index 0000000..8a0f05e
--- /dev/null
@@ -0,0 +1 @@
+01
diff --git a/test/ssl/fake-ca.crt b/test/ssl/fake-ca.crt
new file mode 100644 (file)
index 0000000..209af42
--- /dev/null
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIEITCCAwmgAwIBAgIJAJu8ZUmvHGqDMA0GCSqGSIb3DQEBBQUAMIGmMQswCQYD
+VQQGEwJERTEQMA4GA1UECAwHR2VybWFueTEPMA0GA1UEBwwGQmVybGluMR8wHQYD
+VQQKDBZGYWtlIENlcnRpZmljYXRlcyBHbWJoMRcwFQYDVQQLDA5Eb2RneSBEZWFs
+aW5nczEZMBcGA1UEAwwQZmFrZS1jZXJ0aWZpY2F0ZTEfMB0GCSqGSIb3DQEJARYQ
+ZmFrZUBleGFtcGxlLmNvbTAeFw0xMzAxMjUwOTE0MTJaFw0yMzAxMjMwOTE0MTJa
+MIGmMQswCQYDVQQGEwJERTEQMA4GA1UECAwHR2VybWFueTEPMA0GA1UEBwwGQmVy
+bGluMR8wHQYDVQQKDBZGYWtlIENlcnRpZmljYXRlcyBHbWJoMRcwFQYDVQQLDA5E
+b2RneSBEZWFsaW5nczEZMBcGA1UEAwwQZmFrZS1jZXJ0aWZpY2F0ZTEfMB0GCSqG
+SIb3DQEJARYQZmFrZUBleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAKRMCDzm2gYvw4LViJLqbyYJLozmv12IIO2XYq1ySVftJlx3rfsP
+v67llJ56hncj/c53vCe9U1jcb/R+ycDMP+28XvnEaEGAsotO4W1ky60Sku8rUwKM
+wIIMj5/bhPGCuZqw1PTlUfwb5BCcP1Kte+Tx4lElZZ7KgigWFFrxwlORUaRrVrBm
+qervDlb4ze7iryShpPQRtWvzCxyXyEB2mD9QEwDdFRF3+mooqCfSxy/LlzMPn2dx
+5MJtJ9M1dnjY8vI1GI+Uiw78GigJjlBLiZAfRRTlRWja/Q8vt/j0IbNQHKumfzEe
+uTnl0UlpzzX5CpVtfZXJRXtF7KRnCBpMT5ECAwEAAaNQME4wHQYDVR0OBBYEFDA+
+RHNT+TYVdOAJ1YJWIfKlY/zIMB8GA1UdIwQYMBaAFDA+RHNT+TYVdOAJ1YJWIfKl
+Y/zIMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAG2oSw+kBTD9/FGJ
++rYAmBchTQTMOS2RPgpudEwRapswLvnYhWh/Vos6I0oQaGu9wf7rYPdqmTsNPoke
+/0j/Jqwp7/QEwtu6X0heqtAh/+FJVrpUlLbRoGbAuCb8rS3t3k8zGjaAKzNLB5vL
+quy9f4TZI0ojyN7v4q6B5FTdjaaxQa5hFVRno98oipI6jghQsnL9oxqsBedKaRE5
+WowXviqK3umHh0zqfkzVptee5GsNt7MceKRAgWUE2qj70kp2ceSX8D9aoWEar5k8
+VmvWfTAgmFq9GPV2WhJAoDj7P8lGDdqqtoI5qJAg+KSjtC1MSzMDSy/xuW7eFdbJ
+qJVuqN8=
+-----END CERTIFICATE-----
diff --git a/test/ssl/fake-ca.key b/test/ssl/fake-ca.key
new file mode 100644 (file)
index 0000000..1edfb1b
--- /dev/null
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIqEGGNc7YpJYCAggA
+MBQGCCqGSIb3DQMHBAiBEYk/9zv90QSCBMj06vSmv9YwCHKKSwyD2u7E+rbUbQz1
+UfY/Shf7zOvBTaELaSsctO9UpoAeWzPjkNvhhTsZIBhHxKomHc+YFBvAHLkUx7mk
+9MT9zxC5hQd0k0lcYt4xc5HFpg8AVx34PGA2AnRCWKz+Aj+fgxV0U3tNIzAw8V0w
+bZJGLFxQqhLXlME3Bd4iSG+oh5UzjhDvOVMTJoRsOpipOUfx/DKCMB23Z6987k7j
+t45VucNF3BlbBlaUDOCUKecMtkYAjHn44D9JmvII53xF5PTUFMVjd3Xdqdo6jQBX
+dN/HNigt2iDl1g0Wrere/8CjMFBuh3ANBrIF91eyj1xLTQ8+ZhBVRpEhgq11oMUd
+5jqWUJgax7PTgY4wSK24dYak9iSaqi32+1+KxQW8gi2E+vtCBgTO8NbH1F7OJarK
+wyUmRSE/hmD1XTLg6QEoKKl7/8WnGL5GkuppMkePHQj1YI5Dpwy4ElVFOzo7VvF0
+gqeLIFP7afE1BoTO7pcu8R30/KjHmg//LFJ4YksYMInyg1zv2djnoE7s/gwLA2jY
+J95VhN7SA4r5kpVzHdkwscaQ1J1ss9BLGRSJ2NuMo5vrvFnAHnZ9pvhzUA2UveD9
+EKzSbR8KRZf/BFn6d06n1nVEyNtGvD0p427L4Fn1HmD1yOi5z/iQHpgHUektFrEl
+LVduaLONwPyPXX3Gzzuy8ETQVRdiHA6J0ZQh/EpYPtsJ1MvmWMocQwKqaBvyQKc6
+CLop+2/MbAyJszUvUTJIDuXoHO4mm2u7G8h/4KOifDmRyERJdugQ+ZMu3LSgjl+e
+/Bi5qSDcgzI3GJLBjTsnkY5yKSz9VpCvvIh0gXlZT/7aFTBkic5+cc2l3K4Sf6DF
+C0FY2xfiNiFTsK2LZov4Dc7msv3Psfc0oCABpkv/i+lrHEMnJw3jj/KnVfw30/Gp
+/0hgcGBpdQ7EJmGXKY/yAd7gA3frfQ6lT7Fr6MTD6v5LRY7ZfJAka3RCQM4DrIj4
+Tl+CnAskekL2Zgm0L5K1QuVOZkH0uJca7Dy6HEAlSB+EhYQ0F/HZA94Vp/ZjtOvp
+cw9PJEztWLYSvsHwC9691vgkG79+YsI3mZ2i+t/Ps0zHe7EL6p3sbHp3h9FZj+7+
+rIfMWF7SGc0mQBNdwq+eyRg46LgZp4Za1R6ap5QoYFtV7cnHzZGeNAk6+ucmtviu
+2y5p5d51J1Ll6KELKodDZ7PLUDez9l0JMSLurlysQI7uvM91za6gtjoWgGlhNqYb
+/V8Zdj8jn2ri2fL3CGRHAQcPRDy26lzjh5DuHCXkHC8sn8cCl+wJWcBhU1GCrvlk
+d0TJZWbm8MDKTxJ+vC/jn0PZkZ0v0fOzVFYgnIDmteBvOx8IRkJUYmIY/tcBcpUz
+fMNXYedw9Xdd3dRdGcSuEPcFMtKWNX8H8GW6rydcj1wUsrYL7w8ONLWGWF1u09dQ
+N9fV1PK4J3ZS7u6O5aR9Pp+aZIUTj1nUgZucTlmyeNe8wl2pNm1TDNZHuoG1ogFj
+SFITR86ftxaYmn6oi5fbmbQN9n1zcSl4a+oAyldm1kwl2t8tbWlxmbfLiiPEkWlP
+MZky8x63apLPx1aJ3cf8Ie+ZwmHSYHSvNP89KdLnHOhRULW1KejVyLjmmnlEIRTr
+RUY=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/test/ssl/readme.txt b/test/ssl/readme.txt
new file mode 100644 (file)
index 0000000..bbb6ad3
--- /dev/null
@@ -0,0 +1,2 @@
+This directory contains certificates and keys required for SSL testing.
+The CA key has password "password".
diff --git a/test/ssl/server-expired.crt b/test/ssl/server-expired.crt
new file mode 100644 (file)
index 0000000..cf3824c
--- /dev/null
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICvjCCAicCCQDNrg5WSiiRqTANBgkqhkiG9w0BAQUFADCBoDELMAkGA1UEBhMC
+R0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTEdMBsG
+A1UECgwUTW9zcXVpdHRvIFRlc3QgU3VpdGUxDzANBgNVBAsMBkJyb2tlcjEXMBUG
+A1UEAwwOYnJva2VyLXRlc3QtY2ExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBs
+ZS5jb20wHhcNMTIwNzAzMTU1MDE1WhcNMTIwNzA0MTU1MDE1WjCBpTELMAkGA1UE
+BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTEd
+MBsGA1UECgwUTW9zcXVpdHRvIFRlc3QgU3VpdGUxFDASBgNVBAsMC0Jyb2tlciBU
+ZXN0MRcwFQYDVQQDDA5sb2NhbGhvc3QtdGVzdDEfMB0GCSqGSIb3DQEJARYQdGVz
+dEBleGFtcGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAttnPoWjp
+KXROyGM2yqR7IGVmn4RID5hdCCWiJT1s0cSm3BwY3aMD1m/ZrCZfMu3K6tA+9rrh
+xMjEVCmG4ez4UdTv+xfxRalo2SkA2J6Yiti5/ec8Hjh6m3ch9F8Ju62XsS5KZl0Q
+oOE5D7UrMayq2eflBlO02qobn8114MIc0EkCAwEAATANBgkqhkiG9w0BAQUFAAOB
+gQC2KotrVoQCtsqW54VbcaCyHki9GpYw2QR1Ex+0sRCLcr2HhUK471D8BCooNo53
+Kft0yEclN1x5j8I7Rk6QmLmrXDeZBrRqSasDo0glYGCN8QwoVfx5L54r0ktEGDvr
+4PUWTieyuLKbFB+be0esM+/5IwpdsgVZuDI3D4jBR53SgQ==
+-----END CERTIFICATE-----
diff --git a/test/ssl/server.crt b/test/ssl/server.crt
new file mode 100644 (file)
index 0000000..7a1b776
--- /dev/null
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICvjCCAicCCQDNrg5WSiiRpDANBgkqhkiG9w0BAQUFADCBoDELMAkGA1UEBhMC
+R0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTEdMBsG
+A1UECgwUTW9zcXVpdHRvIFRlc3QgU3VpdGUxDzANBgNVBAsMBkJyb2tlcjEXMBUG
+A1UEAwwOYnJva2VyLXRlc3QtY2ExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBs
+ZS5jb20wHhcNMTIwNzAzMTEzMjM0WhcNMzkxMTE4MTEzMjM0WjCBpTELMAkGA1UE
+BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTEd
+MBsGA1UECgwUTW9zcXVpdHRvIFRlc3QgU3VpdGUxFDASBgNVBAsMC0Jyb2tlciBU
+ZXN0MRcwFQYDVQQDDA5sb2NhbGhvc3QtdGVzdDEfMB0GCSqGSIb3DQEJARYQdGVz
+dEBleGFtcGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAttnPoWjp
+KXROyGM2yqR7IGVmn4RID5hdCCWiJT1s0cSm3BwY3aMD1m/ZrCZfMu3K6tA+9rrh
+xMjEVCmG4ez4UdTv+xfxRalo2SkA2J6Yiti5/ec8Hjh6m3ch9F8Ju62XsS5KZl0Q
+oOE5D7UrMayq2eflBlO02qobn8114MIc0EkCAwEAATANBgkqhkiG9w0BAQUFAAOB
+gQCNcNqm8mb7K/ys+3LENUB7XccA1gzyb3ylpsqQj5TmGYqT+Z1g7pSw0Pbd94Uc
+x+ihqjRo5Eaz7GqCyS7mnNu5aGBHH3s1ir9hT18R7tm+XwMTQcGoRy986O1BJy+r
+q1Gg0lmgvu+jlYpR4xJHGzd3wK8agi+y9ZSAlfAZ6hJkrw==
+-----END CERTIFICATE-----
diff --git a/test/ssl/server.csr b/test/ssl/server.csr
new file mode 100644 (file)
index 0000000..81f5dff
--- /dev/null
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIB5jCCAU8CAQAwgaUxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5Vbml0ZWQgS2lu
+Z2RvbTEOMAwGA1UEBwwFRGVyYnkxHTAbBgNVBAoMFE1vc3F1aXR0byBUZXN0IFN1
+aXRlMRQwEgYDVQQLDAtCcm9rZXIgVGVzdDEXMBUGA1UEAwwObG9jYWxob3N0LXRl
+c3QxHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcN
+AQEBBQADgY0AMIGJAoGBALbZz6Fo6Sl0TshjNsqkeyBlZp+ESA+YXQgloiU9bNHE
+ptwcGN2jA9Zv2awmXzLtyurQPva64cTIxFQphuHs+FHU7/sX8UWpaNkpANiemIrY
+uf3nPB44ept3IfRfCbutl7EuSmZdEKDhOQ+1KzGsqtnn5QZTtNqqG5/NdeDCHNBJ
+AgMBAAGgADANBgkqhkiG9w0BAQUFAAOBgQBDqZNA2bsljTddqvAONJDLXv9R7mTy
+sGHIRlQoV/p8GMywBaOzh1T5H3RdUKBDKN8Kt9nNW8Xfqi9vJGPse4ZBq11FoC+b
+59aFTlh+IXQu0rH9r1E8htjcMdNdzDSFxcD/6cwp1uiFm/2YbYl0iojsKLxbVlaK
+jMIfJi3EpeDyHQ==
+-----END CERTIFICATE REQUEST-----
diff --git a/test/ssl/server.key b/test/ssl/server.key
new file mode 100644 (file)
index 0000000..dd76952
--- /dev/null
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQC22c+haOkpdE7IYzbKpHsgZWafhEgPmF0IJaIlPWzRxKbcHBjd
+owPWb9msJl8y7crq0D72uuHEyMRUKYbh7PhR1O/7F/FFqWjZKQDYnpiK2Ln95zwe
+OHqbdyH0Xwm7rZexLkpmXRCg4TkPtSsxrKrZ5+UGU7TaqhufzXXgwhzQSQIDAQAB
+AoGAXUSq8SVHUXrfOL3K1ACkQXkXqKRb8YCBa8dudtpnKHTLvBik4mDlczsoZ/RG
+uP6sc6v3gfj/clYKNvfbsmAipRWfHVC157vBlEiBfAoBbNgicF/4dCOSGsDYStOy
+F88l1SvcDWjK6u33gj/SBHDMz6SOam2muXZNZa0brSSW2tUCQQDrQTuTHkf4PgUa
+5a4stlx4bplAEtJGJvXt4k2xXvtZ1UW/G7xMspphQb8n2UB3uQeXXV6cJAmHnmx2
+2ghxje0zAkEAxvmVMb2ZRmeIfiUOSFXPjtKNqJZG8hpQ8i2yDuyq69Hi7L3SQSGN
+V9uPceEdVW+IQEOJg2feXhmlfCNWIFP0kwJBAKy5HzlbsTGEr5DY8zFmzqupYCEX
+8ISLFGMMlUhV2StSl7vBbFXPh+NCN0vViSydkAJFDjKLjuegnDgCytI8htsCQA/N
+gLjrmwHJdUC3hrPeBNcOB+wsy0OtLWKemHaw+z4xdDljNhCwLn6c1H6x51eCvSqF
+cqV6GWIV3VvHnq6AnHsCQBqyU46p0dax4z+5vzbL+zWmi8gdBYvPSY5SpAjsQADQ
+A3PcKi2DFuPjcxdl0qr9aq1qg6VUHy3RLTcmgA8YKWo=
+-----END RSA PRIVATE KEY-----
diff --git a/test/ssl/test-ca-alt.crt b/test/ssl/test-ca-alt.crt
new file mode 100644 (file)
index 0000000..37c540c
--- /dev/null
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHjCCAoegAwIBAgIJAK2kGB3tYrLVMA0GCSqGSIb3DQEBBQUAMIGnMQswCQYD
+VQQGEwJHQjEXMBUGA1UECAwOVW5pdGVkIEtpbmdkb20xDjAMBgNVBAcMBURlcmJ5
+MR0wGwYDVQQKDBRNb3NxdWl0dG8gVGVzdCBTdWl0ZTEXMBUGA1UECwwOQWx0ZXJu
+YXRpdmUgQ0ExFDASBgNVBAMMC3Rlc3QtY2EtYWx0MSEwHwYJKoZIhvcNAQkBFhJj
+YS1hbHRAZXhhbXBsZS5jb20wHhcNMTIwNzAzMTQ1MDI3WhcNMzkxMTE5MTQ1MDI3
+WjCBpzELMAkGA1UEBhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYD
+VQQHDAVEZXJieTEdMBsGA1UECgwUTW9zcXVpdHRvIFRlc3QgU3VpdGUxFzAVBgNV
+BAsMDkFsdGVybmF0aXZlIENBMRQwEgYDVQQDDAt0ZXN0LWNhLWFsdDEhMB8GCSqG
+SIb3DQEJARYSY2EtYWx0QGV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQDxlFX2Ihc2Uk0ksPe0EJoULBKfr3b5LEuTEqocnypXZ52i61sx8DPd
+HM1EBjlxSrUGxPR0mVoL7d/i9kgEs+4seeNeXn27Vot4Wd+jPTyaHbziLUG1L/nZ
+112hWfAfqTU6MUFAkv5BNCoHZZKXLybP4tBXgHpwrVzXa9f3hUGfMwIDAQABo1Aw
+TjAdBgNVHQ4EFgQUZik8IjLHGc/taXUD60zLp4TA3gkwHwYDVR0jBBgwFoAUZik8
+IjLHGc/taXUD60zLp4TA3gkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOB
+gQAomJg4uKFY3Bi5+k+63O/Ze0SaV9gsgO9GWwb1Jjyi3ZyFTzCGWr3XsD9DsOyT
+gHwAzUgDFIyYVXc3kgBBg54wjEA8A7yQ++HIsutEIR3XykbBfU2oS0VbPKejsPrS
+4zuGt3nQdYrKI2iD207HG6XiO0VfUTro6BGuazvsfE9jGg==
+-----END CERTIFICATE-----
diff --git a/test/ssl/test-ca-alt.key b/test/ssl/test-ca-alt.key
new file mode 100644 (file)
index 0000000..2e14911
--- /dev/null
@@ -0,0 +1,17 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQImtEogQ274ucCAggA
+MBQGCCqGSIb3DQMHBAgmCV4tCw6jVwSCAoA7cj6maNe06C2LjYbSgRMr4s2DIini
+2E1KqqRnrz1Ie3U9a4OzmToGuwDqnYR1uK6ImWHwzFZxXkIfsmJUra2BEnSj7W1N
+qGPiQwsBD6L93YyejwoTRFMkkFw6pgdg64eyIstNxYMiiJ9cB+6dc4Z+kY4Ik7Fg
+4xR1+PTHVbNpHeM2ApUHuaoOyaMijzdhwwMbUegVdnsrjb6kgM/5EoIsrxvpax4E
+JSIk+DpdQ8jspwC6n6RNfYhOJdk6ECSi6Wszz1vfLb76YkSFvSsucxYFK7iH6vW2
+llrW/GqfA1+KQwUbD7RULSne+TIWqkk+Z3u/gpDsDe6qf17b3DLcJFGqRdUZXZ9G
+lJfGiWYBro3m/z8gELAfKqVf5BbRYdfAqXdNRHqQkC/VvwsqGknRO0XIawzHxZLA
+OajqAZ4MX2lG/GGYGv51bpnB6B7gdT4LcXtJAUUfezBiu+aw0cFxx3Mox+gPQgKy
+YimuMLPTVaayFfe963odDwVUTcmh48dIvfvfHonvJA8n6pdF3dl+F4FcJ3yTUdBf
+LivlIuXtbobm2ANR4aBrISP47tug11XKs92nGBv5fgvmALr8qjbMLd4naKjA3HR6
+g36cRAu5XBSqN8UNpNyw1lQrQMsNHlFtHhvD5pdh5KuXf9KJrVt6PVUuXCzOb4fW
+EvcSSNR2xIJeRkPwdgAIasnnThyCWQPxm+SoYvogNuRMuy/T2k4Y+RtofJ12KfPr
+mI7M6x2/TPuSbu9Vnee7Xt67JyCLAv1RLXrzqIIJlHrS4hw7Oza11CBCDbYVT3UE
+8Wf1a+L4dF6TSp4NGY6KXaISjvjGGCyueVwH8/YrxnfMk80HvVLNlS3g
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/test/ssl/test-ca.crt b/test/ssl/test-ca.crt
new file mode 100644 (file)
index 0000000..64e29f9
--- /dev/null
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDEDCCAnmgAwIBAgIJALKg/M/sjw95MA0GCSqGSIb3DQEBBQUAMIGgMQswCQYD
+VQQGEwJHQjEXMBUGA1UECAwOVW5pdGVkIEtpbmdkb20xDjAMBgNVBAcMBURlcmJ5
+MR0wGwYDVQQKDBRNb3NxdWl0dG8gVGVzdCBTdWl0ZTEPMA0GA1UECwwGQnJva2Vy
+MRcwFQYDVQQDDA5icm9rZXItdGVzdC1jYTEfMB0GCSqGSIb3DQEJARYQdGVzdEBl
+eGFtcGxlLmNvbTAeFw0xMjA3MDMxMTMwMDdaFw0zOTExMTkxMTMwMDdaMIGgMQsw
+CQYDVQQGEwJHQjEXMBUGA1UECAwOVW5pdGVkIEtpbmdkb20xDjAMBgNVBAcMBURl
+cmJ5MR0wGwYDVQQKDBRNb3NxdWl0dG8gVGVzdCBTdWl0ZTEPMA0GA1UECwwGQnJv
+a2VyMRcwFQYDVQQDDA5icm9rZXItdGVzdC1jYTEfMB0GCSqGSIb3DQEJARYQdGVz
+dEBleGFtcGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAxdqM/gN/
+nRuHdvjKQ3nOIHzblSQRmnU17AvusuucKaEAaIBJO05pUryoUfAPhb/QtPbiqGQ+
+VZN5n2Di4MxgYTdQ9SAjJKv/v6TNm23IjlYgt5XnXbvZhGid/FrTjsldPVEKKKM/
+DTPx24o3coE2KxDOjnfGsR20LPnmEp7icBkCAwEAAaNQME4wHQYDVR0OBBYEFO2l
+nlq5L/AmkNaZ0s7OqJr1KoUtMB8GA1UdIwQYMBaAFO2lnlq5L/AmkNaZ0s7OqJr1
+KoUtMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAEaeNG4qmUrJtQqR6
+Kq8rIYBfYpkhlWTgo/uOxo8OgAjwhrYja7SBiSDOk7dk+OPdvMxxLIalzb+4IaoH
+IMoFBsCuJApFBEkaxE4W6AZA7wjtwq5t3HbRtRD44soBtoeHhPILFfKuWpYVM4Vg
+esxJKdf10bGX48nXFREBcSg6ce4=
+-----END CERTIFICATE-----
diff --git a/test/ssl/test-ca.key b/test/ssl/test-ca.key
new file mode 100644 (file)
index 0000000..13f7a41
--- /dev/null
@@ -0,0 +1,17 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI9H1Lfg1rh7wCAggA
+MBQGCCqGSIb3DQMHBAi1Lzw2lwTCCgSCAoAJV4++qH5sbnxx4yOO2OYOn8ceZePP
+xjfFHgG3D6AxrQtNnzIqk/WTeSOJKPn3jt5AZmpr///t2cEb45KZFPJff/CzXtfM
+jxhbklkTZLBv+4S7Z/Me0Z14eJdjk2uGDnV2dJdCOB7U+Ig06dfyliHktC/CIqiH
+5v9qsGog9Dv1oauN6HGcWjIz/fXI3THBRlynNo4tv1BOaoxDXTYenHgo2v4CXmsQ
+chOQX6u/RDDpht9Dm45IFQKcHfHbHIpB9s0iXXm+9kbDAdxIRfNV2EywK53Xm3D8
+u0iynbmAlUh30d0n+0sLEbITrs6BTS+/eCvRUnPqerYTVBOpGb+o4GiVLm3LsogH
+3nuh//JlRDD894rHLqQQ4uzXHvn1fVFNe+th8kjiIVEr6NevIIBPvTnPrxSFco4k
+CHmOMmm+hhp2B2sZa2IFfIKJ64DrmiT5mezG4aMRjkB2PK0MNDctoYdhQYmB01VR
+1P0r5svnt39tNep6jQhydiMOXhyhX2AVfbTBG9izoi0Sn2eQ0tYtlT+oQzw7yxK2
+7MOo7PTemlvIsCVgyL1+OkVSBn+n0nHEgRd+DNsu1gsmetUpENfYtqjqnKD6FDLN
+gOPJ3Eoj7XIo5qTrKbJd2EQNlzMFCMikfWHcijRLioEhS9tx8PHppdP7MSinXAYc
+IsWN1+4lIHj8jsnEWDp3UWy6FkLFsy5iOayWsC1PjJqo4yVdsYJ/Ef34g5IBBB1x
+AC7Orq6ZGWoV4jFXkzFj/FhOpf9G6wpQ30qiW/wnVUT09Nr45vsOqcLChLxUO7s7
+VQk9XcDNhUJQB9uVUgF+Z536CG6U9K9fZSqK72iN/1t+dhQn/eZbrwLL
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/test/ssl/test-ca.srl b/test/ssl/test-ca.srl
new file mode 100644 (file)
index 0000000..53b5530
--- /dev/null
@@ -0,0 +1 @@
+CDAE0E564A2891AA