diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a8a62b5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,427 @@
+# Mosquitto-PHP
+
+This is an extension to allow using the [Mosquitto MQTT library](http://mosquitto.org) with PHP. See the examples/ directory for usage.
+
+[![Build Status](https://travis-ci.org/mgdm/Mosquitto-PHP.svg?branch=master)](https://travis-ci.org/mgdm/Mosquitto-PHP)
+
+## Requirements
+
+* PHP 5.3+
+* libmosquitto 1.2.x
+* Linux or Mac OS X. I do not have a Windows machine handy, though patches or
+ pull requests are of course very welcome!
+
+## Installation
+
+You may obtain this package using [PECL](http://pecl.php.net):
+
+````
+pecl install Mosquitto-alpha
+````
+
+Alternatively, you can use the normal extension build process:
+
+````
+phpize
+./configure --with-mosquitto=/path/to/libmosquitto
+make
+make install
+````
+
+Then add `extension=mosquitto.so` to your `php.ini`.
+
+The `--with-mosquitto` argument is optional, and only required if your
+libmosquitto install cannot be found.
+
+## Documentation
+
+The classes in this extension are namespaced.
+
+### Class Mosquitto\Client
+
+This is the actual Mosquitto client.
+
+1. [__construct](#__construct) - create a new client
+1. [setCredentials](#setcredentials) - set the credentials to use on connection
+1. [setTlsCertificates](#settlscertificates) - set the TLS certificate sources
+1. [setTlsInsecure](#settlsinsecure) - Set verification of the server hostname
+ in TLS certificates
+1. [setTlsOptions](#settlsoptions) - Set advanced TLS options
+1. [setTlsPSK](#settlspsk) - Configure the client for pre-shared-key based TLS
+ support.
+1. [setWill](#setwill) - set the client will, to be delivered if disconnected
+ uncleanly
+1. [clearWill](#clearwill) - clear a previously-set will
+1. [setReconnectDelay](#setreconnectdelay) - set the behaviour if disconnected
+ uncleanly
+1. [connect](#connect) - connect to an MQTT broker
+1. [disconnect](#disconnect) - disconnect from an MQTT broker
+1. [onConnect](#onconnect) - set the connect callback
+1. [onDisconnect](#ondisconnect) - set the disconnect callback
+1. [onLog](#onlog) - set the logging callback
+1. [onSubscribe](#onsubscribe) - set the subscribe callback
+1. [onMessage](#onmessage) - set the callback fired when a message is received
+1. [setMaxInFlightMessages](#setmaxinflightmessages) - set the number of QoS
+ 1 and 2 messages that can be "in flight" at once
+1. [setMessageRetry](#setmessageretry) - set the number of seconds to wait
+ before retrying messages
+1. [publish](#publish) - publish a message to a broker
+1. [subscribe](#subscribe) - subscribe to a topic
+1. [unsubscribe](#unsubscribe) - unsubscribe from a topic
+1. [loop](#loop) - The main network loop
+1. [loopForever](#loopforever) - run loop() in an infinite blocking loop
+
+#### __construct
+
+Creates a new Client instance.
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| id | string | Client ID. Optional. If not supplied or NULL, one will be generated at random. |
+| clean_session | boolean | Set to true to instruct the broker to clean all messages and subscriptions on disconnect. |
+
+#### setCredentials
+
+Set the username and password to use on connecting to the broker. Must be
+called before connect().
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| Username | string | Username to supply to the broker |
+| Password | string | Password to supply to the broker |
+
+#### setTlsCertificates
+
+Configure the client for certificate based SSL/TLS support. Must be called
+before connect(). Cannot be used in conjunction with setTlsPSK().
+
+Define the Certificate Authority certificates to be trusted (ie. the server
+certificate must be signed with one of these certificates) using cafile.
+If the server you are connecting to requires clients to provide a certificate,
+define certfile and keyfile with your client certificate and private key. If
+your private key is encrypted, provide the password as the fourth parameter, or
+you will have to enter the password at the command line.
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| capath | string | Path to the PEM encoded trusted CA certificate files, or to a directory containing them |
+| certfile | string | Path to the PEM encoded certificate file for this client. Optional. |
+| keyfile | string | Path to a file containing the PEM encoded private key for this client. Required if certfile is set. |
+| password | string | The password for the keyfile, if it is encrypted. If null, the password will be asked for on the command line. |
+
+#### setTlsInsecure
+
+Configure verification of the server hostname in the server certificate. If
+value is set to true, it is impossible to guarantee that the host you are
+connecting to is not impersonating your server. Do not use this function in
+a real system. Must be called before connect().
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| value | boolean | If set to false, the default, certificate hostname checking is performed. If set to true, no hostname checking is performed and the connection is insecure. |
+
+#### setTlsOptions
+
+Set advanced SSL/TLS options. Must be called before connect().
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| certReqs | int | Whether or not to verify the server. Can be Mosquitto\Client::SSL_VERIFY_NONE, to disable certificate verification, or Mosquitto\Client::SSL_VERIFY_PEER (the default), to verify the server certificate. |
+| tlsVersion | string | The TLS version to use. If NULL, a default is used. The default value depends on the version of OpenSSL the library was compiled against. Available options on OpenSSL >= 1.0.1 are 'tlsv1.2', 'tlsv1.1' and 'tlsv1'. |
+| cipers | string | A string describing the ciphers available for use. See the `openssl ciphers` tool for more information. If NULL, the default set will be used. |
+
+#### setTlsPSK
+
+Configure the client for pre-shared-key based TLS support. Must be called before connect(). Cannot be used in conjunction with setTlsCertificates.
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| psk | string | The pre-shared key in hex format with no leading "0x".
+| identity | string | The identity of this client. May be used as the username depending on server settings. |
+| cipers | string | Optional. A string describing the ciphers available for use. See the `openssl ciphers` tool for more information. If NULL, the default set will be used. |
+
+#### setWill
+
+Set the client "last will and testament", which will be sent on an unclean
+disconnection from the broker. Must be called before connect().
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| topic | string | The topic on which to publish the will. |
+| payload | string | The data to send. |
+| qos | int | Optional. Default 0. Integer 0, 1, or 2 indicating the Quality of Service to be used. |
+| retain | boolean | Optional. Default false. If true, the message will be retained. |
+
+#### clearWill
+
+Remove a previously-set will. No parameters.
+
+#### setReconnectDelay
+
+Control the behaviour of the client when it has unexpectedly disconnected in
+loopForever. The default behaviour if this method is not used is to
+repeatedly attempt to reconnect with a delay of 1 second until the connection
+succeeds.
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| reconnect_delay | int | Set delay between successive reconnection attempts. |
+| reconnect_delay | int | Set max delay between successive reconnection attempts when exponential backoff is enabled |
+| exponential_backoff | bool | Enable exponential backoff |
+
+#### connect
+
+Connect to an MQTT broker.
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| host | string | Hostname to connect to |
+| port | int | Optional. Port number to connect to. Defaults to 1883. |
+| keepalive | int | Optional. Number of sections after which the broker should PING the client if no messages have been recieved. |
+| interface | string | Optional. The address or hostname of a local interface to bind to for this connection. |
+
+#### disconnect
+
+Disconnect from the broker. No parameters.
+
+#### onConnect
+
+Set the connect callback. This is called when the broker sends a CONNACK message in response to a connection.
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| callback | callback | The callback |
+
+The callback should take parameters of the form:
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| rc | int | Response code from the broker. |
+| message | string | String description of the response code. |
+
+Response codes are as follows:
+
+| Code | Meaning |
+| --- | --- |
+| 0 | Success |
+| 1 | Connection refused (unacceptable protocol version) |
+| 2 | Connection refused (identifier rejected) |
+| 3 | Connection refused (broker unavailable ) |
+| 4-255 | Reserved for future use |
+
+#### onDisconnect
+
+Set the disconnect callback. This is called when the broker has received the
+DISCONNECT command and has disconnected the client.
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| callback | callback | The callback |
+
+The callback should take parameters of the form:
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| rc | int | Reason for the disconnection. 0 means the client requested it. Any other value indicates an unexpected disconnection. |
+
+#### onLog
+
+Set the logging callback.
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| callback | callback | The callback |
+
+The callback should take parameters of the form:
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| level | int | The log message level from the values below |
+| str | string | The message string.
+
+The level can be one of:
+
+* Mosquitto\Client::LOG_DEBUG
+* Mosquitto\Client::LOG_INFO
+* Mosquitto\Client::LOG_NOTICE
+* Mosquitto\Client::LOG_WARNING
+* Mosquitto\Client::LOG_ERR
+
+#### onSubscribe
+
+Set the subscribe callback. This is called when the broker responds to
+a subscription request.
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| callback | callback | The callback |
+
+The callback should take parameters of the form:
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| mid | int | Message ID of the subscribe message |
+| qos_count | int | Number of granted subscriptions |
+
+This function needs to return the granted QoS for each subscription, but
+currently cannot.
+
+#### onUnsubscribe
+
+Set the unsubscribe callback. This is called when the broker responds to
+a unsubscribe request.
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| callback | callback | The callback |
+
+The callback should take parameters of the form:
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| mid | int | Message ID of the unsubscribe message |
+
+#### onMessage
+
+Set the message callback. This is called when a message is received from the broker.
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| callback | callback | The callback |
+
+The callback should take parameters of the form:
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| message | Mosquitto\Message | A Message object containing the message data |
+
+
+#### setMaxInFlightMessages
+
+Set the number of QoS 1 and 2 messages that can be “in flight” at one time. An
+in flight message is part way through its delivery flow. Attempts to send
+further messages with publish() will result in the messages being queued until
+the number of in flight messages reduces.
+
+Set to 0 for no maximum.
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| max_inflight_messages | int | The maximum |
+
+#### setMessageRetry
+
+Set the number of seconds to wait before retrying messages. This applies to
+publish messages with QoS>0. May be called at any time.
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| message_retry | int | The retry period |
+
+#### publish
+
+Publish a message on a given topic.
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| topic | string | The topic to publish on |
+| payload | string | The message payload |
+| qos | int | Integer value 0, 1 or 2 indicating the QoS for this message |
+| retain | boolean | If true, make this message retained |
+
+#### subscribe
+
+Subscribe to a topic.
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| topic | string | The topic. |
+| qos | The QoS to request for this subscription |
+
+Returns the message ID of the subscription message, so this can be matched up
+in the onSubscribe callback.
+
+#### unsubscribe
+
+Unsubscribe from a topic.
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| topic | string | The topic. |
+| qos | The QoS to request for this subscription |
+
+Returns the message ID of the subscription message, so this can be matched up
+in the onUnsubscribe callback.
+
+#### loop
+
+The main network loop for the client. You must call this frequently in order
+to keep communications between the client and broker working. 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.
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| timeout | int | Optional. Number of milliseconds to wait for network activity. Pass 0 for instant timeout. Defaults to 1000. |
+| max_packets | int | Currently unused. |
+
+#### loopForever
+
+Call loop() in an infinite blocking loop. Callbacks will be called as required.
+This will handle reconnecting if the connection is lost. Call disconnect() in
+a callback to return from the loop.
+
+Note: exceptions thrown in callbacks do not currently cause the loop to exit. To work around this, use loop() and wrap your own loop structure around it such as a while().
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| timeout | int | Optional. Number of milliseconds to wait for network activity. Pass 0 for instant timeout. Defaults to 1000. |
+| max_packets | int | Currently unused. |
+
+### Class Mosquitto\Message
+
+Represents a message received from a broker. All data is represented as
+properties.
+
+| Property | Type | Description |
+| --- | --- | --- |
+| topic | string | The topic this message was delivered to. |
+| payload | string | The payload of this message. |
+| mid | int | The ID of this message. |
+| qos | int | The QoS value applied to this message. |
+| retain | bool | Whether this is a retained message or not. |
+
+This class has two static methods.
+
+#### topicMatchesSub
+
+Returns true if the supplied topic matches the supplied description, and
+otherwise false.
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| topic | string | The topic to match |
+| subscription | string | The subscription to match |
+
+#### tokeniseTopic
+
+Tokenise a topic or subscription string into an array of strings representing the topic hierarchy.
+
+| Parameter | Type | Description |
+| --- | --- | ---- |
+| topic | string | The topic to tokenise |
+
+### Class Mosquitto\Exception
+
+This is an exception that may be thrown by many of the operations in the Client
+object.
+
+## To do
+
+* Arginfo
+* Logging callbacks
+* TLS support
+* Tests
diff --git a/mosquitto_message.c b/mosquitto_message.c
new file mode 100644
index 0000000..509708d
--- /dev/null
+++ b/mosquitto_message.c
@@ -0,0 +1,425 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "zend_variables.h"
+#include "zend_exceptions.h"
+#include "zend_API.h"
+#include "ext/standard/info.h"
+#include "php_mosquitto.h"
+
+zend_class_entry *mosquitto_ce_message;
+static zend_object_handlers mosquitto_message_object_handlers;
+static HashTable php_mosquitto_message_properties;
+
+/* {{{ Arginfo */
+
+ZEND_BEGIN_ARG_INFO(Mosquitto_Message_topicMatchesSub_args, ZEND_SEND_BY_VAL)
+ ZEND_ARG_INFO(0, topic)
+ ZEND_ARG_INFO(0, subscription)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(Mosquitto_Message_tokeniseTopic_args, ZEND_SEND_BY_VAL)
+ ZEND_ARG_INFO(0, topic)
+ZEND_END_ARG_INFO()
+
+/* }}} */
+
+PHP_METHOD(Mosquitto_Message, __construct)
+{
+ PHP_MOSQUITTO_ERROR_HANDLING();
+ if (zend_parse_parameters_none() == FAILURE) {
+ PHP_MOSQUITTO_RESTORE_ERRORS();
+ return;
+ }
+ PHP_MOSQUITTO_RESTORE_ERRORS();
+}
+
+/* {{{ Mosquitto\Message::topicMatchesSub() */
+PHP_METHOD(Mosquitto_Message, topicMatchesSub)
+{
+ char *topic = NULL, *subscription = NULL;
+ int topic_len, subscription_len;
+ zend_bool result;
+
+ PHP_MOSQUITTO_ERROR_HANDLING();
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",
+ &topic, &topic_len, &subscription, &subscription_len) == FAILURE) {
+ PHP_MOSQUITTO_RESTORE_ERRORS();
+ return;
+ }
+ PHP_MOSQUITTO_RESTORE_ERRORS();
+
+ mosquitto_topic_matches_sub(subscription, topic, (bool *) &result);
+ RETURN_BOOL(result);
+}
+/* }}} */
+
+/* {{{ Mosquitto\Message::tokeniseTopic() */
+PHP_METHOD(Mosquitto_Message, tokeniseTopic)
+{
+ char *topic = NULL, **topics = NULL;
+ int topic_len = 0, retval = 0, count = 0, i = 0;
+
+ PHP_MOSQUITTO_ERROR_HANDLING();
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &topic, &topic_len) == FAILURE) {
+ PHP_MOSQUITTO_RESTORE_ERRORS();
+ return;
+ }
+ PHP_MOSQUITTO_RESTORE_ERRORS();
+
+ retval = mosquitto_sub_topic_tokenise(topic, &topics, &count);
+
+ if (retval == MOSQ_ERR_NOMEM) {
+ zend_throw_exception_ex(mosquitto_ce_exception, 0 TSRMLS_CC, "Failed to tokenise topic");
+ return;
+ }
+
+ array_init(return_value);
+ for (i = 0; i < count; i++) {
+ if (topics[i] == NULL) {
+ add_next_index_null(return_value);
+ } else {
+ add_next_index_string(return_value, topics[i], 1);
+ }
+ }
+
+ mosquitto_sub_topic_tokens_free(&topics, count);
+}
+/* }}} */
+
+PHP_MOSQUITTO_MESSAGE_LONG_PROPERTY_READER_FUNCTION(mid);
+PHP_MOSQUITTO_MESSAGE_LONG_PROPERTY_READER_FUNCTION(qos);
+
+static int php_mosquitto_message_read_retain(mosquitto_message_object *mosquitto_object, zval **retval TSRMLS_DC)
+{
+ MAKE_STD_ZVAL(*retval);
+ ZVAL_BOOL(*retval, mosquitto_object->message.retain);
+ return SUCCESS;
+}
+
+static int php_mosquitto_message_read_topic(mosquitto_message_object *mosquitto_object, zval **retval TSRMLS_DC)
+{
+ MAKE_STD_ZVAL(*retval);
+
+ if (mosquitto_object->message.topic != NULL) {
+ ZVAL_STRINGL(*retval, mosquitto_object->message.topic, strlen(mosquitto_object->message.topic), 1);
+ } else {
+ ZVAL_NULL(*retval);
+ }
+
+ return SUCCESS;
+}
+
+static int php_mosquitto_message_read_payload(mosquitto_message_object *mosquitto_object, zval **retval TSRMLS_DC)
+{
+ MAKE_STD_ZVAL(*retval);
+ ZVAL_STRINGL(*retval, mosquitto_object->message.payload, mosquitto_object->message.payloadlen, 1);
+ return SUCCESS;
+}
+
+PHP_MOSQUITTO_MESSAGE_LONG_PROPERTY_WRITER_FUNCTION(mid);
+PHP_MOSQUITTO_MESSAGE_LONG_PROPERTY_WRITER_FUNCTION(qos);
+
+static int php_mosquitto_message_write_retain(mosquitto_message_object *mosquitto_object, zval *newval TSRMLS_DC)
+{
+ zval ztmp;
+ if (Z_TYPE_P(newval) != IS_BOOL) {
+ ztmp = *newval;
+ zval_copy_ctor(&ztmp);
+ convert_to_boolean(&ztmp);
+ newval = &ztmp;
+ }
+
+ mosquitto_object->message.retain = Z_LVAL_P(newval);
+
+ if (newval == &ztmp) {
+ zval_dtor(newval);
+ }
+
+ return SUCCESS;
+}
+
+static int php_mosquitto_message_write_topic(mosquitto_message_object *mosquitto_object, zval *newval TSRMLS_DC)
+{
+ zval ztmp;
+ if (Z_TYPE_P(newval) != IS_STRING) {
+ ztmp = *newval;
+ zval_copy_ctor(&ztmp);
+ convert_to_string(&ztmp);
+ newval = &ztmp;
+ }
+
+ if (mosquitto_object->message.topic && mosquitto_object->owned_topic) {
+ efree(mosquitto_object->message.topic);
+ }
+
+ mosquitto_object->message.topic = estrdup(Z_STRVAL_P(newval));
+ mosquitto_object->owned_topic = 1;
+
+ if (newval == &ztmp) {
+ zval_dtor(newval);
+ }
+
+ return SUCCESS;
+}
+
+static int php_mosquitto_message_write_payload(mosquitto_message_object *mosquitto_object, zval *newval TSRMLS_DC)
+{
+ zval ztmp;
+ if (Z_TYPE_P(newval) != IS_STRING) {
+ ztmp = *newval;
+ zval_copy_ctor(&ztmp);
+ convert_to_string(&ztmp);
+ newval = &ztmp;
+ }
+
+ if (mosquitto_object->message.payload && mosquitto_object->owned_payload) {
+ efree(mosquitto_object->message.payload);
+ mosquitto_object->message.payloadlen = 0;
+ }
+
+ mosquitto_object->message.payload = estrdup(Z_STRVAL_P(newval));
+ mosquitto_object->message.payloadlen = Z_STRLEN_P(newval);
+ mosquitto_object->owned_payload = 1;
+
+ if (newval == &ztmp) {
+ zval_dtor(newval);
+ }
+
+ return SUCCESS;
+}
+
+const php_mosquitto_prop_handler php_mosquitto_message_property_entries[] = {
+ PHP_MOSQUITTO_MESSAGE_PROPERTY_ENTRY_RECORD(mid),
+ PHP_MOSQUITTO_MESSAGE_PROPERTY_ENTRY_RECORD(topic),
+ PHP_MOSQUITTO_MESSAGE_PROPERTY_ENTRY_RECORD(payload),
+ PHP_MOSQUITTO_MESSAGE_PROPERTY_ENTRY_RECORD(qos),
+ PHP_MOSQUITTO_MESSAGE_PROPERTY_ENTRY_RECORD(retain),
+ {NULL, 0, NULL, NULL}
+};
+
+zval *php_mosquitto_message_read_property(zval *object, zval *member, int type ZEND_LITERAL_KEY_DC TSRMLS_DC)
+{
+ zval tmp_member;
+ zval *retval;
+ mosquitto_message_object *message_object;
+ php_mosquitto_prop_handler *hnd;
+ int ret;
+
+ message_object = (mosquitto_message_object *) zend_object_store_get_object(object TSRMLS_CC);
+
+ if (Z_TYPE_P(member) != IS_STRING) {
+ tmp_member = *member;
+ zval_copy_ctor(&tmp_member);
+ convert_to_string(&tmp_member);
+ member = &tmp_member;
+ }
+
+ ret = zend_hash_find(&php_mosquitto_message_properties, Z_STRVAL_P(member), Z_STRLEN_P(member)+1, (void **) &hnd);
+
+ if (ret == SUCCESS && hnd->read_func) {
+ ret = hnd->read_func(message_object, &retval TSRMLS_CC);
+ if (ret == SUCCESS) {
+ /* ensure we're creating a temporary variable */
+ Z_SET_REFCOUNT_P(retval, 0);
+ } else {
+ retval = EG(uninitialized_zval_ptr);
+ }
+ } else {
+ zend_object_handlers * std_hnd = zend_get_std_object_handlers();
+ retval = std_hnd->read_property(object, member, type ZEND_LITERAL_KEY_CC TSRMLS_CC);
+ }
+
+ if (member == &tmp_member) {
+ zval_dtor(member);
+ }
+
+ return(retval);
+}
+
+void php_mosquitto_message_write_property(zval *object, zval *member, zval *value ZEND_LITERAL_KEY_DC TSRMLS_DC)
+{
+ zval tmp_member;
+ mosquitto_message_object *obj;
+ php_mosquitto_prop_handler *hnd;
+ int ret;
+
+ if (Z_TYPE_P(member) != IS_STRING) {
+ tmp_member = *member;
+ zval_copy_ctor(&tmp_member);
+ convert_to_string(&tmp_member);
+ member = &tmp_member;
+ }
+
+ ret = FAILURE;
+ obj = (mosquitto_message_object *)zend_objects_get_address(object TSRMLS_CC);
+
+ ret = zend_hash_find(&php_mosquitto_message_properties, Z_STRVAL_P(member), Z_STRLEN_P(member) + 1, (void **) &hnd);
+
+ if (ret == SUCCESS && hnd->write_func) {
+ hnd->write_func(obj, value TSRMLS_CC);
+ if (! PZVAL_IS_REF(value) && Z_REFCOUNT_P(value) == 0) {
+ Z_ADDREF_P(value);
+ zval_ptr_dtor(&value);
+ }
+ } else {
+ zend_object_handlers * std_hnd = zend_get_std_object_handlers();
+ std_hnd->write_property(object, member, value ZEND_LITERAL_KEY_CC TSRMLS_CC);
+ }
+
+ if (member == &tmp_member) {
+ zval_dtor(member);
+ }
+}
+
+static int php_mosquitto_message_has_property(zval *object, zval *member, int has_set_exists ZEND_LITERAL_KEY_DC TSRMLS_DC)
+{
+ php_mosquitto_prop_handler *hnd;
+ int ret = 0;
+
+ if (zend_hash_find(&php_mosquitto_message_properties, Z_STRVAL_P(member), Z_STRLEN_P(member) + 1, (void **)&hnd) == SUCCESS) {
+ switch (has_set_exists) {
+ case 2:
+ ret = 1;
+ break;
+ case 0: {
+ zval *value = php_mosquitto_message_read_property(object, member, BP_VAR_IS ZEND_LITERAL_KEY_CC TSRMLS_CC);
+ if (value != EG(uninitialized_zval_ptr)) {
+ ret = Z_TYPE_P(value) != IS_NULL? 1:0;
+ /* refcount is 0 */
+ Z_ADDREF_P(value);
+ zval_ptr_dtor(&value);
+ }
+ break;
+ }
+ default: {
+ zval *value = php_mosquitto_message_read_property(object, member, BP_VAR_IS ZEND_LITERAL_KEY_CC TSRMLS_CC);
+ if (value != EG(uninitialized_zval_ptr)) {
+ convert_to_boolean(value);
+ ret = Z_BVAL_P(value)? 1:0;
+ /* refcount is 0 */
+ Z_ADDREF_P(value);
+ zval_ptr_dtor(&value);
+ }
+ break;
+ }
+ }
+ } else {
+ zend_object_handlers * std_hnd = zend_get_std_object_handlers();
+ ret = std_hnd->has_property(object, member, has_set_exists ZEND_LITERAL_KEY_CC TSRMLS_CC);
+ }
+ return ret;
+}
+
+static HashTable *php_mosquitto_message_get_properties(zval *object TSRMLS_DC)
+{
+ mosquitto_message_object *obj;
+ php_mosquitto_prop_handler *hnd;
+ HashTable *props;
+ zval *val;
+ char *key;
+ uint key_len;
+ HashPosition pos;
+ ulong num_key;
+
+ obj = (mosquitto_message_object *)zend_objects_get_address(object TSRMLS_CC);
+ props = zend_std_get_properties(object TSRMLS_CC);
+
+ zend_hash_internal_pointer_reset_ex(&php_mosquitto_message_properties, &pos);
+
+ while (zend_hash_get_current_data_ex(&php_mosquitto_message_properties, (void**)&hnd, &pos) == SUCCESS) {
+ zend_hash_get_current_key_ex(&php_mosquitto_message_properties, &key, &key_len, &num_key, 0, &pos);
+ if (!hnd->read_func || hnd->read_func(obj, &val TSRMLS_CC) != SUCCESS) {
+ val = EG(uninitialized_zval_ptr);
+ Z_ADDREF_P(val);
+ }
+ zend_hash_update(props, key, key_len, (void *)&val, sizeof(zval *), NULL);
+ zend_hash_move_forward_ex(&php_mosquitto_message_properties, &pos);
+ }
+ return obj->std.properties;
+}
+
+
+void php_mosquitto_message_add_property(HashTable *h, const char *name, size_t name_length, php_mosquitto_read_t read_func, php_mosquitto_write_t write_func TSRMLS_DC)
+{
+ php_mosquitto_prop_handler p;
+
+ p.name = (char*) name;
+ p.name_length = name_length;
+ p.read_func = (read_func) ? read_func : NULL;
+ p.write_func = (write_func) ? write_func : NULL;
+ zend_hash_add(h, (char *)name, name_length + 1, &p, sizeof(php_mosquitto_prop_handler), NULL);
+}
+
+static void mosquitto_message_object_destroy(void *object TSRMLS_DC)
+{
+ mosquitto_message_object *message = (mosquitto_message_object *) object;
+ zend_hash_destroy(message->std.properties);
+ FREE_HASHTABLE(message->std.properties);
+
+ if (message->owned_topic == 1) {
+ efree(message->message.topic);
+ }
+
+ if (message->owned_payload == 1) {
+ efree(message->message.payload);
+ }
+
+ efree(object);
+}
+
+static zend_object_value mosquitto_message_object_new(zend_class_entry *ce TSRMLS_DC) {
+
+ zend_object_value retval;
+ mosquitto_message_object *message_obj;
+#if PHP_VERSION_ID < 50399
+ zval *temp;
+#endif
+
+ message_obj = ecalloc(1, sizeof(mosquitto_message_object));
+ message_obj->std.ce = ce;
+
+#ifdef ZTS
+ message_obj->TSRMLS_C = TSRMLS_C;
+#endif
+
+ ALLOC_HASHTABLE(message_obj->std.properties);
+ zend_hash_init(message_obj->std.properties, 0, NULL, ZVAL_PTR_DTOR, 0);
+#if PHP_VERSION_ID < 50399
+ zend_hash_copy(message_obj->std.properties, &mosquitto_ce_message->default_properties, (copy_ctor_func_t) zval_add_ref,(void *) &temp, sizeof(zval *));
+#else
+ object_properties_init(&message_obj->std, mosquitto_ce_message);
+#endif
+ retval.handle = zend_objects_store_put(message_obj, NULL, (zend_objects_free_object_storage_t) mosquitto_message_object_destroy, NULL TSRMLS_CC);
+ retval.handlers = &mosquitto_message_object_handlers;
+ return retval;
+}
+
+const zend_function_entry mosquitto_message_methods[] = {
+ PHP_ME(Mosquitto_Message, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
+ PHP_ME(Mosquitto_Message, topicMatchesSub, Mosquitto_Message_topicMatchesSub_args, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+ PHP_ME(Mosquitto_Message, tokeniseTopic, Mosquitto_Message_tokeniseTopic_args, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+ PHP_FE_END
+};
+
+PHP_MINIT_FUNCTION(mosquitto_message)
+{
+ zend_class_entry message_ce;
+ memcpy(&mosquitto_message_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+ mosquitto_message_object_handlers.read_property = php_mosquitto_message_read_property;
+ mosquitto_message_object_handlers.write_property = php_mosquitto_message_write_property;
+ mosquitto_message_object_handlers.has_property = php_mosquitto_message_has_property;
+ mosquitto_message_object_handlers.get_properties = php_mosquitto_message_get_properties;
+
+ INIT_NS_CLASS_ENTRY(message_ce, "Mosquitto", "Message", mosquitto_message_methods);
+ mosquitto_ce_message = zend_register_internal_class(&message_ce TSRMLS_CC);
+ mosquitto_ce_message->create_object = mosquitto_message_object_new;
+
+ zend_hash_init(&php_mosquitto_message_properties, 0, NULL, NULL, 1);
+ PHP_MOSQUITTO_ADD_PROPERTIES(&php_mosquitto_message_properties, php_mosquitto_message_property_entries);
+
+ return SUCCESS;
+}
diff --git a/package.xml b/package.xml
new file mode 100644
index 0000000..535cd93
--- /dev/null
+++ b/package.xml
@@ -0,0 +1,61 @@
+
+
+ Mosquitto
+ pecl.mgdm.net
+ Extension for libmosquitto
+ Mosquitto provides support for the MQTT protocol, including publishing, subscribing, and an event loop.
+
+ Michael Maclean
+ mgdm
+ mgdm@php.net
+ yes
+
+
+ 2014-04-28
+ 0.2.20.2.0
+ alphaalpha
+ BSD 3-Clause License
+
+ * Add missing unsubscribe method
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 5.3.0
+
+
+ 1.4.0
+
+
+
+
+
+ mosquitto
+
+
+
+
+
+
+
+
+
diff --git a/php_mosquitto.h b/php_mosquitto.h
new file mode 100644
index 0000000..86f1660
--- /dev/null
+++ b/php_mosquitto.h
@@ -0,0 +1,179 @@
+#ifndef PHP_MOSQUITTO_H
+#define PHP_MOSQUITTO_H
+
+#define PHP_MOSQUITTO_VERSION "0.2.2"
+
+extern zend_module_entry mosquitto_module_entry;
+#define phpext_mosquitto_ptr &mosquitto_module_entry
+
+#ifdef PHP_WIN32
+# define PHP_MOSQUITTO_API __declspec(dllexport)
+#elif defined(__GNUC__) && __GNUC__ >= 4
+# define PHP_MOSQUITTO_API __attribute__ ((visibility("default")))
+#else
+# define PHP_MOSQUITTO_API
+#endif
+
+#ifdef __GLIBC__
+#define POSSIBLY_UNUSED __attribute__((unused))
+#else
+#define POSSIBLY_UNUSED
+#endif
+
+#if defined(PHP_VERSION_ID) && (PHP_VERSION_ID >= 50399)
+# define ZEND_LITERAL_KEY_DC , const zend_literal *_zend_literal_key
+# define ZEND_LITERAL_KEY_CC , _zend_literal_key
+# define ZEND_LITERAL_NIL_CC , NULL
+#else
+# define ZEND_LITERAL_KEY_DC
+# define ZEND_LITERAL_KEY_CC
+# define ZEND_LITERAL_NIL_CC
+#endif
+
+#ifdef ZTS
+#include "TSRM.h"
+#endif
+
+#include
+
+typedef struct _mosquitto_client_object {
+ zend_object std;
+ struct mosquitto *client;
+
+ zend_fcall_info connect_callback;
+ zend_fcall_info_cache connect_callback_cache;
+ zend_fcall_info subscribe_callback;
+ zend_fcall_info_cache subscribe_callback_cache;
+ zend_fcall_info unsubscribe_callback;
+ zend_fcall_info_cache unsubscribe_callback_cache;
+ zend_fcall_info message_callback;
+ zend_fcall_info_cache message_callback_cache;
+ zend_fcall_info disconnect_callback;
+ zend_fcall_info_cache disconnect_callback_cache;
+ zend_fcall_info log_callback;
+ zend_fcall_info_cache log_callback_cache;
+
+ int looping;
+
+#ifdef ZTS
+ TSRMLS_D;
+#endif
+} mosquitto_client_object;
+
+typedef struct _mosquitto_message_object {
+ zend_object std;
+ struct mosquitto_message message;
+ zend_bool owned_topic;
+ zend_bool owned_payload;
+#ifdef ZTS
+ TSRMLS_D;
+#endif
+} mosquitto_message_object;
+
+typedef int (*php_mosquitto_read_t)(mosquitto_message_object *mosquitto_object, zval **retval TSRMLS_DC);
+typedef int (*php_mosquitto_write_t)(mosquitto_message_object *mosquitto_object, zval *newval TSRMLS_DC);
+
+typedef struct _php_mosquitto_prop_handler {
+ const char *name;
+ size_t name_length;
+ php_mosquitto_read_t read_func;
+ php_mosquitto_write_t write_func;
+} php_mosquitto_prop_handler;
+
+
+#define PHP_MOSQUITTO_ERROR_HANDLING() \
+ zend_replace_error_handling(EH_THROW, mosquitto_ce_exception, &MQTTG(mosquitto_original_error_handling) TSRMLS_CC)
+
+#define PHP_MOSQUITTO_RESTORE_ERRORS() \
+ zend_restore_error_handling(&MQTTG(mosquitto_original_error_handling) TSRMLS_CC)
+
+
+#define PHP_MOSQUITTO_FREE_CALLBACK(CALLBACK) \
+ if (ZEND_FCI_INITIALIZED(client->CALLBACK ## _callback)) { \
+ zval_ptr_dtor(&client->CALLBACK ## _callback.function_name); \
+ } \
+ \
+ if (client->CALLBACK ## _callback.object_ptr != NULL) { \
+ zval_ptr_dtor(&client->CALLBACK ## _callback.object_ptr); \
+ }
+
+
+#define PHP_MOSQUITTO_MESSAGE_PROPERTY_ENTRY_RECORD(name) \
+ { "" #name "", sizeof("" #name "") - 1, php_mosquitto_message_read_##name, php_mosquitto_message_write_##name }
+
+#define PHP_MOSQUITTO_ADD_PROPERTIES(a, b) \
+{ \
+ int i = 0; \
+ while (b[i].name != NULL) { \
+ php_mosquitto_message_add_property((a), (b)[i].name, (b)[i].name_length, \
+ (php_mosquitto_read_t)(b)[i].read_func, (php_mosquitto_write_t)(b)[i].write_func TSRMLS_CC); \
+ i++; \
+ } \
+}
+
+#define PHP_MOSQUITTO_MESSAGE_LONG_PROPERTY_READER_FUNCTION(name) \
+ static int php_mosquitto_message_read_##name(mosquitto_message_object *mosquitto_object, zval **retval TSRMLS_DC) \
+ { \
+ MAKE_STD_ZVAL(*retval); \
+ ZVAL_LONG(*retval, mosquitto_object->message.name); \
+ return SUCCESS; \
+ }
+
+#define PHP_MOSQUITTO_MESSAGE_LONG_PROPERTY_WRITER_FUNCTION(name) \
+static int php_mosquitto_message_write_##name(mosquitto_message_object *mosquitto_object, zval *newval TSRMLS_DC) \
+{ \
+ zval ztmp; \
+ if (Z_TYPE_P(newval) != IS_LONG) { \
+ ztmp = *newval; \
+ zval_copy_ctor(&ztmp); \
+ convert_to_long(&ztmp); \
+ newval = &ztmp; \
+ } \
+\
+ mosquitto_object->message.name = Z_LVAL_P(newval); \
+\
+ if (newval == &ztmp) { \
+ zval_dtor(newval); \
+ } \
+ return SUCCESS; \
+}
+
+ZEND_BEGIN_MODULE_GLOBALS(mosquitto)
+ char *client_key;
+ int client_key_len;
+ zend_object_handlers mosquitto_std_object_handlers;
+ zend_error_handling mosquitto_original_error_handling;
+ZEND_END_MODULE_GLOBALS(mosquitto)
+
+#ifdef ZTS
+# define MQTTG(v) TSRMG(mosquitto_globals_id, zend_mosquitto_globals *, v)
+#else
+# define MQTTG(v) (mosquitto_globals.v)
+#endif
+
+ZEND_EXTERN_MODULE_GLOBALS(mosquitto)
+
+extern zend_class_entry *mosquitto_ce_exception;
+extern zend_class_entry *mosquitto_ce_client;
+extern zend_class_entry *mosquitto_ce_message;
+
+PHP_MOSQUITTO_API void php_mosquitto_connect_callback(struct mosquitto *mosq, void *obj, int rc);
+PHP_MOSQUITTO_API void php_mosquitto_disconnect_callback(struct mosquitto *mosq, void *obj, int rc);
+PHP_MOSQUITTO_API void php_mosquitto_log_callback(struct mosquitto *mosq, void *obj, int level, const char *str);
+PHP_MOSQUITTO_API void php_mosquitto_message_callback(struct mosquitto *mosq, void *client_obj, const struct mosquitto_message *message);
+PHP_MOSQUITTO_API void php_mosquitto_subscribe_callback(struct mosquitto *mosq, void *client_obj, int mid, int qos_count, const int *granted_qos);
+PHP_MOSQUITTO_API void php_mosquitto_unsubscribe_callback(struct mosquitto *mosq, void *client_obj, int mid);
+PHP_MOSQUITTO_API void php_mosquitto_disconnect_callback(struct mosquitto *mosq, void *obj, int rc);
+
+PHP_MOSQUITTO_API char *php_mosquitto_strerror_wrapper(int err);
+void php_mosquitto_handle_errno(int retval, int err TSRMLS_DC);
+void php_mosquitto_exit_loop(mosquitto_client_object *object);
+
+PHP_MINIT_FUNCTION(mosquitto);
+PHP_MINIT_FUNCTION(mosquitto_message);
+PHP_MSHUTDOWN_FUNCTION(mosquitto);
+PHP_MINFO_FUNCTION(mosquitto);
+
+#endif /* PHP_MOSQUITTO_H */
+
+/* __footer_here__ */