├── .gitignore ├── .gitmodules ├── .travis.yml ├── CMakeLists.txt ├── Jenkinsfile ├── LICENSE ├── README.md ├── cmake ├── FindMosquitto.cmake └── FindTarantool.cmake ├── debian ├── .gitignore ├── changelog ├── compat ├── control ├── copyright ├── docs ├── rules └── source │ └── format ├── examples ├── connect.lua └── producer_consumer_queue.lua ├── mqtt-scm-1.rockspec ├── mqtt ├── CMakeLists.txt ├── driver.c └── init.lua ├── rpm └── tarantool-mqtt.spec └── test ├── api.lua ├── pub_sub.lua ├── pub_sub_2.lua └── run_all.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | *.o 4 | *.lib 5 | *.a 6 | *.so 7 | *.so.* 8 | *.dylib 9 | 10 | build 11 | CMakeCache.txt 12 | CMakeFiles 13 | Makefile 14 | *.snap 15 | *.xlog 16 | cmake_install.cmake 17 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/mosquitto"] 2 | path = third_party/mosquitto 3 | url = https://github.com/eclipse/mosquitto 4 | tag = v1.6.2 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: C 3 | services: 4 | - docker 5 | 6 | cache: 7 | directories: 8 | - $HOME/.cache 9 | 10 | env: 11 | global: 12 | - PRODUCT=tarantool-mqtt 13 | matrix: 14 | - OS=el DIST=7 15 | - OS=el DIST=8 16 | - OS=fedora DIST=28 17 | - OS=fedora DIST=29 18 | - OS=fedora DIST=30 19 | - OS=fedora DIST=31 20 | - OS=ubuntu DIST=xenial 21 | - OS=ubuntu DIST=bionic 22 | - OS=ubuntu DIST=eoan 23 | - OS=ubuntu DIST=focal 24 | - OS=debian DIST=jessie 25 | - OS=debian DIST=stretch 26 | - OS=debian DIST=buster 27 | 28 | script: 29 | - git describe --long 30 | - git clone https://github.com/packpack/packpack.git packpack 31 | - packpack/packpack 32 | 33 | before_deploy: 34 | - ls -l build/ 35 | 36 | deploy: 37 | # Deploy packages to PackageCloud from master branch 38 | - provider: packagecloud 39 | username: tarantool 40 | repository: "1_9" 41 | token: ${PACKAGECLOUD_TOKEN} 42 | dist: ${OS}/${DIST} 43 | package_glob: build/*.{rpm,deb,dsc} 44 | skip_cleanup: true 45 | on: 46 | branch: master 47 | condition: -n "${OS}" && -n "${DIST}" && -n "${PACKAGECLOUD_TOKEN}" 48 | - provider: packagecloud 49 | username: tarantool 50 | repository: "1_10" 51 | token: ${PACKAGECLOUD_TOKEN} 52 | dist: ${OS}/${DIST} 53 | package_glob: build/*.{rpm,deb,dsc} 54 | skip_cleanup: true 55 | on: 56 | branch: master 57 | condition: -n "${OS}" && -n "${DIST}" && -n "${PACKAGECLOUD_TOKEN}" 58 | - provider: packagecloud 59 | username: tarantool 60 | repository: "2x" 61 | token: ${PACKAGECLOUD_TOKEN} 62 | dist: ${OS}/${DIST} 63 | package_glob: build/*.{rpm,deb,dsc} 64 | skip_cleanup: true 65 | on: 66 | branch: master 67 | condition: -n "${OS}" && -n "${DIST}" && -n "${PACKAGECLOUD_TOKEN}" 68 | - provider: packagecloud 69 | username: tarantool 70 | repository: "2_2" 71 | token: ${PACKAGECLOUD_TOKEN} 72 | dist: ${OS}/${DIST} 73 | package_glob: build/*.{rpm,deb,dsc} 74 | skip_cleanup: true 75 | on: 76 | branch: master 77 | condition: -n "${OS}" && -n "${DIST}" && -n "${PACKAGECLOUD_TOKEN}" 78 | - provider: packagecloud 79 | username: tarantool 80 | repository: "2_3" 81 | token: ${PACKAGECLOUD_TOKEN} 82 | dist: ${OS}/${DIST} 83 | package_glob: build/*.{rpm,deb,dsc} 84 | skip_cleanup: true 85 | on: 86 | branch: master 87 | condition: -n "${OS}" && -n "${DIST}" && -n "${PACKAGECLOUD_TOKEN}" 88 | - provider: packagecloud 89 | username: tarantool 90 | repository: "2_4" 91 | token: ${PACKAGECLOUD_TOKEN} 92 | dist: ${OS}/${DIST} 93 | package_glob: build/*.{rpm,deb,dsc} 94 | skip_cleanup: true 95 | on: 96 | branch: master 97 | condition: -n "${OS}" && -n "${DIST}" && -n "${PACKAGECLOUD_TOKEN}" 98 | # Deploy packages to PackageCloud from tags 99 | # see: 100 | # * https://github.com/tarantool/tarantool/issues/3745 101 | # * https://github.com/travis-ci/travis-ci/issues/7780#issuecomment-302389370 102 | - provider: packagecloud 103 | username: tarantool 104 | repository: "1_9" 105 | token: ${PACKAGECLOUD_TOKEN} 106 | dist: ${OS}/${DIST} 107 | package_glob: build/*.{rpm,deb,dsc} 108 | skip_cleanup: true 109 | on: 110 | tags: true 111 | condition: -n "${OS}" && -n "${DIST}" && -n "${PACKAGECLOUD_TOKEN}" 112 | - provider: packagecloud 113 | username: tarantool 114 | repository: "1_10" 115 | token: ${PACKAGECLOUD_TOKEN} 116 | dist: ${OS}/${DIST} 117 | package_glob: build/*.{rpm,deb,dsc} 118 | skip_cleanup: true 119 | on: 120 | tags: true 121 | condition: -n "${OS}" && -n "${DIST}" && -n "${PACKAGECLOUD_TOKEN}" 122 | - provider: packagecloud 123 | username: tarantool 124 | repository: "2x" 125 | token: ${PACKAGECLOUD_TOKEN} 126 | dist: ${OS}/${DIST} 127 | package_glob: build/*.{rpm,deb,dsc} 128 | skip_cleanup: true 129 | on: 130 | tags: true 131 | condition: -n "${OS}" && -n "${DIST}" && -n "${PACKAGECLOUD_TOKEN}" 132 | - provider: packagecloud 133 | username: tarantool 134 | repository: "2_2" 135 | token: ${PACKAGECLOUD_TOKEN} 136 | dist: ${OS}/${DIST} 137 | package_glob: build/*.{rpm,deb,dsc} 138 | skip_cleanup: true 139 | on: 140 | tags: true 141 | condition: -n "${OS}" && -n "${DIST}" && -n "${PACKAGECLOUD_TOKEN}" 142 | - provider: packagecloud 143 | username: tarantool 144 | repository: "2_3" 145 | token: ${PACKAGECLOUD_TOKEN} 146 | dist: ${OS}/${DIST} 147 | package_glob: build/*.{rpm,deb,dsc} 148 | skip_cleanup: true 149 | on: 150 | tags: true 151 | condition: -n "${OS}" && -n "${DIST}" && -n "${PACKAGECLOUD_TOKEN}" 152 | - provider: packagecloud 153 | username: tarantool 154 | repository: "2_4" 155 | token: ${PACKAGECLOUD_TOKEN} 156 | dist: ${OS}/${DIST} 157 | package_glob: build/*.{rpm,deb,dsc} 158 | skip_cleanup: true 159 | on: 160 | tags: true 161 | condition: -n "${OS}" && -n "${DIST}" && -n "${PACKAGECLOUD_TOKEN}" 162 | 163 | notifications: 164 | email: 165 | recipients: 166 | - build@tarantool.org 167 | on_success: change 168 | on_failure: always 169 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | 3 | project(mqtt C) 4 | 5 | set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) 6 | 7 | # Find deps 8 | find_package(Tarantool REQUIRED) 9 | include_directories(${TARANTOOL_INCLUDE_DIRS}) 10 | 11 | if (NOT DEFINED STATIC_BUILD) 12 | find_package(Mosquitto REQUIRED) 13 | include_directories(${MOSQUITTO_INCLUDE_DIRS}) 14 | message(STATUS "libmosquitto: ${MOSQUITTO_INCLUDE_DIRS} ${MOSQUITTO_LIBRARIES}") 15 | endif() 16 | 17 | message(STATUS "tarantool: ${TARANTOOL_INCLUDE_DIRS}") 18 | 19 | # Set CFLAGS 20 | set(MY_C_FLAGS "-Wall -Wextra -Werror -std=gnu11 -fno-strict-aliasing") 21 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${MY_C_FLAGS}") 22 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${MY_C_FLAGS}") 23 | 24 | include_directories(${TARANTOOL_INCLUDE_DIRS}) 25 | 26 | # Build module 27 | add_subdirectory(mqtt) 28 | 29 | add_custom_target(test 30 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 31 | COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test/run_all.sh 32 | DEPENDS driver) 33 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | stage('Build'){ 2 | packpack = new org.tarantool.packpack() 3 | 4 | // No mosquitto library on some old distros 5 | matrix = packpack.filterMatrix( 6 | packpack.default_matrix, 7 | {!(it['OS'] == 'el' && it['DIST'] == '6') && 8 | !(it['OS'] == 'ubuntu' && it['DIST'] == 'precise') && 9 | !(it['OS'] == 'ubuntu' && it['DIST'] == 'trusty') && 10 | !(it['OS'] == 'fedora' && it['DIST'] == 'rawhide')}) 11 | 12 | node { 13 | checkout scm 14 | packpack.prepareSources() 15 | } 16 | packpack.packpackBuildMatrix('result', matrix) 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2016 Tarantool AUTHORS: please see AUTHORS file. 2 | 3 | Redistribution and use in source and binary forms, with or 4 | without modification, are permitted provided that the following 5 | conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above 8 | copyright notice, this list of conditions and the 9 | following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials 14 | provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 20 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 21 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 24 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 27 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | # Tarantool MQTT client 6 | --------------------------------- 7 | 8 | Key features: 9 | 10 | * non-blocking communication with MQTT brokers; 11 | * TLS support; 12 | * pretty low overheads, code based on `libmosquitto`. 13 | 14 | ## Content 15 | ---------- 16 | * [Prerequisites](#prerequisites) 17 | * [Building from source](#building-from-source) 18 | * [API](#api) 19 | * [lib_destroy](#lib_destroy) 20 | * [new](#new) 21 | * [connect](#connect) 22 | * [reconnect](#reconnect) 23 | * [subscribe](#subscribe) 24 | * [unsubscribe](#unsubscribe) 25 | * [destroy](#destroy) 26 | * [publish](#publish) 27 | * [will_set](#will_set) 28 | * [will_clear](#will_clear) 29 | * [login_set](#login_set) 30 | * [tls_insecure_set](#tls_insecure_set) 31 | * [tls_set](#tls_set) 32 | * [on_message](#on_message) 33 | * [Subscribe to events](#subscribe-to-events) 34 | * [Performance tuning](#performance-tuning) 35 | * [Examples](#examples) 36 | * [Copyright & License](#copyright--license) 37 | * [See also](#see-also) 38 | 39 | ## Prerequisites 40 | ------------------------------- 41 | 42 | Before reading any further, make sure you have an MQTT broker installed or use static build by passing **STATIC_BUILD** flags to cmake. 43 | 44 | ### Building from source 45 | 46 | Clone the repository with submodules and build the client: 47 | 48 | ```bash 49 | $ git clone https://github.com/tarantool/mqtt.git 50 | $ cd mqtt 51 | $ git submodule update --init --recursive 52 | $ mkdir build && cd build 53 | $ cmake .. 54 | $ make -j 55 | ``` 56 | 57 | [Back to content](#content) 58 | 59 | ## API 60 | ------ 61 | 62 | Lua API documentation. 63 | 64 | ### new 65 | ------- 66 | 67 | Create a new `mosquitto` client instance. 68 | 69 | Parameters: 70 | 71 | client_id - String. If NULL, a random client id will be generated 72 | and the clean_session parameter must be set to true. 73 | clean_session - Boolean. Set to true to instruct the broker to clean all 74 | messages and subscriptions on disconnect; false to instruct 75 | it to keep them. See the man page mqtt(7) for more details. 76 | Must be set to true if the id parameter is NULL. 77 | 78 | Note that a client will never discard its own outgoing messages on disconnect. 79 | Calling [connect](#connect) or [reconnect](#reconnect) will resend the messages. 80 | 81 | Returns: 82 | 83 | mqtt object (see mqtt_mt) or raises error 84 | 85 | Example: 86 | 87 | ```lua 88 | mqtt = require('mqtt') 89 | instance = mqtt.new("client_id", true) 90 | ``` 91 | 92 | [Back to content](#content) 93 | 94 | ### connect 95 | ----------- 96 | 97 | Connect to an MQTT broker. 98 | 99 | Parameters: 100 | 101 | opts.host - Hostname or IP address of the broker to connect to. 102 | opts.port - Network port to connect to. Usually 1883. 103 | opts.keepalive - The number of seconds the broker waits since the last 104 | message before sending a PING message to the client. 105 | opts.log_mask - LOG_NONE, LOG_INFO, LOG_NOTICE, LOG_WARNING, 106 | LOG_ERROR[default], LOG_DEBUG, LOG_ALL. 107 | opts.auto_reconect - [true|false] - auto reconnect on (default) or off. 108 | 109 | Returns: 110 | 111 | true or false, emsg 112 | 113 | Example: 114 | 115 | ```lua 116 | mqtt = require('mqtt') 117 | instance = mqtt.new("client_id", true) 118 | instance:connect({ 119 | host='127.0.0.1', 120 | port=1883, 121 | keepalive=60, 122 | log_mask=mqtt.LOG_NONE 123 | }) 124 | ``` 125 | 126 | [Back to content](#content) 127 | 128 | ### reconnect 129 | ------------- 130 | 131 | Reconnect to broker. 132 | 133 | This function provides an easy way of reconnecting to the broker after 134 | connection loss. It uses the values provided in the [connect](#connect) 135 | call and must not be called prior. 136 | 137 | Note: After the reconnection you must call [subscribe](#subscribe) to 138 | subscribe to topics. 139 | 140 | See the [connect](#connect) `opts.auto_reconect` parameter. 141 | 142 | Example: 143 | 144 | ```lua 145 | mqtt = require('mqtt') 146 | instance = mqtt.new("client_id", true) 147 | instance:connect({host='127.0.0.1', port=1883, auto_reconect=false}) 148 | ok, emsg_or_mid = instance:subscribe('topic') 149 | if not ok and not mqtt.connect then 150 | print('subscribe - failed, reconnecting ...') 151 | ok, _ = instance:reconnect() 152 | end 153 | ``` 154 | 155 | [Back to content](#content) 156 | 157 | ### subscribe 158 | ------------- 159 | 160 | Subscribe to a topic. 161 | 162 | Parameters: 163 | 164 | sub - Subscription pattern. 165 | qos - Requested Quality of Service for this subscription. 166 | 167 | Returns: 168 | 169 | true or false, integer mid or error message 170 | 171 | Example: 172 | 173 | ```lua 174 | mqtt = require('mqtt') 175 | instance = mqtt.new("client_id", true) 176 | -- Cut, see the connect function 177 | ok, err_or_mid = instance:subscribe('my/topic/#', 1) 178 | if ok then 179 | print(ok, err_or_mid) 180 | end 181 | ``` 182 | 183 | [Back to content](#content) 184 | 185 | ### unsubscribe 186 | --------------- 187 | 188 | Unsubscribe from a topic. 189 | 190 | Parameters: 191 | 192 | topic - Unsubscription pattern. 193 | 194 | Returns: 195 | 196 | true or false, integer mid or error message 197 | 198 | Example: 199 | 200 | ```lua 201 | mqtt = require('mqtt') 202 | instance = mqtt.new("client_id", true) 203 | -- Cut, see the connect function 204 | ok, err = instance:unsubscribe('my/topic/#') 205 | if ok then 206 | print(ok, err) 207 | end 208 | ``` 209 | 210 | [Back to content](#content) 211 | 212 | ### destroy 213 | ----------- 214 | 215 | Destroy an `mqtt` object. 216 | 217 | Note: Call this function manually as the module does not use the Lua's GC. 218 | 219 | Parameters: 220 | 221 | None 222 | 223 | Returns: 224 | 225 | true or false, error message 226 | 227 | Example: 228 | 229 | ```lua 230 | mqtt = require('mqtt') 231 | instance = mqtt.new("client_id", true) 232 | -- Cut, see the connect function 233 | ok, err = instance:destroy() 234 | if ok then 235 | print(ok, err) 236 | end 237 | ``` 238 | 239 | [Back to content](#content) 240 | 241 | ### lib_destroy 242 | --------------- 243 | 244 | Cleanup everything. 245 | 246 | Note: The module does not use the Lua's GC, the latter has to be called 247 | manually. To call it manually, first call `destroy` on each `mqtt` object. 248 | 249 | Parameters: 250 | 251 | None 252 | 253 | Returns: 254 | 255 | None 256 | 257 | Example: 258 | 259 | ```lua 260 | mqtt = require('mqtt') 261 | instance = mqtt.new("client_id", true) 262 | mqtt.lib_destroy() 263 | ``` 264 | 265 | [Back to content](#content) 266 | 267 | ### publish 268 | ----------- 269 | 270 | Publish a message on a given topic. 271 | 272 | Parameters: 273 | 274 | topic - Null-terminated string of the topic to publish to. 275 | payload - Pointer to the data to send. 276 | qos - Integer value 0, 1 or 2 indicating the Quality of Service to be 277 | used for the message. When you call the library with "mqtt = require('mqtt')", 278 | you can use mqtt.QOS_0, mqtt.QOS_1 and mqtt.QOS_2 as a replacement 279 | for some strange digital variable. 280 | retain - Set to true to make the message retained. You can also use the values 281 | mqtt.RETAIN and mqtt.NON_RETAIN to replace the unmarked variable. 282 | 283 | Returns: 284 | 285 | true or false, emsg, message id (i.e. MID) is referenced in the publish callback 286 | 287 | Example: 288 | 289 | ```lua 290 | mqtt = require('mqtt') 291 | instance = mqtt.new("client_id", true) 292 | -- Cut, see the connect function 293 | ok, err = instance:publish('my/topic/#', 'Some payload as string', mqtt.QOS_0, mqtt.RETAIN) 294 | if ok then 295 | print(ok, err) 296 | end 297 | ``` 298 | 299 | [Back to content](#content) 300 | 301 | ### will_set 302 | ------------ 303 | 304 | Configure the `will` information for a `mosquitto` instance. By default, clients do 305 | not have a `will`. Must be called before calling [connect](#connect). 306 | 307 | Parameters: 308 | 309 | topic - Topic for which to publish the will. 310 | payload - Pointer to the data to send. 311 | qos - Integer value 0, 1 or 2 indicating the Quality of Service to be 312 | used for the will. 313 | retain - Set to true to make the will a retained message. 314 | 315 | Returns: 316 | 317 | true or false, emsg 318 | 319 | Example: 320 | 321 | ```lua 322 | mqtt = require('mqtt') 323 | instance = mqtt.new("client_id", true) 324 | -- Cut, see the connect function 325 | ok, err = instance:will_set('my/topic/#', 'Some payload as string', 0, true) 326 | if ok then 327 | print(ok, err) 328 | end 329 | ``` 330 | 331 | [Back to content](#content) 332 | 333 | ### will_clear 334 | -------------- 335 | 336 | Remove a previously configured `will`. Must be called before calling 337 | [connect](#connect). 338 | 339 | Returns: 340 | 341 | true or false, emsg 342 | 343 | Example: 344 | 345 | ```lua 346 | mqtt = require('mqtt') 347 | instance = mqtt.new("client_id", true) 348 | -- Cut, see the connect function 349 | ok, err = instance:will_clear() 350 | if ok then 351 | print(ok, err) 352 | end 353 | ``` 354 | 355 | [Back to content](#content) 356 | 357 | ### login_set 358 | ------------- 359 | 360 | Configure a username and password for the `mosquitto` instance. Supported only by 361 | the brokers that implement the MQTT spec v3.1. By default, no username 362 | or password will be sent. If the username is NULL, the password argument is ignored. 363 | 364 | Must be called before calling [connect](#connect). 365 | 366 | Parameters: 367 | 368 | username - Username to send as a string or NULL to disable 369 | authentication. 370 | password - Password to send as a string. Set to NULL to send 371 | just a valid username. 372 | 373 | Returns: 374 | 375 | true or false, emsg 376 | 377 | Example: 378 | 379 | ```lua 380 | mqtt = require('mqtt') 381 | instance = mqtt.new("client_id", true) 382 | -- Cut, see the connect function 383 | ok, err = instance:login_set('user', 'password') 384 | if ok then 385 | print(ok, err) 386 | end 387 | ``` 388 | 389 | [Back to content](#content) 390 | 391 | ### tls_insecure_set 392 | -------------------- 393 | 394 | If set to `true`, do not check if the hostname in the server's certificate 395 | matches the hostname of the server to connect to. 396 | 397 | If the check is disabled, connection encryption is pointless and 398 | it is impossible to guarantee that the host you are connecting to is not 399 | impersonating the server. This can be useful during the initial server 400 | testing but makes it possible for a malicious third party to impersonate 401 | the server through, e.g., DNS spoofing. 402 | 403 | Do not use this function in a production environment. 404 | 405 | Must be called before [connect](#connect). 406 | 407 | Parameters: 408 | 409 | value - If set to false (default), certificate hostname is checked. 410 | If set to true, no checking is performed and connection is insecure. 411 | 412 | Returns: 413 | 414 | true or false, emsg 415 | 416 | Example: 417 | 418 | ```lua 419 | mqtt = require('mqtt') 420 | instance = mqtt.new("client_id", true) 421 | -- Cut, see the connect function 422 | ok, err = instance:tls_insecure_set(true) 423 | if ok then 424 | print(ok, err) 425 | end 426 | ``` 427 | 428 | [Back to content](#content) 429 | 430 | ### tls_set 431 | ----------- 432 | 433 | Configure a client for certificate-based SSL/TLS support. Must be called 434 | before [connect](#connect). 435 | 436 | Define certificates signed by a Certificate Authority (CA) as trusted 437 | (i.e. the server certificate must be signed by it) using `cafile`. 438 | 439 | If the server to connect to requires clients to provide a certificate, 440 | set the `certfile` and `keyfile` paths to your client certificate 441 | and private key files. If the private key is encrypted, provide a password 442 | callback function or enter the password via the command line. 443 | 444 | Parameters: 445 | 446 | cafile - Path to a file containing PEM-encoded trusted CA 447 | certificate. Either the cafile or capath must not be NULL. 448 | capath - Path to a directory containing the PEM-encoded trusted CA 449 | certificate files. See mosquitto.conf for more details on 450 | configuring this directory. Either the cafile or capath must 451 | not be NULL. 452 | certfile - Path to a file containing a PEM-encoded certificate 453 | for this client. If NULL, the keyfile must also be NULL and no 454 | client certificate will be used. 455 | keyfile - Path to a file containing a PEM-encoded private key for 456 | this client. If NULL, the certfile must also be NULL and no 457 | client certificate will be used. 458 | pw_callback - TODO: implement me. 459 | 460 | Returns: 461 | 462 | true or false, emsg 463 | 464 | See also: [tls_insecure_set](#tls_insecure_set). 465 | 466 | Example: 467 | 468 | ```lua 469 | mqtt = require('mqtt') 470 | instance = mqtt.new("client_id", true) 471 | -- Cut, see the connect function 472 | ok, err = instance:tls_set('my.pem', '/home/user/pems', 'my.ca', 'my.key') 473 | if ok then 474 | print(ok, err) 475 | end 476 | ``` 477 | 478 | [Back to content](#content) 479 | 480 | ### on_message 481 | 482 | Set a message callback. Called when a message from the broker 483 | is received. 484 | 485 | Parameters: 486 | 487 | F - a callback function with the following form: 488 | function F(integer_mid, string_topic, string_payload, integer_gos, integer_retain) 489 | 490 | Returns: 491 | 492 | true or false, emsg 493 | 494 | Example: 495 | 496 | ```lua 497 | mqtt = require('mqtt') 498 | instance = mqtt.new("client_id", true) 499 | -- Cut, see the connect function 500 | ok, err = instance:on_message( 501 | function(message_id, topic, payload, gos, retain) 502 | print('Recv. new message', 503 | message_id, topic, payload, gos, retain) 504 | end) 505 | if ok then 506 | print(ok, err) 507 | end 508 | ``` 509 | 510 | [Back to content](#content) 511 | 512 | ### Subscribe to events 513 | ----------------------- 514 | 515 | Warning: Use the following functions with caution as 516 | incorrect calls can break asynchronous I/O loops! 517 | 518 | Non-mandatory functions: 519 | 520 | * on_connect 521 | 522 | * on_disconnect 523 | 524 | * on_publish 525 | 526 | * on_subscribe 527 | 528 | * on_unsubscribe 529 | 530 | See the detailed documentation of these functions in the [mqtt.init.lua](mqtt/init.lua) file. 531 | 532 | [Back to content](#content) 533 | 534 | ## Performance tuning 535 | --------------------- 536 | 537 | TODO: describe me. 538 | 539 | [Back to content](#content) 540 | 541 | ## Examples 542 | ----------- 543 | 544 | The [examples/connect.lua](examples/connect.lua) file shows how to connect 545 | to an MQTT broker. 546 | 547 | The [examples/producer_consumer_queue.lua](examples/producer_consumer_queue.lua) file shows how 548 | Tarantool produces, passes, and consumes data to and from an MQTT broker 549 | via the MQTT connector in a non-blocking way. 550 | 551 | [Back to content](#content) 552 | 553 | ## Copyright & License 554 | ---------------------- 555 | [LICENSE](https://github.com/tarantool/mqtt/blob/master/LICENSE) 556 | 557 | [Back to content](#content) 558 | 559 | ## See also 560 | ---------- 561 | * [Tarantool](https://www.tarantool.io) homepage. 562 | * MQTT brokers: 563 | * [Mosquitto](https://mosquitto.org) homepage. 564 | * [RabbitMQ](https://www.rabbitmq.com) homepage. 565 | 566 | [Back to content](#content) 567 | 568 | --- 569 | 570 | Please report bugs at https://github.com/tarantool/mqtt/issues. 571 | 572 | We also warmly welcome your feedback in the discussion mailing list: tarantool@googlegroups.com. 573 | -------------------------------------------------------------------------------- /cmake/FindMosquitto.cmake: -------------------------------------------------------------------------------- 1 | # - Find libmosquitto 2 | # Find the native libmosquitto includes and libraries 3 | # 4 | # MOSQUITTO_INCLUDE_DIR - where to find mosquitto.h, etc. 5 | # MOSQUITTO_LIBRARIES - List of libraries when using libmosquitto. 6 | # MOSQUITTO_FOUND - True if libmosquitto found. 7 | 8 | if (NOT MOSQUITTO_INCLUDE_DIR) 9 | find_path(MOSQUITTO_INCLUDE_DIR mosquitto.h) 10 | endif() 11 | 12 | if (NOT MOSQUITTO_LIBRARY) 13 | find_library( 14 | MOSQUITTO_LIBRARY 15 | NAMES mosquitto) 16 | endif() 17 | 18 | include(FindPackageHandleStandardArgs) 19 | 20 | find_package_handle_standard_args( 21 | MOSQUITTO DEFAULT_MSG 22 | MOSQUITTO_LIBRARY MOSQUITTO_INCLUDE_DIR) 23 | 24 | message(STATUS "libmosquitto include dir: ${MOSQUITTO_INCLUDE_DIR}") 25 | message(STATUS "libmosquitto: ${MOSQUITTO_LIBRARY}") 26 | set(MOSQUITTO_LIBRARIES ${MOSQUITTO_LIBRARY}) 27 | 28 | mark_as_advanced(MOSQUITTO_INCLUDE_DIR MOSQUITTO_LIBRARY) 29 | -------------------------------------------------------------------------------- /cmake/FindTarantool.cmake: -------------------------------------------------------------------------------- 1 | # Define GNU standard installation directories 2 | include(GNUInstallDirs) 3 | 4 | macro(extract_definition name output input) 5 | string(REGEX MATCH "#define[\t ]+${name}[\t ]+\"([^\"]*)\"" 6 | _t "${input}") 7 | string(REGEX REPLACE "#define[\t ]+${name}[\t ]+\"(.*)\"" "\\1" 8 | ${output} "${_t}") 9 | endmacro() 10 | 11 | find_path(TARANTOOL_INCLUDE_DIR tarantool/module.h 12 | HINTS ${TARANTOOL_DIR} ENV TARANTOOL_DIR 13 | PATH_SUFFIXES include 14 | ) 15 | 16 | if(TARANTOOL_INCLUDE_DIR) 17 | set(_config "-") 18 | file(READ "${TARANTOOL_INCLUDE_DIR}/tarantool/module.h" _config0) 19 | string(REPLACE "\\" "\\\\" _config ${_config0}) 20 | unset(_config0) 21 | extract_definition(PACKAGE_VERSION TARANTOOL_VERSION ${_config}) 22 | extract_definition(INSTALL_PREFIX _install_prefix ${_config}) 23 | unset(_config) 24 | endif() 25 | 26 | include(FindPackageHandleStandardArgs) 27 | find_package_handle_standard_args(TARANTOOL 28 | REQUIRED_VARS TARANTOOL_INCLUDE_DIR VERSION_VAR TARANTOOL_VERSION) 29 | if(TARANTOOL_FOUND) 30 | set(TARANTOOL_INCLUDE_DIRS "${TARANTOOL_INCLUDE_DIR}" 31 | "${TARANTOOL_INCLUDE_DIR}/tarantool/" 32 | CACHE PATH "Include directories for Tarantool") 33 | set(TARANTOOL_INSTALL_LIBDIR "${CMAKE_INSTALL_LIBDIR}/tarantool" 34 | CACHE PATH "Directory for storing Lua modules written in Lua") 35 | set(TARANTOOL_INSTALL_LUADIR "${CMAKE_INSTALL_DATADIR}/tarantool" 36 | CACHE PATH "Directory for storing Lua modules written in C") 37 | 38 | if (NOT TARANTOOL_FIND_QUIETLY AND NOT FIND_TARANTOOL_DETAILS) 39 | set(FIND_TARANTOOL_DETAILS ON CACHE INTERNAL "Details about TARANTOOL") 40 | message(STATUS "Tarantool LUADIR is ${TARANTOOL_INSTALL_LUADIR}") 41 | message(STATUS "Tarantool LIBDIR is ${TARANTOOL_INSTALL_LIBDIR}") 42 | endif () 43 | endif() 44 | mark_as_advanced(TARANTOOL_INCLUDE_DIRS TARANTOOL_INSTALL_LIBDIR 45 | TARANTOOL_INSTALL_LUADIR) 46 | -------------------------------------------------------------------------------- /debian/.gitignore: -------------------------------------------------------------------------------- 1 | files 2 | stamp-* 3 | *.substvars 4 | *.log 5 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | tarantool-mqtt (1.4.0-1) stable; urgency=medium 2 | * Fixed: https://github.com/tarantool/mqtt/issues/14 3 | * Fixed: https://github.com/tarantool/mqtt/issues/12 4 | * Fixed: https://github.com/tarantool/mqtt/issues/11 5 | * Fixed: https://github.com/tarantool/mqtt/issues/9 6 | 7 | -- Vasiliy Soshnikov Sun, 8 Jul 2018 16:23:00 +0300 8 | 9 | tarantool-mqtt (1.2.0-1) stable; urgency=medium 10 | * Build issue have been fixed. 11 | 12 | -- Vasiliy Soshnikov Sun, 2 Apr 2017 21:40:00 +0300 13 | 14 | tarantool-mqtt (1.1.0-1) stable; urgency=medium 15 | 16 | * RabbitMQ connection issue have been fixed. 17 | * Hight CPU usage issue have been fixed. 18 | * API errors hanling have been fixed. 19 | 20 | -- Vasiliy Soshnikov Sun, 2 Apr 2017 19:24:00 +0300 21 | 22 | tarantool-mqtt (1.0.0-1) unstable; urgency=medium 23 | 24 | * Initial release. 25 | 26 | -- Konstantin Nazarov Mon, 26 Sep 2016 19:04:00 +0300 27 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: tarantool-mqtt 2 | Priority: optional 3 | Section: database 4 | Maintainer: Vasiliy Soshnikov 5 | Build-Depends: debhelper (>= 9), cdbs, cmake (>= 2.8), 6 | tarantool-dev (>= 1.6.8.0), libmosquitto-dev 7 | Standards-Version: 3.9.6 8 | Homepage: https://github.com/tarantool/mqtt 9 | Vcs-Git: git://github.com/tarantool/mqtt.git 10 | Vcs-Browser: https://github.com/tarantool/mqtt 11 | 12 | Package: tarantool-mqtt 13 | Architecture: i386 amd64 armhf arm64 14 | Depends: tarantool (>= 1.6.8.0), libmosquitto1, ${shlibs:Depends}, ${misc:Depends} 15 | Pre-Depends: ${misc:Pre-Depends} 16 | Description: tarantool-mqtt is a MQTT client for Tarantool 17 | This package provides a MQTT bindings for Tarantool. 18 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Debianized-By: Konstantin Nazarov 3 | Upstream-Name: tarantool-mqtt 4 | Upstream-Contact: racktear@tarantool.org 5 | Source: https://github.com/tarantool/mqtt 6 | 7 | Files: * 8 | Copyright: 2010-2016 by Tarantool AUTHORS, please see AUTHORS file. 9 | License: BSD-2-Clause 10 | Redistribution and use in source and binary forms, with or 11 | without modification, are permitted provided that the following 12 | conditions are met: 13 | . 14 | 1. Redistributions of source code must retain the above 15 | copyright notice, this list of conditions and the 16 | following disclaimer. 17 | . 18 | 2. Redistributions in binary form must reproduce the above 19 | copyright notice, this list of conditions and the following 20 | disclaimer in the documentation and/or other materials 21 | provided with the distribution. 22 | . 23 | THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND 24 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 25 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 27 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 28 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 31 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 32 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 33 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 34 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 | SUCH DAMAGE. 36 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | DEB_CMAKE_EXTRA_FLAGS := -DCMAKE_INSTALL_LIBDIR=lib/$(DEB_HOST_MULTIARCH) \ 4 | -DCMAKE_BUILD_TYPE=RelWithDebInfo 5 | 6 | include /usr/share/cdbs/1/rules/debhelper.mk 7 | include /usr/share/cdbs/1/class/cmake.mk 8 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /examples/connect.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tarantool 2 | 3 | -- Load Tarantool mqtt 4 | local mqtt = require('mqtt') 5 | 6 | -- Create instance 7 | connection = mqtt.new() 8 | 9 | -- Connect to the server 10 | connection:connect({host='127.0.0.1', port=1883}) 11 | 12 | -- Set callback for recv new messages 13 | connection:on_message(function (message_id, topic, payload, gos, retain) 14 | print('New message', message_id, topic, payload, gos, retain) 15 | end) 16 | 17 | -- Subscribe to a system topic 18 | connection:subscribe('$SYS/#') 19 | -------------------------------------------------------------------------------- /examples/producer_consumer_queue.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tarantool 2 | 3 | local mqtt = require('mqtt') 4 | local fiber = require('fiber') 5 | local yaml = require('yaml') 6 | 7 | box.cfg {wal_mode = 'none'} 8 | 9 | -- Queue out 10 | queue = box.schema.space.create('queue') 11 | queue:create_index('id') 12 | 13 | -- Create instance 14 | connection = mqtt.new() 15 | 16 | -- Connect to the server 17 | local ok, emsg = connection:connect({host='127.0.0.1', port=1883}) 18 | if not ok then 19 | error('connect ->', emsg) 20 | end 21 | 22 | -- Subscribe to a system topic 23 | local ok, emsg = connection:subscribe('tarantool/box/info') 24 | if not ok then 25 | error('subscribe -> ',emsg) 26 | end 27 | 28 | -- 29 | -- Work 30 | -- 31 | 32 | -- Consumer 33 | connection:on_message(function (message_id, topic, payload, gos, retain) 34 | queue:auto_increment{yaml.decode(payload)} 35 | if queue:len() > 10 then 36 | print('Queue [[') 37 | for _, v in pairs(queue:select{}) do 38 | print(_, yaml.encode(v[2])) 39 | queue:delete(v[1]) 40 | end 41 | print(']]') 42 | end 43 | end) 44 | 45 | -- Producer 46 | fiber.create(function() 47 | while true do 48 | connection:publish('tarantool/box/info', yaml.encode{box.info()}) 49 | fiber.sleep(0.5) 50 | end 51 | end) 52 | -------------------------------------------------------------------------------- /mqtt-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "mqtt" 2 | version = "scm-1" 3 | source = { 4 | url = "git+https://github.com/tarantool/mqtt.git", 5 | tag = "master" 6 | } 7 | description = { 8 | summary = "Mqtt connector for Tarantool", 9 | homepage = "https://github.com/tarantool/mqtt", 10 | license = "BSD" 11 | } 12 | dependencies = { 13 | "lua >= 5.1" 14 | } 15 | build = { 16 | type = "cmake"; 17 | variables = { 18 | CMAKE_BUILD_TYPE="RelWithDebInfo"; 19 | TARANTOOL_INSTALL_LIBDIR="$(LIBDIR)"; 20 | TARANTOOL_INSTALL_LUADIR="$(LUADIR)"; 21 | STATIC_BUILD="ON"; 22 | }; 23 | platforms = { 24 | macosx = { 25 | variables = { 26 | OPENSSL_ROOT_DIR="/usr/local/opt/openssl"; 27 | }; 28 | }; 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /mqtt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # driver 2 | 3 | # we don't need to build documentation for mosquitto 4 | option(DOCUMENTATION "Build documentation?" OFF) 5 | 6 | option(WITH_STATIC_LIBRARIES "Build static versions of the libmosquitto/pp libraries?" OFF) 7 | option(WITH_PIC "Build the static library with PIC (Position Independent Code) enabled archives?" OFF) 8 | 9 | if( STATIC_BUILD ) 10 | set(WITH_STATIC_LIBRARIES ON) 11 | set(WITH_PIC ON) 12 | set(OPENSSL_USE_STATIC_LIBS ON) 13 | endif() 14 | 15 | set(LDFLAGS_EX "") 16 | if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") 17 | set(LDFLAGS_EX ${LDFLAGS_EX} "-undefined dynamic_lookup") 18 | endif() 19 | 20 | add_library(driver SHARED driver.c) 21 | 22 | if( DEFINED STATIC_BUILD ) 23 | set(CMAKE_C_FLAGS "-ldl -lpthread") 24 | add_subdirectory(../third_party/mosquitto ../third_party/mosquitto/build EXCLUDE_FROM_ALL) 25 | include_directories(../third_party/mosquitto/lib) 26 | if( STATIC_BUILD ) 27 | target_link_libraries(driver libmosquitto_static ${LDFLAGS_EX}) 28 | else() 29 | target_link_libraries(driver libmosquitto ${LDFLAGS_EX} -rdynamic) 30 | endif() 31 | else() 32 | target_link_libraries(driver ${MOSQUITTO_LIBRARIES} ${LDFLAGS_EX}) 33 | endif() 34 | set_target_properties(driver PROPERTIES PREFIX "" OUTPUT_NAME driver) 35 | 36 | install(TARGETS driver DESTINATION ${TARANTOOL_INSTALL_LIBDIR}/mqtt) 37 | install(FILES init.lua DESTINATION ${TARANTOOL_INSTALL_LUADIR}/mqtt) 38 | -------------------------------------------------------------------------------- /mqtt/driver.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) 2016 - 2018 Tarantool AUTHORS: please see AUTHORS file. 4 | * 5 | * Redistribution and use in source and binary forms, with or 6 | * without modification, are permitted provided that the following 7 | * conditions are met: 8 | * 9 | * 1. Redistributions of source code must retain the above 10 | * copyright notice, this list of conditions and the 11 | * following disclaimer. 12 | * 13 | * 2. Redistributions in binary form must reproduce the above 14 | * copyright notice, this list of conditions and the following 15 | * disclaimer in the documentation and/or other materials 16 | * provided with the distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 20 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 22 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 23 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 26 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 29 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include 43 | 44 | 45 | #define TIMEOUT 1.0 46 | 47 | #define MOSQ_LUA_UDATA_NAME "__tnt_mqtt_mosquitto" 48 | 49 | typedef struct mosq_ctx { 50 | lua_State *L; 51 | struct mosquitto *mosq; 52 | int connect_ref; 53 | int disconnect_ref; 54 | int publish_ref; 55 | int message_ref; 56 | int subscribe_ref; 57 | int unsubscribe_ref; 58 | int log_ref; 59 | 60 | int log_level_mask; 61 | } mosq_t; 62 | 63 | enum callback_types { 64 | MESSAGE, 65 | CONNECT, 66 | PUBLISH, 67 | SUBSCRIBE, 68 | UNSUBSCRIBE, 69 | DISCONNECT, 70 | }; 71 | 72 | enum connect_return_codes { 73 | CONN_ACCEPT, 74 | CONN_REF_BAD_PROTOCOL, 75 | CONN_REF_BAD_ID, 76 | CONN_REF_SERVER_NOAVAIL, 77 | CONN_REF_BAD_LOGIN, 78 | CONN_REF_NO_AUTH, 79 | CONN_REF_BAD_TLS 80 | }; 81 | 82 | static bool mosq_initialized = false; 83 | 84 | static inline mosq_t* 85 | mosq_get(lua_State *L, int i) 86 | { 87 | return (mosq_t *) luaL_checkudata(L, i, MOSQ_LUA_UDATA_NAME); 88 | } 89 | 90 | static inline int 91 | make_str_result(lua_State *L, bool ok, const char *str) 92 | { 93 | lua_pushboolean(L, ok); 94 | lua_pushstring(L, str); 95 | return 2; 96 | } 97 | 98 | static inline 99 | int 100 | make_int_result(lua_State *L, bool ok, int i) 101 | { 102 | lua_pushboolean(L, ok); 103 | lua_pushinteger(L, i); 104 | return 2; 105 | } 106 | 107 | static inline 108 | int 109 | make_mosq_status_result(lua_State *L, int mosq_errno) 110 | { 111 | switch (mosq_errno) { 112 | case MOSQ_ERR_SUCCESS: 113 | return make_str_result(L, true, "ok"); 114 | 115 | case MOSQ_ERR_INVAL: 116 | case MOSQ_ERR_NOMEM: 117 | case MOSQ_ERR_PROTOCOL: 118 | case MOSQ_ERR_NOT_SUPPORTED: 119 | case MOSQ_ERR_NO_CONN: 120 | case MOSQ_ERR_CONN_LOST: 121 | case MOSQ_ERR_PAYLOAD_SIZE: 122 | return make_str_result(L, false, mosquitto_strerror(mosq_errno)); 123 | case MOSQ_ERR_ERRNO: 124 | return make_str_result(L, false, strerror(errno)); 125 | default: 126 | break; 127 | } 128 | return make_str_result(L, false, "unknown status"); 129 | } 130 | 131 | static int 132 | mosq_do_io_run_one(mosq_t *ctx, int max_packets) 133 | { 134 | /** XXX 135 | * I have confused: socket < 0 means MOSQ_ERR_NO_CONN, or? 136 | */ 137 | int rc = MOSQ_ERR_NO_CONN, 138 | revents = 0, 139 | fd = mosquitto_socket(ctx->mosq); 140 | 141 | if (fd >= 0) { 142 | 143 | rc = MOSQ_ERR_SUCCESS; 144 | 145 | if (mosquitto_want_write(ctx->mosq)) { 146 | revents = coio_wait(fd, COIO_WRITE, TIMEOUT); 147 | if (revents > 0) { 148 | rc = mosquitto_loop_write(ctx->mosq, max_packets); 149 | } 150 | } else { 151 | revents = coio_wait(fd, COIO_READ, TIMEOUT); 152 | if (revents > 0) 153 | rc = mosquitto_loop_read(ctx->mosq, max_packets); 154 | } 155 | /** 156 | * mosquitto_loop_miss 157 | * This function is handling PINGs and checking 158 | * whether messages need to be retried, 159 | * so should be called fairly _frequently_(!). 160 | * */ 161 | if (rc == MOSQ_ERR_SUCCESS) { 162 | rc = mosquitto_loop_misc(ctx->mosq); 163 | } 164 | } 165 | 166 | return rc; 167 | } 168 | 169 | 170 | static 171 | int 172 | mosq_lib_version(lua_State *L) 173 | { 174 | int major, minor, rev; 175 | char version[16]; 176 | 177 | mosquitto_lib_version(&major, &minor, &rev); 178 | sprintf(version, "%i.%i.%i", major, minor, rev); 179 | 180 | return make_str_result(L, true, version); 181 | } 182 | 183 | static 184 | int 185 | mosq_lib_init(lua_State *L) 186 | { 187 | if (!mosq_initialized) 188 | mosquitto_lib_init(); 189 | 190 | return make_mosq_status_result(L, MOSQ_ERR_SUCCESS); 191 | } 192 | 193 | static 194 | int 195 | mosq_lib_destroy(lua_State *L) 196 | { 197 | mosquitto_lib_cleanup(); 198 | mosq_initialized = false; 199 | return make_mosq_status_result(L, MOSQ_ERR_SUCCESS); 200 | } 201 | 202 | static 203 | int 204 | mosq_new(lua_State *L) 205 | { 206 | const char *id = luaL_optstring(L, 1, NULL); 207 | bool clean_session = (lua_isboolean(L, 2) ? lua_toboolean(L, 2) : true); 208 | 209 | if (id == NULL && !clean_session) 210 | return luaL_argerror(L, 2, 211 | "if 'id' is set then 'clean session' must be true"); 212 | 213 | mosq_t *ctx = (mosq_t *) lua_newuserdata(L, sizeof(mosq_t)); 214 | memset(ctx, 0, sizeof(mosq_t)); 215 | 216 | ctx->L = L; 217 | ctx->connect_ref = LUA_REFNIL; 218 | ctx->disconnect_ref = LUA_REFNIL; 219 | ctx->publish_ref = LUA_REFNIL; 220 | ctx->message_ref = LUA_REFNIL; 221 | ctx->subscribe_ref = LUA_REFNIL; 222 | ctx->unsubscribe_ref = LUA_REFNIL; 223 | ctx->log_ref = LUA_REFNIL; 224 | 225 | ctx->mosq = mosquitto_new(id, clean_session, ctx); 226 | if (ctx->mosq == NULL) 227 | return luaL_error(L, strerror(errno)); 228 | 229 | luaL_getmetatable(L, MOSQ_LUA_UDATA_NAME); 230 | lua_setmetatable(L, -2); 231 | 232 | return 1; 233 | } 234 | 235 | static int 236 | mosq_destroy(lua_State *L) 237 | { 238 | mosq_t *ctx = mosq_get(L, 1); 239 | 240 | if (ctx->mosq) 241 | mosquitto_destroy(ctx->mosq); 242 | 243 | ctx->mosq = NULL; 244 | 245 | luaL_unref(ctx->L, LUA_REGISTRYINDEX, ctx->connect_ref); 246 | luaL_unref(ctx->L, LUA_REGISTRYINDEX, ctx->disconnect_ref); 247 | luaL_unref(ctx->L, LUA_REGISTRYINDEX, ctx->publish_ref); 248 | luaL_unref(ctx->L, LUA_REGISTRYINDEX, ctx->message_ref); 249 | luaL_unref(ctx->L, LUA_REGISTRYINDEX, ctx->subscribe_ref); 250 | luaL_unref(ctx->L, LUA_REGISTRYINDEX, ctx->unsubscribe_ref); 251 | luaL_unref(ctx->L, LUA_REGISTRYINDEX, ctx->log_ref); 252 | 253 | /* remove all methods operating on ctx */ 254 | lua_newtable(L); 255 | lua_setmetatable(L, -2); 256 | 257 | return make_mosq_status_result(L, MOSQ_ERR_SUCCESS); 258 | } 259 | 260 | static int 261 | mosq_will_set(lua_State *L) 262 | { 263 | mosq_t *ctx = mosq_get(L, 1); 264 | const char *topic = luaL_checkstring(L, 2); 265 | size_t payloadlen = 0; 266 | const void *payload = NULL; 267 | 268 | if (!lua_isnil(L, 3)) 269 | payload = lua_tolstring(L, 3, &payloadlen); 270 | 271 | const int qos = luaL_optinteger(L, 4, 0); 272 | const bool retain = lua_toboolean(L, 5); 273 | 274 | return make_mosq_status_result(L, 275 | mosquitto_will_set( 276 | ctx->mosq, topic, payloadlen, payload, 277 | qos, retain)); 278 | } 279 | 280 | static 281 | int 282 | mosq_will_clear(lua_State *L) 283 | { 284 | mosq_t *ctx = mosq_get(L, 1); 285 | return make_mosq_status_result(L, mosquitto_will_clear(ctx->mosq)); 286 | } 287 | 288 | static 289 | int 290 | mosq_login_set(lua_State *L) 291 | { 292 | mosq_t *ctx = mosq_get(L, 1); 293 | const char *username = (lua_isnil(L, 2) ? NULL : luaL_checkstring(L, 2)); 294 | const char *password = (lua_isnil(L, 3) ? NULL : luaL_checkstring(L, 3)); 295 | return make_mosq_status_result(L, 296 | mosquitto_username_pw_set(ctx->mosq, username, password)); 297 | } 298 | 299 | static 300 | int 301 | mosq_tls_set(lua_State *L) 302 | { 303 | mosq_t *ctx = mosq_get(L, 1); 304 | const char *cafile = luaL_optstring(L, 2, NULL); 305 | const char *capath = luaL_optstring(L, 3, NULL); 306 | const char *certfile = luaL_optstring(L, 4, NULL); 307 | const char *keyfile = luaL_optstring(L, 5, NULL); 308 | /* 309 | the last param is a callback to a function that asks for a passphrase 310 | for a keyfile our keyfiles should NOT have a passphrase 311 | */ 312 | return make_mosq_status_result(L, 313 | mosquitto_tls_set(ctx->mosq, cafile, capath, certfile, keyfile, 0)); 314 | } 315 | 316 | static 317 | int 318 | mosq_tls_insecure_set(lua_State *L) 319 | { 320 | mosq_t *ctx = mosq_get(L, 1); 321 | const bool value = lua_toboolean(L, 2); 322 | return make_mosq_status_result(L, 323 | mosquitto_tls_insecure_set(ctx->mosq, value)); 324 | } 325 | 326 | static 327 | int 328 | mosq_connect(lua_State *L) 329 | { 330 | mosq_t *ctx = mosq_get(L, 1); 331 | const char *host = luaL_optstring(L, 2, "localhost"); 332 | const int port = luaL_optinteger(L, 3, 1883); 333 | const int keepalive = luaL_optinteger(L, 4, 60); 334 | return make_mosq_status_result(L, 335 | mosquitto_connect(ctx->mosq, host, port, keepalive)); 336 | } 337 | 338 | static 339 | int 340 | mosq_reconnect(lua_State *L) 341 | { 342 | mosq_t *ctx = mosq_get(L, 1); 343 | 344 | int rc = mosquitto_reconnect(ctx->mosq); 345 | return make_mosq_status_result(L, rc); 346 | } 347 | 348 | static 349 | int 350 | mosq_disconnect(lua_State *L) 351 | { 352 | mosq_t *ctx = mosq_get(L, 1); 353 | 354 | int rc = mosquitto_disconnect(ctx->mosq); 355 | return make_mosq_status_result(L, rc); 356 | } 357 | 358 | static 359 | int 360 | mosq_publish(lua_State *L) 361 | { 362 | mosq_t *ctx = mosq_get(L, 1); 363 | /* message id is referenced in the publish callback */ 364 | int mid = -1; 365 | const char *topic = luaL_checkstring(L, 2); 366 | size_t payloadlen = 0; 367 | const void *payload = NULL; 368 | 369 | if (!lua_isnil(L, 3)) { 370 | payload = lua_tolstring(L, 3, &payloadlen); 371 | }; 372 | 373 | int qos = luaL_optinteger(L, 4, 0); 374 | bool retain = lua_toboolean(L, 5); 375 | 376 | int rc = mosquitto_publish(ctx->mosq, &mid, topic, payloadlen, payload, 377 | qos, retain); 378 | if (rc != MOSQ_ERR_SUCCESS) { 379 | return make_mosq_status_result(L, rc); 380 | } 381 | 382 | return make_int_result(L, true, mid); 383 | } 384 | 385 | static 386 | int 387 | mosq_subscribe(lua_State *L) 388 | { 389 | mosq_t *ctx = mosq_get(L, 1); 390 | int mid; 391 | const char *sub = luaL_checkstring(L, 2); 392 | int qos = luaL_optinteger(L, 3, 0); 393 | 394 | int rc = mosquitto_subscribe(ctx->mosq, &mid, sub, qos); 395 | 396 | if (rc != MOSQ_ERR_SUCCESS) { 397 | return make_mosq_status_result(L, rc); 398 | } 399 | 400 | return make_int_result(L, true, mid); 401 | } 402 | 403 | static 404 | int 405 | mosq_unsubscribe(lua_State *L) 406 | { 407 | mosq_t *ctx = mosq_get(L, 1); 408 | int mid; 409 | const char *sub = luaL_checkstring(L, 2); 410 | 411 | int rc = mosquitto_unsubscribe(ctx->mosq, &mid, sub); 412 | 413 | if (rc != MOSQ_ERR_SUCCESS) { 414 | return make_mosq_status_result(L, rc); 415 | } 416 | 417 | return make_int_result(L, true, mid); 418 | } 419 | 420 | static void 421 | mosq_connect_f(struct mosquitto *mosq __attribute__((unused)), 422 | void *obj, int rc) 423 | { 424 | mosq_t *ctx = obj; 425 | if (!ctx || !ctx->mosq) 426 | return; 427 | enum connect_return_codes return_code = rc; 428 | 429 | bool success = false; 430 | const char *str = "connection status unknown"; 431 | 432 | switch (return_code) { 433 | case CONN_ACCEPT: 434 | success = true; 435 | str = "connection accepted"; 436 | break; 437 | 438 | case CONN_REF_BAD_PROTOCOL: 439 | str = "connection refused - incorrect protocol version"; 440 | break; 441 | 442 | case CONN_REF_BAD_ID: 443 | str = "connection refused - invalid client identifier"; 444 | break; 445 | 446 | case CONN_REF_SERVER_NOAVAIL: 447 | str = "connection refused - server unavailable"; 448 | break; 449 | 450 | case CONN_REF_BAD_LOGIN: 451 | str = "connection refused - bad username or password"; 452 | break; 453 | 454 | case CONN_REF_NO_AUTH: 455 | str = "connection refused - not authorised"; 456 | break; 457 | 458 | case CONN_REF_BAD_TLS: 459 | str = "connection refused - TLS error"; 460 | break; 461 | default: 462 | break; 463 | } 464 | 465 | lua_rawgeti(ctx->L, LUA_REGISTRYINDEX, ctx->connect_ref); 466 | 467 | lua_pushboolean(ctx->L, success); 468 | lua_pushinteger(ctx->L, rc); 469 | lua_pushstring(ctx->L, str); 470 | 471 | if (lua_pcall(ctx->L, 3, 0, 0) != LUA_OK) 472 | say_error("Connect callback failed: ref:%d, message: \"%s\"", 473 | ctx->connect_ref, lua_tostring(ctx->L, -1)); 474 | } 475 | 476 | static void 477 | mosq_disconnect_f(struct mosquitto *mosq __attribute__((unused)), 478 | void *obj, int rc) 479 | { 480 | mosq_t *ctx = obj; 481 | if (!ctx || !ctx->mosq) 482 | return; 483 | bool success = true; 484 | char *str = "client-initiated disconnect"; 485 | 486 | if (rc) { 487 | success = false; 488 | str = "unexpected disconnect"; 489 | } 490 | 491 | lua_rawgeti(ctx->L, LUA_REGISTRYINDEX, ctx->disconnect_ref); 492 | 493 | lua_pushboolean(ctx->L, success); 494 | lua_pushinteger(ctx->L, rc); 495 | lua_pushstring(ctx->L, str); 496 | 497 | if (lua_pcall(ctx->L, 3, 0, 0) != LUA_OK) 498 | say_error("Disconnect callback failed: ref:%d, message: \"%s\"", 499 | ctx->disconnect_ref, lua_tostring(ctx->L, -1)); 500 | 501 | } 502 | 503 | static void 504 | mosq_publish_f(struct mosquitto *mosq __attribute__((unused)), 505 | void *obj, int mid) 506 | { 507 | mosq_t *ctx = obj; 508 | if (!ctx || !ctx->mosq) 509 | return; 510 | lua_rawgeti(ctx->L, LUA_REGISTRYINDEX, ctx->publish_ref); 511 | lua_pushinteger(ctx->L, mid); 512 | if (lua_pcall(ctx->L, 1, 0, 0) != LUA_OK) 513 | say_error("Publish callback failed: ref:%d, message: \"%s\"", 514 | ctx->publish_ref, lua_tostring(ctx->L, -1)); 515 | 516 | } 517 | 518 | static void 519 | mosq_message_f(struct mosquitto *mosq __attribute__((unused)), 520 | void *obj, const struct mosquitto_message *msg) 521 | { 522 | mosq_t *ctx = obj; 523 | if (!ctx || !ctx->mosq) 524 | return; 525 | lua_rawgeti(ctx->L, LUA_REGISTRYINDEX, ctx->message_ref); 526 | 527 | /** 528 | * function F(mid, topic, payload, qos, retain) 529 | */ 530 | lua_pushinteger(ctx->L, msg->mid); 531 | lua_pushstring(ctx->L, msg->topic); 532 | lua_pushlstring(ctx->L, msg->payload, msg->payloadlen); 533 | lua_pushinteger(ctx->L, msg->qos); 534 | lua_pushboolean(ctx->L, msg->retain); 535 | 536 | if (lua_pcall(ctx->L, 5, 0, 0) != LUA_OK) 537 | say_error("Message callback failed: ref:%d, message: \"%s\"", 538 | ctx->message_ref, lua_tostring(ctx->L, -1)); 539 | } 540 | 541 | static void 542 | mosq_subscribe_f(struct mosquitto *mosq __attribute__((unused)), 543 | void *obj, int mid, int qos_count, const int *granted_qos) 544 | { 545 | mosq_t *ctx = obj; 546 | if (!ctx || !ctx->mosq) 547 | return; 548 | lua_rawgeti(ctx->L, LUA_REGISTRYINDEX, ctx->subscribe_ref); 549 | lua_pushinteger(ctx->L, mid); 550 | int i = 0; 551 | for (i = 0; i < qos_count; i++) 552 | lua_pushinteger(ctx->L, granted_qos[i]); 553 | if (lua_pcall(ctx->L, qos_count + 1, 0, 0) != LUA_OK) 554 | say_error("Subscribe callback failed: ref:%d, message: \"%s\"", 555 | ctx->subscribe_ref, lua_tostring(ctx->L, -1)); 556 | } 557 | 558 | static void 559 | mosq_unsubscribe_f(struct mosquitto *mosq __attribute__((unused)), 560 | void *obj, int mid) 561 | { 562 | mosq_t *ctx = obj; 563 | if (!ctx || !ctx->mosq) 564 | return; 565 | lua_rawgeti(ctx->L, LUA_REGISTRYINDEX, ctx->unsubscribe_ref); 566 | lua_pushinteger(ctx->L, mid); 567 | if (lua_pcall(ctx->L, 1, 0, 0) != LUA_OK) 568 | say_error("Unsubscribe callback failed: ref:%d, message: \"%s\"", 569 | ctx->unsubscribe_ref, lua_tostring(ctx->L, -1)); 570 | } 571 | 572 | static void 573 | mosq_log_f(struct mosquitto *mosq __attribute__((unused)), 574 | void *obj, int level, const char *message) 575 | { 576 | mosq_t *ctx = obj; 577 | if (!ctx || !ctx->mosq) 578 | return; 579 | if (ctx->log_level_mask & level) { 580 | lua_rawgeti(ctx->L, LUA_REGISTRYINDEX, ctx->log_ref); 581 | lua_pushinteger(ctx->L, level); 582 | lua_pushstring(ctx->L, message); 583 | if (lua_pcall(ctx->L, 2, 0, 0) != LUA_OK) 584 | say_error("Loc callback failed: ref:%d, message: \"%s\"", 585 | ctx->log_ref, lua_tostring(ctx->L, -1)); 586 | } 587 | } 588 | 589 | static int 590 | mosq_log_callback_set(lua_State *L) 591 | { 592 | mosq_t *ctx = mosq_get(L, 1); 593 | int log_level_mask = luaL_optinteger(L, 2, MOSQ_LOG_ERR); 594 | if (!lua_isfunction(L, 3)) 595 | return luaL_argerror(L, 3, "expecting a function"); 596 | 597 | ctx->log_level_mask = log_level_mask; 598 | ctx->log_ref = luaL_ref(L, LUA_REGISTRYINDEX); 599 | mosquitto_log_callback_set(ctx->mosq, mosq_log_f); 600 | 601 | return make_mosq_status_result(L, MOSQ_ERR_SUCCESS); 602 | } 603 | 604 | static int 605 | mosq_callback_set(lua_State *L) 606 | { 607 | mosq_t *ctx = mosq_get(L, 1); 608 | 609 | enum callback_types callback_type = luaL_checkinteger(L, 2); 610 | 611 | if (!lua_isfunction(L, 3)) 612 | return luaL_argerror(L, 3, "expecting a function"); 613 | 614 | int ref = luaL_ref(L, LUA_REGISTRYINDEX); 615 | 616 | switch (callback_type) { 617 | case CONNECT: 618 | ctx->connect_ref = ref; 619 | mosquitto_connect_callback_set(ctx->mosq, mosq_connect_f); 620 | break; 621 | 622 | case DISCONNECT: 623 | ctx->disconnect_ref = ref; 624 | mosquitto_disconnect_callback_set(ctx->mosq, mosq_disconnect_f); 625 | break; 626 | 627 | case PUBLISH: 628 | ctx->publish_ref = ref; 629 | mosquitto_publish_callback_set(ctx->mosq, mosq_publish_f); 630 | break; 631 | 632 | case MESSAGE: 633 | ctx->message_ref = ref; 634 | mosquitto_message_callback_set(ctx->mosq, mosq_message_f); 635 | break; 636 | 637 | case SUBSCRIBE: 638 | ctx->subscribe_ref = ref; 639 | mosquitto_subscribe_callback_set(ctx->mosq, mosq_subscribe_f); 640 | break; 641 | 642 | case UNSUBSCRIBE: 643 | ctx->unsubscribe_ref = ref; 644 | mosquitto_unsubscribe_callback_set(ctx->mosq, mosq_unsubscribe_f); 645 | break; 646 | 647 | default: 648 | luaL_unref(L, LUA_REGISTRYINDEX, ref); 649 | luaL_argerror(L, 2, "unknown mosquitto callback type"); 650 | break; 651 | } 652 | 653 | return make_mosq_status_result(L, MOSQ_ERR_SUCCESS); 654 | } 655 | 656 | static int 657 | mosq_io_run_one(lua_State *L) 658 | { 659 | mosq_t *ctx = mosq_get(L, 1); 660 | if (!ctx || !ctx->mosq) { 661 | say_error("mosq_io_run_one() called but object was destroyed"); 662 | return 0; 663 | } 664 | int max_packets = luaL_optinteger(L, 2, 1); 665 | return make_mosq_status_result(L, mosq_do_io_run_one(ctx, max_packets)); 666 | } 667 | 668 | /* 669 | * List of exporting: aliases, callbacks, definitions, functions etc [[ 670 | */ 671 | struct define { 672 | const char* name; 673 | int value; 674 | }; 675 | 676 | static const struct define defines[] = { 677 | 678 | {"CONNECT", CONNECT}, 679 | {"DISCONNECT", DISCONNECT}, 680 | {"PUBLISH", PUBLISH}, 681 | {"MESSAGE", MESSAGE}, 682 | {"SUBSCRIBE", SUBSCRIBE}, 683 | {"UNSUBSCRIBE", UNSUBSCRIBE}, 684 | 685 | /** Log levels [[ 686 | */ 687 | {"LOG_NONE", MOSQ_LOG_NONE}, 688 | {"LOG_INFO", MOSQ_LOG_INFO}, 689 | {"LOG_NOTICE", MOSQ_LOG_NOTICE}, 690 | {"LOG_WARNING", MOSQ_LOG_WARNING}, 691 | {"LOG_ERROR", MOSQ_LOG_ERR}, 692 | {"LOG_DEBUG", MOSQ_LOG_DEBUG}, 693 | {"LOG_ALL", MOSQ_LOG_ALL}, 694 | /** ]] 695 | */ 696 | 697 | {NULL, 0} 698 | }; 699 | 700 | /* 701 | * Lists of exporting: object and/or functions to the Lua 702 | */ 703 | 704 | static const struct luaL_Reg R[] = { 705 | {"version", mosq_lib_version}, 706 | {"init", mosq_lib_init}, 707 | {"lib_destroy", mosq_lib_destroy}, 708 | {"new", mosq_new}, 709 | {NULL, NULL} 710 | }; 711 | 712 | static const struct luaL_Reg M[] = { 713 | 714 | {"destroy", mosq_destroy}, 715 | 716 | /** Setup, options, misc [[ 717 | */ 718 | {"will_set", mosq_will_set}, 719 | {"will_clear", mosq_will_clear}, 720 | {"login_set", mosq_login_set}, 721 | {"tls_insecure_set", mosq_tls_insecure_set}, 722 | {"tls_set", mosq_tls_set}, 723 | /* ]] 724 | */ 725 | 726 | /** Events [[ 727 | */ 728 | {"publish", mosq_publish}, 729 | {"subscribe", mosq_subscribe}, 730 | {"unsubscribe", mosq_unsubscribe}, 731 | {"callback_set", mosq_callback_set}, 732 | {"log_callback_set", mosq_log_callback_set}, 733 | /* ]] 734 | */ 735 | 736 | /** Networking, coio loop [[ 737 | */ 738 | {"connect", mosq_connect}, 739 | {"reconnect", mosq_reconnect}, 740 | {"disconnect", mosq_disconnect}, 741 | {"io_run_one", mosq_io_run_one}, 742 | /** ]] 743 | */ 744 | 745 | {NULL, NULL} 746 | }; 747 | /* 748 | * ]] 749 | */ 750 | 751 | 752 | /* 753 | * Lib initializer 754 | */ 755 | LUA_API int 756 | luaopen_mqtt_driver(lua_State *L) 757 | { 758 | mosquitto_lib_init(); 759 | mosq_initialized = true; 760 | 761 | /** 762 | * Add metatable.__index = metatable 763 | */ 764 | luaL_newmetatable(L, MOSQ_LUA_UDATA_NAME); 765 | lua_pushvalue(L, -1); 766 | lua_setfield(L, -2, "__index"); 767 | luaL_register(L, NULL, M); 768 | luaL_register(L, NULL, R); 769 | 770 | /** 771 | * Add definitions 772 | */ 773 | const struct define *defs = &defines[0]; 774 | while (defs->name) { 775 | lua_pushinteger(L, defs->value); 776 | lua_setfield(L, -2, defs->name); 777 | defs++; 778 | } 779 | 780 | return 1; 781 | } 782 | -------------------------------------------------------------------------------- /mqtt/init.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Copyright (C) 2016 - 2018 Tarantool AUTHORS: please see AUTHORS file. 3 | -- 4 | -- Redistribution and use in source and binary forms, with or 5 | -- without modification, are permitted provided that the following 6 | -- conditions are met: 7 | -- 8 | -- 1. Redistributions of source code must retain the above 9 | -- copyright notice, this list of conditions and the 10 | -- following disclaimer. 11 | -- 12 | -- 2. Redistributions in binary form must reproduce the above 13 | -- copyright notice, this list of conditions and the following 14 | -- disclaimer in the documentation and/or other materials 15 | -- provided with the distribution. 16 | -- 17 | -- THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND 18 | -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | -- TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | -- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 21 | -- OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 22 | -- INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | -- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | -- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 25 | -- BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | -- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 28 | -- THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | -- SUCH DAMAGE. 30 | -- 31 | 32 | local mqtt_driver = require('mqtt.driver') 33 | local fiber = require('fiber') 34 | local log = require('log') 35 | local json = require('json') 36 | 37 | local mqtt_mt 38 | 39 | -- 40 | -- Create a new mosquitto client instance. 41 | -- 42 | -- Parameters: 43 | -- id - String to use as the client id. If NULL, a random client id 44 | -- will be generated. If id is NULL, clean_session must be true. 45 | -- clean_session - set to true to instruct the broker to clean all messages 46 | -- and subscriptions on disconnect, false to instruct it to 47 | -- keep them. See the man page mqtt(7) for more details. 48 | -- Note that a client will never discard its own outgoing 49 | -- messages on disconnect. Calling or 50 | -- will cause the messages to be resent. 51 | -- Must be set to true if the id parameter is NULL. 52 | -- 53 | -- Returns: 54 | -- mqtt object or raise error 55 | -- 56 | local new = function(id, clean_session) 57 | local mqtt = mqtt_driver.new(id, clean_session) 58 | local ok, version_string = mqtt.version() 59 | 60 | return setmetatable({VERSION = version_string, 61 | RECONNECT_INTERVAL = 0.5, 62 | POLL_INTERVAL = 0.0, 63 | 64 | mqtt = mqtt, 65 | lib_destroy = mqtt_driver.lib_destroy, 66 | connected = false, 67 | auto_reconect = true, 68 | fiber = nil, 69 | 70 | subscribers = {}, }, mqtt_mt) 71 | end 72 | 73 | mqtt_mt = { 74 | 75 | __index = { 76 | 77 | -- 78 | -- Private functions 79 | -- 80 | __connect = function(self, opts) 81 | local host = self:__opt_get_or_nil(opts, 'host') 82 | local port = self:__opt_get_or_nil(opts, 'port') 83 | local keepalive = self:__opt_get_or_nil(opts, 'keepalive') 84 | return self.mqtt:connect(host, port, keepalive) 85 | end, 86 | 87 | -- [Re]Connect until not connected [[ 88 | __reconnect = function(self) 89 | while not self.connected do 90 | -- Reconnect 91 | self.connected, _ = self.mqtt:reconnect() 92 | -- Restoring subscribing 93 | if self.connected then 94 | for topic, opts in pairs(self.subscribers) do 95 | self.connected, _ = self.mqtt:subscribe(topic, opts.qos) 96 | end 97 | end 98 | fiber.sleep(self.RECONNECT_INTERVAL) 99 | end 100 | end, 101 | -- ]] 102 | 103 | -- BG work 104 | __io_loop = function(self) 105 | local mq = self.mqtt 106 | while true do 107 | fiber.testcancel() 108 | self.connected = mq:io_run_one() 109 | if not self.connected then 110 | if self.auto_reconect then 111 | self:__reconnect() 112 | else 113 | log.error( 114 | 'mqtt: the client is not currently connected, error %s', emsg) 115 | end 116 | end 117 | fiber.sleep(self.POLL_INTERVAL) 118 | end 119 | end, 120 | 121 | -- libmosquitto's log hook 122 | __default_logger_set = function(self, opts) 123 | local log_mask = self:__opt_get_or_nil(opts, 'log_mask') 124 | return self.mqtt:log_callback_set(log_mask, function(level, message) 125 | log.error("mqtt[%s]:'%s'", level, message) 126 | end) 127 | end, 128 | 129 | -- Helpers [[ 130 | __opt_get = function(self, opts, what, default) 131 | if opts[what] then 132 | return opts[what] 133 | end 134 | return default 135 | end, 136 | __opt_get_or_nil = function(self, opts, what) 137 | return self:__opt_get(opts, what) 138 | end, 139 | -- ]] 140 | 141 | -- 142 | -- Public functions 143 | -- 144 | 145 | -- 146 | -- Connect to an MQTT broker. 147 | -- 148 | -- Parameters: 149 | -- opts.host - the hostname or ip address of the broker to connect to. 150 | -- opts.port - the network port to connect to. Usually 1883. 151 | -- opts.keepalive - the number of seconds after which the broker should send a PING 152 | -- message to the client if no other messages have been exchanged 153 | -- in that time. 154 | -- opts.log_mask - LOG_NONE, LOG_INFO, LOG_NOTICE, LOG_WARNING, 155 | -- LOG_ERROR[default], LOG_DEBUG, LOG_ALL 156 | -- opts.auto_reconect - true[default], false - auto reconnect on, off 157 | -- 158 | -- Returns: 159 | -- true or false, emsg 160 | -- 161 | connect = function(self, opts_) 162 | 163 | if type(self) ~= 'table' or self.mqtt == nil then 164 | error("mqtt: usage: mqtt:connect()") 165 | end 166 | 167 | local opts = opts_ or {} 168 | 169 | self.auto_reconect = self:__opt_get(opts, 'auto_reconect', true) 170 | 171 | -- Setup logger 172 | local ok, emsg = self:__default_logger_set(opts) 173 | if not ok then 174 | return ok, emsg 175 | end 176 | 177 | ok, emsg = self:__connect(opts) 178 | if ok then 179 | self.fiber = fiber.create(self.__io_loop, self) 180 | end 181 | 182 | return ok, emsg 183 | end, 184 | 185 | -- 186 | -- Reconnect to a broker. 187 | -- 188 | -- This function provides an easy way of reconnecting to a broker after a 189 | -- connection has been lost. It uses the values that were provided in the 190 | -- call. It must not be called before 191 | -- . 192 | -- 193 | -- NOTE 194 | -- After reconnecting you must call for subscribing 195 | -- to a topics. 196 | -- 197 | -- See 198 | -- 199 | -- 200 | reconnect = function(self) 201 | if type(self) ~= 'table' or self.mqtt == nil then 202 | error("mqtt: usage: mqtt:reconnect()") 203 | end 204 | return self.mqtt:reconnect() 205 | end, 206 | 207 | -- 208 | -- Subscribe to a topic. 209 | -- 210 | -- Parameters: 211 | -- sub - the subscription pattern. 212 | -- qos - the requested Quality of Service for this subscription. 213 | -- 214 | -- Returns: 215 | -- true or false, integer mid or error message 216 | -- 217 | subscribe = function(self, topic, qos) 218 | if type(self) ~= 'table' or self.mqtt == nil then 219 | error("mqtt: usage: mqtt:subscribe()") 220 | end 221 | local ok, mid_or_emsg = self.mqtt:subscribe(topic, qos) 222 | if ok then 223 | self.subscribers[topic] = {qos = qos} 224 | end 225 | return ok, mid_or_emsg 226 | end, 227 | 228 | -- 229 | -- Unsubscribe from a topic. 230 | -- 231 | -- Parameters: 232 | -- topic - the unsubscription pattern. 233 | -- 234 | -- Returns: 235 | -- true or false, integer mid or error message 236 | -- 237 | unsubscribe = function(self, topic) 238 | if type(self) ~= 'table' or self.mqtt == nil then 239 | error("mqtt: usage: mqtt:unsubscribe()") 240 | end 241 | local ok, emsg = self.mqtt:unsubscribe(topic) 242 | self.subscribers[topic] = nil 243 | return ok, emsg 244 | end, 245 | 246 | -- 247 | -- Publish a message on a given topic. 248 | -- 249 | -- Parameters: 250 | -- topic - null terminated string of the topic to publish to. 251 | -- payload - some data (e.g. number, string) or table. 252 | -- qos - integer value 0, 1 or 2 indicating the Quality of Service to be 253 | -- used for the will. When you call the library as "mqtt = require('mqtt')" 254 | -- can be used mqtt.QOS_0, mqtt.QOS_1 and mqtt.QOS_2 as a replacement for some strange 255 | -- digital variable 256 | -- retain - set to true to make the will a retained message. You can also use the values 257 | -- mqtt.RETAIN and mqtt.NON_RETAIN to replace the unmarked variable 258 | -- 259 | -- Returns: 260 | -- true or false, emsg, message id(e.g. MID) is referenced in the publish callback 261 | -- 262 | publish = function(self, topic, payload, qos, retail) 263 | if type(self) ~= 'table' or self.mqtt == nil then 264 | error("mqtt: usage: mqtt:publish()") 265 | end 266 | local raw_payload = payload 267 | if type(payload) == 'table' then 268 | raw_payload = json.encode(payload) 269 | end 270 | return self.mqtt:publish(topic, raw_payload, qos, retail) 271 | end, 272 | 273 | -- 274 | -- Configure will information for a mosquitto instance. By default, clients do 275 | -- not have a will. This must be called before calling . 276 | -- 277 | -- Parameters: 278 | -- topic - the topic on which to publish the will. 279 | -- payload - pointer to the data to send. 280 | -- qos - integer value 0, 1 or 2 indicating the Quality of Service to be 281 | -- used for the will. 282 | -- retain - set to true to make the will a retained message. 283 | -- 284 | -- Returns: 285 | -- true or false, emsg 286 | -- 287 | will_set = function(self, topic, payload, qos, retain) 288 | return self.mqtt:will_set(topic, payload, qos, retain) 289 | end, 290 | 291 | -- 292 | -- 293 | -- Remove a previously configured will. This must be called before calling 294 | -- . 295 | -- 296 | -- Returns: 297 | -- true or false, emsg 298 | -- 299 | will_clear = function(self) 300 | return self.mqtt:will_clear() 301 | end, 302 | 303 | -- 304 | -- Configure username and password for a mosquitton instance. This is only 305 | -- supported by brokers that implement the MQTT spec v3.1. By default, no 306 | -- username or password will be sent. 307 | -- If username is NULL, the password argument is ignored. 308 | -- This must be called before calling connect. 309 | -- 310 | -- This is must be called before calling . 311 | -- 312 | -- Parameters: 313 | -- username - the username to send as a string, or NULL to disable 314 | -- authentication. 315 | -- password - the password to send as a string. Set to NULL when username is 316 | -- valid in order to send just a username. 317 | -- 318 | -- Returns: 319 | -- true or false, emsg 320 | -- 321 | login_set = function(self, username, password) 322 | if type(self) ~= 'table' or self.mqtt == nil then 323 | error("mqtt: usage: mqtt:login_set()") 324 | end 325 | return self.mqtt:login_set(username, password) 326 | end, 327 | 328 | -- 329 | -- Configure verification of the server hostname in the server certificate. If 330 | -- value is set to true, it is impossible to guarantee that the host you are 331 | -- connecting to is not impersonating your server. This can be useful in 332 | -- initial server testing, but makes it possible for a malicious third party to 333 | -- impersonate your server through DNS spoofing, for example. 334 | -- Do not use this function in a real system. Setting value to true makes the 335 | -- connection encryption pointless. 336 | -- Must be called before . 337 | -- 338 | -- Parameters: 339 | -- value - if set to false, the default, certificate hostname checking is 340 | -- performed. If set to true, no hostname checking is performed and 341 | -- the connection is insecure. 342 | -- 343 | -- Returns: 344 | -- true or false, emsg 345 | -- 346 | tls_insecure_set = function(self, value) 347 | return self.mqtt:tls_insecure_set(value) 348 | end, 349 | 350 | -- 351 | -- Configure the client for certificate based SSL/TLS support. Must be called 352 | -- before . 353 | -- 354 | -- Define the Certificate Authority certificates to be trusted (ie. the server 355 | -- certificate must be signed with one of these certificates) using cafile. 356 | -- 357 | -- If the server you are connecting to requires clients to provide a 358 | -- certificate, define certfile and keyfile with your client certificate and 359 | -- private key. If your private key is encrypted, provide a password callback 360 | -- function or you will have to enter the password at the command line. 361 | -- 362 | -- Parameters: 363 | -- cafile - path to a file containing the PEM encoded trusted CA 364 | -- certificate files. Either cafile or capath must not be NULL. 365 | -- capath - path to a directory containing the PEM encoded trusted CA 366 | -- certificate files. See mosquitto.conf for more details on 367 | -- configuring this directory. Either cafile or capath must not 368 | -- be NULL. 369 | -- certfile - path to a file containing the PEM encoded certificate file 370 | -- for this client. If NULL, keyfile must also be NULL and no 371 | -- client certificate will be used. 372 | -- keyfile - path to a file containing the PEM encoded private key for 373 | -- this client. If NULL, certfile must also be NULL and no 374 | -- client certificate will be used. 375 | -- pw_callback - TODO Implement me 376 | -- 377 | -- Returns: 378 | -- true or false, emsg 379 | -- 380 | -- See Also: 381 | -- 382 | -- 383 | tls_set = function(self, cafile, capath, certfile, keyfile) 384 | return self.mqtt:tls_set(cafile, capath, certfile, keyfile) 385 | end, 386 | 387 | -- 388 | -- Subscribe on events 389 | -- 390 | 391 | -- 392 | -- Set the connect callback. This is called when the broker sends a CONNACK 393 | -- message in response to a connection. 394 | -- 395 | -- Parameters: 396 | -- F - a callback function in the following form: 397 | -- function F(boolean_success, integer_ret_code, string_message) 398 | -- 399 | -- Returns: 400 | -- true or false, emsg 401 | -- 402 | on_connect = function(self, F) 403 | return self.mqtt:callback_set(mqtt_driver.CONNECT, F) 404 | end, 405 | 406 | -- 407 | -- Set the publish callback. This is called when a message initiated with 408 | -- has been sent to the broker successfully. 409 | -- 410 | -- Parameters: 411 | -- F - a callback function in the following form: 412 | -- function F(integer_mid) 413 | -- integer_mid - the message id of the sent message. 414 | -- 415 | -- Returns: 416 | -- true or false, emsg 417 | -- 418 | on_publish = function(self, F) 419 | return self.mqtt:callback_set(mqtt_driver.PUBLISH, F) 420 | end, 421 | 422 | -- 423 | -- Set the subscribe callback. This is called when the broker responds to a 424 | -- subscription request. 425 | -- 426 | -- Parameters: 427 | -- F - a callback function in the following form: 428 | -- function F(integer_mid, array_of_qos) 429 | -- integer_mid - the message id of the subscribe message. 430 | -- array_of_qos - an array of integers indicating the granted QoS for 431 | -- each of the subscriptions. 432 | -- Returns: 433 | -- true or false, emsg 434 | -- 435 | on_subscribe = function(self, F) 436 | return self.mqtt:callback_set(mqtt_driver.SUBSCRIBE, F) 437 | end, 438 | 439 | -- 440 | -- Set the unsubscribe callback. This is called when the broker responds to a 441 | -- unsubscription request. 442 | -- 443 | -- Parameters: 444 | -- F - a callback function in the following form: 445 | -- function F(integer_mid) 446 | -- mid - the message id of the unsubscribe message. 447 | -- 448 | -- Returns: 449 | -- true or false, emsg 450 | -- 451 | on_unsubscribe = function(self, F) 452 | return self.mqtt:callback_set(mqtt_driver.UNSUBSCRIBE, F) 453 | end, 454 | 455 | -- 456 | -- Set the disconnect callback. This is called when the broker has received the 457 | -- DISCONNECT command and has disconnected the client. 458 | -- 459 | -- Parameters: 460 | -- on_disconnect - a callback function in the following form: 461 | -- function F(boolean_success, integer_ret_code, string_message) 462 | -- 463 | -- Returns: 464 | -- true or false, emsg 465 | -- 466 | on_disconnect = function(self, F) 467 | return self.mqtt:callback_set(mqtt_driver.DISCONNECT, F) 468 | end, 469 | 470 | -- 471 | -- Set the message callback. This is called when a message is received 472 | -- from the broker. 473 | -- 474 | -- Parameters: 475 | -- F - a callback function in the following form: 476 | -- function F(integer_mid, string_topic, string_payload, integer_qos, integer_retain) 477 | -- 478 | -- Returns: 479 | -- true or false, emsg 480 | -- 481 | on_message = function(self, F) 482 | return self.mqtt:callback_set(mqtt_driver.MESSAGE, F) 483 | end, 484 | 485 | -- 486 | -- Destroy (i.e. free) self 487 | -- 488 | destroy = function(self) 489 | if type(self) ~= 'table' or self.mqtt == nil then 490 | error("mqtt: usage: mqtt:destroy()") 491 | end 492 | if (self.fiber ~= nil and self.fiber.cancel ~= nil) then 493 | self.fiber:cancel() 494 | end 495 | if next(getmetatable(self.mqtt)) == nil then 496 | return false, "already destroyed" 497 | end 498 | local ok, emsg = self.mqtt:destroy() 499 | self = nil 500 | return ok, emsg 501 | end, 502 | }, 503 | } 504 | 505 | -- 506 | -- Export 507 | -- 508 | return { 509 | new = new, 510 | QOS_0 = 0, 511 | QOS_1 = 1, 512 | QOS_2 = 2, 513 | RETAIN = true, 514 | NON_RETAIN = false 515 | } 516 | -------------------------------------------------------------------------------- /rpm/tarantool-mqtt.spec: -------------------------------------------------------------------------------- 1 | Name: tarantool-mqtt 2 | Version: 1.4.0 3 | Release: 1%{?dist} 4 | Summary: Tarantool MQTT module 5 | Group: Applications/Databases 6 | License: BSD 7 | URL: https://github.com/tarantool/mqtt 8 | Source0: https://github.com/tarantool/mqtt/archive/%{version}/mqtt-%{version}.tar.gz 9 | BuildRequires: cmake >= 2.8 10 | BuildRequires: gcc >= 4.5 11 | BuildRequires: tarantool >= 1.6.8.0 12 | BuildRequires: tarantool-devel >= 1.6.8.0 13 | BuildRequires: mosquitto-devel >= 1.4.8 14 | BuildRequires: mosquitto >= 1.4.8 15 | BuildRequires: openssl-devel 16 | 17 | Requires: mosquitto >= 1.4.8 18 | Requires: openssl 19 | 20 | %description 21 | Tarantool bindings to mqtt library 22 | 23 | %prep 24 | %setup -q -n mqtt-%{version} 25 | 26 | %build 27 | %cmake . -DCMAKE_BUILD_TYPE=RelWithDebInfo 28 | make %{?_smp_mflags} 29 | make -j2 test 30 | 31 | %install 32 | %make_install 33 | 34 | %files 35 | %{_libdir}/tarantool/*/ 36 | %{_datarootdir}/tarantool/*/ 37 | %doc README.md 38 | %{!?_licensedir:%global license %doc} 39 | %license LICENSE 40 | 41 | %changelog 42 | * Mon Jul 9 2018 V. Soshnikov 1.4.0 43 | - Fixed: https://github.com/tarantool/mqtt/issues/14 44 | 45 | * Mon May 28 2018 V. Soshnikov 1.3.0 46 | - Fixed: https://github.com/tarantool/mqtt/issues/12 47 | - Fixed: https://github.com/tarantool/mqtt/issues/11 48 | - Fixed: https://github.com/tarantool/mqtt/issues/9 49 | 50 | * Sun Apr 2 2017 V. Soshnikov 1.1.0 51 | - Update requires 52 | 53 | * Mon Sep 26 2016 Konstantin Nazarov 1.0.0-1 54 | - Initial version of the RPM spec 55 | -------------------------------------------------------------------------------- /test/api.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tarantool 2 | 3 | -- Those lines of code are for debug purposes only 4 | -- So you have to ignore them 5 | -- {{ 6 | package.preload['mqtt.driver'] = './mqtt/driver.so' 7 | -- }} 8 | 9 | box.cfg {wal_mode = 'none'} 10 | 11 | local os = require('os') 12 | local mqtt = require('mqtt') 13 | local yaml = require('yaml') 14 | local fiber = require('fiber') 15 | 16 | local function F(...) 17 | local desc, ok, emsg = ... 18 | if not ok then 19 | error(string.format("%s failed. Message = %s", desc, emsg)) 20 | end 21 | return ... 22 | end 23 | 24 | local function E(...) 25 | local desc, ok, emsg = ... 26 | if ok then 27 | error(string.format("%s it should be failed. Message = %s", desc, emsg)) 28 | end 29 | return ... 30 | end 31 | 32 | local stat = { } 33 | local function stat_inc(key) 34 | if stat[key] == nil then 35 | stat[key] = 0 36 | end 37 | stat[key] = stat[key] + 1 38 | end 39 | 40 | local conn = mqtt.new() 41 | 42 | assert(type(conn.VERSION) == 'string') 43 | 44 | F('on_connect', conn:on_connect(function(success, ret_code, message) 45 | if not success then 46 | print("can't connect msg = %s, ret_code = %d", message, ret_code) 47 | os.exit(1) 48 | end 49 | stat_inc('on_connect') 50 | end)) 51 | 52 | F('on_publish', conn:on_publish(function(mid) 53 | stat_inc('on_publish') 54 | end)) 55 | 56 | F('on_subscribe', conn:on_subscribe(function(mid, QoS) 57 | stat_inc('on_subscribe') 58 | end)) 59 | 60 | F('on_unsubscribe', conn:on_unsubscribe(function(mid) 61 | stat_inc('on_unsubscribe') 62 | end)) 63 | 64 | F('on_disconnect', conn:on_disconnect( 65 | function(success, ret_code, message) 66 | if not success then 67 | print("can't connect msg = %s, ret_code = %d", message, ret_code) 68 | os.exit(1) 69 | end 70 | stat_inc('on_disconnect') 71 | end 72 | )) 73 | 74 | F('on_message', conn:on_message( 75 | function(mid, topic, payload, qos, retain) 76 | stat_inc('on_message') 77 | stat[topic .. '_' .. payload] = {mid=mid} 78 | end 79 | )) 80 | 81 | -- https://github.com/tarantool/mqtt/issues/6 [[ 82 | E('connect', conn:connect({port=1889})) 83 | E('connect', conn:connect({host="www.does.not.exist"})) 84 | E('connect', conn:connect({host="127.0.0.1", port=1889})) 85 | E('publish', conn:publish('channel/in_1', 'data_1', 0, false)) 86 | E('subscribe', conn:subscribe('channel/in_1')) 87 | E('reconnect', conn:reconnect()) 88 | -- ]] 89 | 90 | -- Here is setup for defaul Rabbitmq MQTT provider. 91 | -- (!) Except. login_set [[ 92 | F('tls_insecure_set', conn:tls_insecure_set(false)) 93 | F('connect', conn:connect({host="127.0.0.1", port=1883})) 94 | F('login_set', conn:login_set('user', 'password')) 95 | -- ]] 96 | 97 | F('reconnect', conn:reconnect()) 98 | 99 | F('subscribe', conn:subscribe('some/topic')) 100 | F('will_set', conn:will_set('some/topic', 'str', 0, false)) 101 | F('will_clear', conn:will_clear()) 102 | F('unsubscribe', conn:unsubscribe('some/topic')) 103 | 104 | F('subscribe', conn:subscribe('channel/#')) 105 | F('publish', conn:publish('channel/in_1', 'data_1', 0, false)) 106 | F('publish', conn:publish('channel/in_1', 'data_2', 0, false)) 107 | F('publish', conn:publish('channel/in_2', 'data_1', 0, false)) 108 | F('publish', conn:publish('channel/in_2', 'data_2', 0, false)) 109 | F('unsubscribe', conn:unsubscribe('channel/#')) 110 | 111 | -- Join 112 | print ("[+] Join result") 113 | fiber.sleep(2.0) 114 | 115 | assert(stat.on_message == stat.on_publish) 116 | assert(stat.on_connect == 1) 117 | assert(stat.on_subscribe == 2) 118 | assert(stat['channel/in_1_data_1'] ~= nil) 119 | assert(stat['channel/in_1_data_2'] ~= nil) 120 | assert(stat['channel/in_2_data_1'] ~= nil) 121 | assert(stat['channel/in_2_data_2'] ~= nil) 122 | 123 | print ("[+] Done") 124 | 125 | F('destroy', conn:destroy()) 126 | E('destroy', conn:destroy()) 127 | 128 | os.exit(0) 129 | -------------------------------------------------------------------------------- /test/pub_sub.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tarantool 2 | 3 | -- Those lines of code are for debug purposes only 4 | -- So you have to ignore them 5 | -- {{ 6 | package.preload['mqtt.driver'] = './mqtt/driver.so' 7 | -- }} 8 | 9 | box.cfg{wal_mode = 'none'} 10 | 11 | local mqtt = require('mqtt') 12 | local fiber = require('fiber') 13 | local yaml = require('yaml') 14 | local os = require('os') 15 | local log = require('log') 16 | 17 | fan_in = box.schema.space.create('fan_in', {if_not_exists = true}) 18 | fan_in:create_index('id') 19 | 20 | conn = mqtt.new() 21 | 22 | function die(msg) 23 | error('[-] ' .. msg) 24 | os.exit(1) 25 | end 26 | 27 | function W(ok, msg) 28 | if not ok then 29 | die(msg) 30 | end 31 | return ok, msg 32 | end 33 | 34 | function W2(ok, msg) 35 | if not ok then 36 | log.error(msg) 37 | end 38 | return ok, msg 39 | end 40 | 41 | local TIME = '' .. fiber.time() 42 | 43 | W(conn:on_message( 44 | function(mid, topic, payload, gos, retain) 45 | W2(conn:unsubscribe('channel2/messages')) 46 | fan_in:auto_increment{topic, payload, gos, retain} 47 | W2(conn:publish('channel/messages', TIME)) 48 | end)) 49 | 50 | W(conn:connect({host="127.0.0.1", port=1883})) 51 | W(conn:subscribe('channel/#')) 52 | W(conn:subscribe('channel2/#')) 53 | 54 | print('[+] Starting tnt loop ...') 55 | 56 | W(conn:publish('channel2/messages', TIME, 0, false)) 57 | 58 | fiber.create(function() 59 | while true do 60 | 61 | if fan_in:len() > 100 then 62 | for k, v in pairs(fan_in:select{}) do 63 | 64 | if v[2] ~= 'channel/messages' and v[2] ~= 'channel2/messages' then 65 | die('expected channel{2}/messages, got ' .. v[2]) 66 | end 67 | if v[3] ~= TIME then 68 | die('expected ' .. time .. ', got ' .. v[3]) 69 | end 70 | if v[4] ~= 0 then 71 | die('expected 0, got ' .. v[4]) 72 | end 73 | if v[5] ~= false then 74 | die('expected false, got ' .. v[5]) 75 | end 76 | --os.exit(0) 77 | print('[+] Test -- OK', yaml.encode(v)) 78 | fan_in:delete{v[1]} 79 | end 80 | os.exit(0) 81 | end 82 | fiber.sleep(0.1) 83 | end 84 | end) 85 | -------------------------------------------------------------------------------- /test/pub_sub_2.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tarantool 2 | 3 | -- Those lines of code are for debug purposes only 4 | -- So you have to ignore them 5 | -- {{ 6 | package.preload['mqtt.driver'] = './mqtt/driver.so' 7 | -- }} 8 | 9 | local os = require('os') 10 | local mqtt = require('mqtt') 11 | local yaml = require('yaml') 12 | local fiber = require('fiber') 13 | 14 | local function F(...) 15 | local desc, ok, emsg = ... 16 | if not ok then 17 | error(string.format("%s failed. Message = %s", desc, emsg)) 18 | end 19 | return ... 20 | end 21 | 22 | local stat = { 23 | on_subscribe = 0, 24 | on_message = 0, 25 | pulish_called = 0, 26 | published = 0 27 | } 28 | 29 | -- Subscriber 30 | fiber.create(function() 31 | local sub = mqtt.new() 32 | 33 | F(sub:on_subscribe(function(mid, QoS) 34 | stat.on_subscribe = stat.on_subscribe + 1 35 | end)) 36 | 37 | F(sub:on_message(function(mid, topic, payload, qos, retain) 38 | stat.on_message = stat.on_message + 1 39 | stat[topic .. '_' .. payload] = {mid=mid} 40 | end)) 41 | 42 | F(sub:tls_insecure_set(false)) 43 | F(sub:login_set('guest', 'guest')) 44 | F('connect', sub:connect({host="127.0.0.1", port=1883})) 45 | F(sub:subscribe('/')) 46 | 47 | -- Join 48 | fiber.sleep(5.0) 49 | 50 | print('Subscriber', yaml.encode(stat)) 51 | assert(stat.on_subscribe == 1) 52 | assert(stat.published == stat.pulish_called) 53 | assert(stat.on_message == 1000) 54 | os.exit(0) 55 | end) 56 | 57 | -- Publisher 58 | local conn = mqtt.new() 59 | F(conn:on_publish(function(mid) 60 | stat.published = stat.published + 1 61 | end)) 62 | F(conn:tls_insecure_set(false)) 63 | F(conn:login_set('guest', 'guest')) 64 | F('connect', conn:connect({host="127.0.0.1", port=1883})) 65 | for i = 1, 1000 do 66 | F(conn:publish('/', 'data_1', 0, false)) 67 | stat.pulish_called = stat.pulish_called + 1 68 | end 69 | -------------------------------------------------------------------------------- /test/run_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -x 4 | 5 | mosquitto & 6 | for test_case in `ls $PWD/test | grep -e '$*\.lua'`; do 7 | $PWD/test/$test_case 8 | echo "[+] Case: $test_case -- OK" 9 | done 10 | kill -s TERM %1 || echo "it's ok" 11 | echo "[+] Done" 12 | --------------------------------------------------------------------------------