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__ */