From b30001429d89240b4f76d50200848e5b2025c03a Mon Sep 17 00:00:00 2001 From: Noel Graf Date: Tue, 14 Jun 2022 09:38:16 +0200 Subject: [PATCH] fix(pubsub): Integrate the mqtt-c library as a Git submodule in 1.3 --- .gitmodules | 4 + CMakeLists.txt | 61 +- deps/mqtt-c | 1 + deps/mqtt-c/mqtt.c | 1846 ----------------- deps/mqtt-c/mqtt.h | 1590 -------------- deps/mqtt-c/mqtt_pal.h | 115 - include/open62541/config.h.in | 1 + .../open62541/plugin/pubsub_mqtt.h} | 16 +- plugins/mqtt/templates/mbedtls_sockets.h | 159 ++ plugins/mqtt/templates/openssl_sockets.h | 102 + plugins/mqtt/templates/posix_sockets.h | 86 + ...{ua_mqtt_adapter.c => ua_mqtt-c_adapter.c} | 448 ++-- ...{ua_mqtt_adapter.h => ua_mqtt-c_adapter.h} | 2 +- plugins/mqtt/ua_mqtt_pal.c | 108 - ...network_pubsub_mqtt.c => ua_pubsub_mqtt.c} | 10 +- src/pubsub/ua_pubsub_networkmessage.c | 14 +- tests/CMakeLists.txt | 16 +- tests/pubsub/check_pubsub_connection_mqtt.c | 7 +- 18 files changed, 563 insertions(+), 4023 deletions(-) create mode 160000 deps/mqtt-c delete mode 100644 deps/mqtt-c/mqtt.c delete mode 100644 deps/mqtt-c/mqtt.h delete mode 100644 deps/mqtt-c/mqtt_pal.h rename plugins/{ua_network_pubsub_mqtt.h => include/open62541/plugin/pubsub_mqtt.h} (78%) create mode 100644 plugins/mqtt/templates/mbedtls_sockets.h create mode 100644 plugins/mqtt/templates/openssl_sockets.h create mode 100644 plugins/mqtt/templates/posix_sockets.h rename plugins/mqtt/{ua_mqtt_adapter.c => ua_mqtt-c_adapter.c} (52%) rename plugins/mqtt/{ua_mqtt_adapter.h => ua_mqtt-c_adapter.h} (95%) delete mode 100644 plugins/mqtt/ua_mqtt_pal.c rename plugins/{ua_network_pubsub_mqtt.c => ua_pubsub_mqtt.c} (97%) diff --git a/.gitmodules b/.gitmodules index 99bd061cc..c4b2135f8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,7 @@ path = deps/ua-nodeset url = https://github.com/OPCFoundation/UA-Nodeset branch = v1.04 +[submodule "deps/mqtt-c"] + path = deps/mqtt-c + url = https://github.com/LiamBindle/MQTT-C.git + branch = master diff --git a/CMakeLists.txt b/CMakeLists.txt index 859e77dce..49433886a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ include(GNUInstallDirs) # Set when installed via make install set(open62541_TOOLS_DIR ${PROJECT_SOURCE_DIR}/tools) set(open62541_NODESET_DIR ${PROJECT_SOURCE_DIR}/deps/ua-nodeset) +set(open62541_MQTT_DIR ${PROJECT_SOURCE_DIR}/deps/mqtt-c) include(macros_internal) include(macros_public) @@ -416,29 +417,49 @@ endif() option(UA_ENABLE_PUBSUB_MQTT "Enable publish/subscribe with mqtt (experimental)" OFF) mark_as_advanced(UA_ENABLE_PUBSUB_MQTT) if(UA_ENABLE_PUBSUB_MQTT) - # Warn if run in Windows - if(WIN32) - MESSAGE(WARNING "Multithreading is enabled in MQTT plugin. This feature is under development and marked as EXPERIMENTAL") + if(NOT UA_FILE_MQTT) + set(UA_FILE_MQTT ${open62541_MQTT_DIR}/src/mqtt.c) endif() - if(NOT UA_ENABLE_PUBSUB) - message(FATAL_ERROR "Mqtt cannot be used with disabled PubSub function.") + if(NOT EXISTS "${UA_FILE_MQTT}") + message(FATAL_ERROR "File ${UA_FILE_MQTT} not found.You probably need to initialize the git submodule for deps/mqtt-c.Therefore execute the following command \ngit submodule update --init --recursive") + else() + # Warn if run in Windows + if(WIN32) + MESSAGE(WARNING "Multithreading is enabled in MQTT plugin. This feature is under development and marked as EXPERIMENTAL") + endif() + if(NOT UA_ENABLE_PUBSUB) + message(FATAL_ERROR "Mqtt cannot be used with disabled PubSub function.") + endif() + configure_file(${PROJECT_SOURCE_DIR}/deps/mqtt-c/include/mqtt.h "${PROJECT_BINARY_DIR}/src_generated/mqtt.h" COPYONLY) + configure_file(${PROJECT_SOURCE_DIR}/deps/mqtt-c/include/mqtt_pal.h "${PROJECT_BINARY_DIR}/src_generated/mqtt_pal.h" COPYONLY) + configure_file(${PROJECT_SOURCE_DIR}/plugins/mqtt/templates/posix_sockets.h "${PROJECT_BINARY_DIR}/src_generated/posix_sockets.h" COPYONLY) endif() endif() option(UA_ENABLE_MQTT_TLS_OPENSSL "Enable TLS support for publish/subscribe with mqtt (use OpenSSL)" OFF) mark_as_advanced(UA_ENABLE_MQTT_TLS_OPENSSL) +option(UA_ENABLE_MQTT_TLS_MBEDTLS "Enable TLS support for publish/subscribe with mqtt (use MBEDTLS)" OFF) +mark_as_advanced(UA_ENABLE_MQTT_TLS_MBEDTLS) option(UA_ENABLE_MQTT_TLS "Enable TLS support for publish/subscribe with mqtt" OFF) mark_as_advanced(UA_ENABLE_MQTT_TLS) if(UA_ENABLE_MQTT_TLS) - if(NOT UA_ENABLE_PUBSUB_MQTT) - message(FATAL_ERROR "Mqtt with TLS cannot be used without UA_ENABLE_PUBSUB_MQTT.") - endif() + if(NOT UA_ENABLE_PUBSUB_MQTT) + message(FATAL_ERROR "Mqtt with TLS cannot be used without UA_ENABLE_PUBSUB_MQTT.") + endif() + if(NOT UA_ENABLE_ENCRYPTION) + message(FATAL_ERROR "Mqtt with TLS cannot be used without UA_ENABLE_ENCRYPTION.") + elseif(UA_ENABLE_ENCRYPTION STREQUAL "OPENSSL") set(UA_ENABLE_MQTT_TLS_OPENSSL ON) -endif() - -if(UA_ENABLE_MQTT_TLS_OPENSSL) - set(UA_ENABLE_MQTT_TLS ON) + add_definitions(-DMQTT_USE_BIO) + configure_file(${PROJECT_SOURCE_DIR}/plugins/mqtt/templates/openssl_sockets.h "${PROJECT_BINARY_DIR}/src_generated/openssl_sockets.h" COPYONLY) + elseif(UA_ENABLE_ENCRYPTION STREQUAL "MBEDTLS") + set(UA_ENABLE_MQTT_TLS_MBEDTLS ON) + add_definitions(-DMQTT_USE_MBEDTLS) + configure_file(${PROJECT_SOURCE_DIR}/plugins/mqtt/templates/mbedtls_sockets.h "${PROJECT_BINARY_DIR}/src_generated/mbedtls_sockets.h" COPYONLY) + else() + message(FATAL_ERROR "UA_ENABLE_ENCRYPTION is set to an undefined value") + endif() endif() option(UA_ENABLE_STATUSCODE_DESCRIPTIONS "Enable conversion of StatusCode to human-readable error message" ON) @@ -1076,14 +1097,14 @@ if(UA_ENABLE_PUBSUB) if(WIN32) MESSAGE(WARNING "Multithreading is enabled in MQTT plugin. This feature is under development and marked as EXPERIMENTAL") else() - list(APPEND default_plugin_headers ${PROJECT_SOURCE_DIR}/deps/mqtt-c/mqtt_pal.h - ${PROJECT_SOURCE_DIR}/deps/mqtt-c/mqtt.h - ${PROJECT_SOURCE_DIR}/plugins/ua_network_pubsub_mqtt.h - ${PROJECT_SOURCE_DIR}/plugins/mqtt/ua_mqtt_adapter.h) - list(APPEND default_plugin_sources ${PROJECT_SOURCE_DIR}/plugins/mqtt/ua_mqtt_pal.c - ${PROJECT_SOURCE_DIR}/deps/mqtt-c/mqtt.c - ${PROJECT_SOURCE_DIR}/plugins/ua_network_pubsub_mqtt.c - ${PROJECT_SOURCE_DIR}/plugins/mqtt/ua_mqtt_adapter.c) + list(APPEND default_plugin_headers ${PROJECT_SOURCE_DIR}/deps/mqtt-c/include/mqtt_pal.h + ${PROJECT_SOURCE_DIR}/deps/mqtt-c/include/mqtt.h + ${PROJECT_SOURCE_DIR}/plugins/include/open62541/plugin/pubsub_mqtt.h + ${PROJECT_SOURCE_DIR}/plugins/mqtt/ua_mqtt-c_adapter.h) + list(APPEND default_plugin_sources ${PROJECT_SOURCE_DIR}/deps/mqtt-c/src/mqtt_pal.c + ${PROJECT_SOURCE_DIR}/deps/mqtt-c/src/mqtt.c + ${PROJECT_SOURCE_DIR}/plugins/ua_pubsub_mqtt.c + ${PROJECT_SOURCE_DIR}/plugins/mqtt/ua_mqtt-c_adapter.c) endif() endif() if(UA_ENABLE_MALLOC_SINGLETON AND UA_ENABLE_PUBSUB_BUFMALLOC) diff --git a/deps/mqtt-c b/deps/mqtt-c new file mode 160000 index 000000000..f69ce1e7f --- /dev/null +++ b/deps/mqtt-c @@ -0,0 +1 @@ +Subproject commit f69ce1e7fd54f3b1834c9c9137ce0ec5d703cb4d diff --git a/deps/mqtt-c/mqtt.c b/deps/mqtt-c/mqtt.c deleted file mode 100644 index 62324d048..000000000 --- a/deps/mqtt-c/mqtt.c +++ /dev/null @@ -1,1846 +0,0 @@ -/* -MIT License - -Copyright(c) 2018 Liam Bindle - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files(the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions : - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -#include "mqtt.h" - -/** - * @file - * @brief Implements the functionality of MQTT-C. - * @note The only files that are included are mqtt.h and mqtt_pal.h. - * - * @cond Doxygen_Suppress - */ - -enum MQTTErrors mqtt_sync(struct mqtt_client *client) { - /* Recover from any errors */ - enum MQTTErrors err; - MQTT_PAL_MUTEX_LOCK(&client->mutex); - if (client->error != MQTT_OK && client->reconnect_callback != NULL) { - client->reconnect_callback(client, &client->reconnect_state); - /* unlocked during CONNECT */ - } else { - MQTT_PAL_MUTEX_UNLOCK(&client->mutex); - } - - /* Call inspector callback if necessary */ - - if (client->inspector_callback != NULL) { - MQTT_PAL_MUTEX_LOCK(&client->mutex); - err = client->inspector_callback(client); - MQTT_PAL_MUTEX_UNLOCK(&client->mutex); - if (err != MQTT_OK) return err; - } - - /* Call receive */ - err = (enum MQTTErrors)__mqtt_recv(client); - if (err != MQTT_OK) return err; - - /* Call send */ - err = (enum MQTTErrors)__mqtt_send(client); - return err; -} - -uint16_t __mqtt_next_pid(struct mqtt_client *client) { - int pid_exists = 0; - if (client->pid_lfsr == 0) { - client->pid_lfsr = 163u; - } - /* LFSR taps taken from: https://en.wikipedia.org/wiki/Linear-feedback_shift_register */ - - do { - struct mqtt_queued_message *curr; - unsigned lsb = client->pid_lfsr & 1; - (client->pid_lfsr) >>= 1; - if (lsb) { - client->pid_lfsr ^= 0xB400u; - } - - - /* check that the PID is unique */ - pid_exists = 0; - for(curr = mqtt_mq_get(&(client->mq), 0); curr >= client->mq.queue_tail; --curr) { - if (curr->packet_id == client->pid_lfsr) { - pid_exists = 1; - break; - } - } - - } while(pid_exists); - return client->pid_lfsr; -} - -enum MQTTErrors mqtt_init(struct mqtt_client *client, - mqtt_pal_socket_handle sockfd, - uint8_t *sendbuf, size_t sendbufsz, - uint8_t *recvbuf, size_t recvbufsz, - void (*publish_response_callback)(void** state,struct mqtt_response_publish *publish)) -{ - if (client == NULL || sendbuf == NULL || recvbuf == NULL) { - return MQTT_ERROR_NULLPTR; - } - - /* initialize mutex */ - MQTT_PAL_MUTEX_INIT(&client->mutex); - MQTT_PAL_MUTEX_LOCK(&client->mutex); /* unlocked during CONNECT */ - - client->socketfd = sockfd; - - mqtt_mq_init(&client->mq, sendbuf, sendbufsz); - - client->recv_buffer.mem_start = recvbuf; - client->recv_buffer.mem_size = recvbufsz; - client->recv_buffer.curr = client->recv_buffer.mem_start; - client->recv_buffer.curr_sz = client->recv_buffer.mem_size; - - client->error = MQTT_ERROR_CONNECT_NOT_CALLED; - client->response_timeout = 30; - client->number_of_timeouts = 0; - client->number_of_keep_alives = 0; - client->typical_response_time = -1.0; - client->publish_response_callback = publish_response_callback; - client->pid_lfsr = 0; - client->send_offset = 0; - - client->inspector_callback = NULL; - client->reconnect_callback = NULL; - client->reconnect_state = NULL; - - return MQTT_OK; -} - -void mqtt_init_reconnect(struct mqtt_client *client, - void (*reconnect)(struct mqtt_client *, void**), - void *reconnect_state, - void (*publish_response_callback)(void** state, struct mqtt_response_publish *publish)) -{ - /* initialize mutex */ - MQTT_PAL_MUTEX_INIT(&client->mutex); - - client->socketfd = (mqtt_pal_socket_handle) -1; - - mqtt_mq_init(&client->mq, NULL, 0); - - client->recv_buffer.mem_start = NULL; - client->recv_buffer.mem_size = 0; - client->recv_buffer.curr = NULL; - client->recv_buffer.curr_sz = 0; - - client->error = MQTT_ERROR_INITIAL_RECONNECT; - client->response_timeout = 30; - client->number_of_timeouts = 0; - client->number_of_keep_alives = 0; - client->typical_response_time = -1.0; - client->publish_response_callback = publish_response_callback; - client->send_offset = 0; - - client->inspector_callback = NULL; - client->reconnect_callback = reconnect; - client->reconnect_state = reconnect_state; -} - -void mqtt_reinit(struct mqtt_client* client, - mqtt_pal_socket_handle socketfd, - uint8_t *sendbuf, size_t sendbufsz, - uint8_t *recvbuf, size_t recvbufsz) -{ - client->error = MQTT_ERROR_CONNECT_NOT_CALLED; - client->socketfd = socketfd; - - mqtt_mq_init(&client->mq, sendbuf, sendbufsz); - - client->recv_buffer.mem_start = recvbuf; - client->recv_buffer.mem_size = recvbufsz; - client->recv_buffer.curr = client->recv_buffer.mem_start; - client->recv_buffer.curr_sz = client->recv_buffer.mem_size; -} - -/** - * A macro function that: - * 1) Checks that the client isn't in an error state. - * 2) Attempts to pack to client's message queue. - * a) handles errors - * b) if mq buffer is too small, cleans it and tries again - * 3) Upon successful pack, registers the new message. - */ -#define MQTT_CLIENT_TRY_PACK(tmp, msg, client, pack_call, release) \ - if (client->error < 0) { \ - if (release) MQTT_PAL_MUTEX_UNLOCK(&client->mutex); \ - return client->error; \ - } \ - tmp = pack_call; \ - if (tmp < 0) { \ - client->error = (enum MQTTErrors)tmp; \ - if (release) MQTT_PAL_MUTEX_UNLOCK(&client->mutex); \ - return (enum MQTTErrors)tmp; \ - } else if (tmp == 0) { \ - mqtt_mq_clean(&client->mq); \ - tmp = pack_call; \ - if (tmp < 0) { \ - client->error = (enum MQTTErrors)tmp; \ - if (release) MQTT_PAL_MUTEX_UNLOCK(&client->mutex); \ - return (enum MQTTErrors)tmp; \ - } else if(tmp == 0) { \ - client->error = MQTT_ERROR_SEND_BUFFER_IS_FULL; \ - if (release) MQTT_PAL_MUTEX_UNLOCK(&client->mutex); \ - return (enum MQTTErrors)MQTT_ERROR_SEND_BUFFER_IS_FULL; \ - } \ - } \ - msg = mqtt_mq_register(&client->mq, (size_t)tmp); \ - - -enum MQTTErrors mqtt_connect(struct mqtt_client *client, - const char* client_id, - const char* will_topic, - const void* will_message, - size_t will_message_size, - const char* user_name, - const char* password, -#ifdef UA_ENABLE_MQTT_TLS_OPENSSL - const char* caFilePath, - const char* caPath, - const char* clientCertPath, - const char* clientKeyPath, - bool useTLS, -#endif - uint8_t connect_flags, - uint16_t keep_alive) -{ - ssize_t rv; - struct mqtt_queued_message *msg; - - /* Note: Current thread already has mutex locked. */ - - /* update the client's state */ - client->keep_alive = keep_alive; - if (client->error == MQTT_ERROR_CONNECT_NOT_CALLED) { - client->error = MQTT_OK; - } - -#ifdef UA_ENABLE_MQTT_TLS_OPENSSL - /* try to pack the message */ - MQTT_CLIENT_TRY_PACK(rv, msg, client, - mqtt_pack_connection_request(client->mq.curr, client->mq.curr_sz, - client_id, will_topic, will_message, - will_message_size,user_name, password, - caFilePath, caPath, - clientCertPath, clientKeyPath, - useTLS, - connect_flags, keep_alive), 1); -#else - /* try to pack the message */ - MQTT_CLIENT_TRY_PACK(rv, msg, client, - mqtt_pack_connection_request(client->mq.curr, client->mq.curr_sz, - client_id, will_topic, will_message, - will_message_size,user_name, password, - connect_flags, keep_alive), 1); -#endif - - /* save the control type of the message */ - msg->control_type = MQTT_CONTROL_CONNECT; - - MQTT_PAL_MUTEX_UNLOCK(&client->mutex); - return MQTT_OK; -} - -enum MQTTErrors mqtt_publish(struct mqtt_client *client, - const char* topic_name, - void* application_message, - size_t application_message_size, - uint8_t publish_flags) -{ - struct mqtt_queued_message *msg; - ssize_t rv; - uint16_t packet_id; - MQTT_PAL_MUTEX_LOCK(&client->mutex); - packet_id = __mqtt_next_pid(client); - - - /* try to pack the message */ - MQTT_CLIENT_TRY_PACK( - rv, msg, client, - mqtt_pack_publish_request( - client->mq.curr, client->mq.curr_sz, - topic_name, - packet_id, - application_message, - application_message_size, - publish_flags - ), - 1 - ); - /* save the control type and packet id of the message */ - msg->control_type = MQTT_CONTROL_PUBLISH; - msg->packet_id = packet_id; - - MQTT_PAL_MUTEX_UNLOCK(&client->mutex); - return MQTT_OK; -} - -ssize_t __mqtt_puback(struct mqtt_client *client, uint16_t packet_id) { - ssize_t rv; - struct mqtt_queued_message *msg; - - /* try to pack the message */ - MQTT_CLIENT_TRY_PACK( - rv, msg, client, - mqtt_pack_pubxxx_request( - client->mq.curr, client->mq.curr_sz, - MQTT_CONTROL_PUBACK, - packet_id - ), - 0 - ); - /* save the control type and packet id of the message */ - msg->control_type = MQTT_CONTROL_PUBACK; - msg->packet_id = packet_id; - - return MQTT_OK; -} - -ssize_t __mqtt_pubrec(struct mqtt_client *client, uint16_t packet_id) { - ssize_t rv; - struct mqtt_queued_message *msg; - - /* try to pack the message */ - MQTT_CLIENT_TRY_PACK( - rv, msg, client, - mqtt_pack_pubxxx_request( - client->mq.curr, client->mq.curr_sz, - MQTT_CONTROL_PUBREC, - packet_id - ), - 0 - ); - /* save the control type and packet id of the message */ - msg->control_type = MQTT_CONTROL_PUBREC; - msg->packet_id = packet_id; - - return MQTT_OK; -} - -ssize_t __mqtt_pubrel(struct mqtt_client *client, uint16_t packet_id) { - ssize_t rv; - struct mqtt_queued_message *msg; - - /* try to pack the message */ - MQTT_CLIENT_TRY_PACK( - rv, msg, client, - mqtt_pack_pubxxx_request( - client->mq.curr, client->mq.curr_sz, - MQTT_CONTROL_PUBREL, - packet_id - ), - 0 - ); - /* save the control type and packet id of the message */ - msg->control_type = MQTT_CONTROL_PUBREL; - msg->packet_id = packet_id; - - return MQTT_OK; -} - -ssize_t __mqtt_pubcomp(struct mqtt_client *client, uint16_t packet_id) { - ssize_t rv; - struct mqtt_queued_message *msg; - - /* try to pack the message */ - MQTT_CLIENT_TRY_PACK( - rv, msg, client, - mqtt_pack_pubxxx_request( - client->mq.curr, client->mq.curr_sz, - MQTT_CONTROL_PUBCOMP, - packet_id - ), - 0 - ); - /* save the control type and packet id of the message */ - msg->control_type = MQTT_CONTROL_PUBCOMP; - msg->packet_id = packet_id; - - return MQTT_OK; -} - -enum MQTTErrors mqtt_subscribe(struct mqtt_client *client, - const char* topic_name, - int max_qos_level) -{ - ssize_t rv; - uint16_t packet_id; - struct mqtt_queued_message *msg; - MQTT_PAL_MUTEX_LOCK(&client->mutex); - packet_id = __mqtt_next_pid(client); - - /* try to pack the message */ - MQTT_CLIENT_TRY_PACK( - rv, msg, client, - mqtt_pack_subscribe_request( - client->mq.curr, client->mq.curr_sz, - packet_id, - topic_name, - max_qos_level, - (const char*)NULL - ), - 1 - ); - /* save the control type and packet id of the message */ - msg->control_type = MQTT_CONTROL_SUBSCRIBE; - msg->packet_id = packet_id; - - MQTT_PAL_MUTEX_UNLOCK(&client->mutex); - return MQTT_OK; -} - -enum MQTTErrors mqtt_unsubscribe(struct mqtt_client *client, - const char* topic_name) -{ - uint16_t packet_id = __mqtt_next_pid(client); - ssize_t rv; - struct mqtt_queued_message *msg; - MQTT_PAL_MUTEX_LOCK(&client->mutex); - - /* try to pack the message */ - MQTT_CLIENT_TRY_PACK( - rv, msg, client, - mqtt_pack_unsubscribe_request( - client->mq.curr, client->mq.curr_sz, - packet_id, - topic_name, - (const char*)NULL - ), - 1 - ); - /* save the control type and packet id of the message */ - msg->control_type = MQTT_CONTROL_UNSUBSCRIBE; - msg->packet_id = packet_id; - - MQTT_PAL_MUTEX_UNLOCK(&client->mutex); - return MQTT_OK; -} - -enum MQTTErrors mqtt_ping(struct mqtt_client *client) { - enum MQTTErrors rv; - MQTT_PAL_MUTEX_LOCK(&client->mutex); - rv = __mqtt_ping(client); - MQTT_PAL_MUTEX_UNLOCK(&client->mutex); - return rv; -} - -enum MQTTErrors __mqtt_ping(struct mqtt_client *client) -{ - ssize_t rv; - struct mqtt_queued_message *msg; - - /* try to pack the message */ - MQTT_CLIENT_TRY_PACK( - rv, msg, client, - mqtt_pack_ping_request( - client->mq.curr, client->mq.curr_sz - ), - 0 - ); - /* save the control type and packet id of the message */ - msg->control_type = MQTT_CONTROL_PINGREQ; - - - return MQTT_OK; -} - -enum MQTTErrors mqtt_disconnect(struct mqtt_client *client) -{ - ssize_t rv; - struct mqtt_queued_message *msg; - MQTT_PAL_MUTEX_LOCK(&client->mutex); - - /* try to pack the message */ - MQTT_CLIENT_TRY_PACK( - rv, msg, client, - mqtt_pack_disconnect( - client->mq.curr, client->mq.curr_sz - ), - 1 - ); - /* save the control type and packet id of the message */ - msg->control_type = MQTT_CONTROL_DISCONNECT; - - MQTT_PAL_MUTEX_UNLOCK(&client->mutex); - return MQTT_OK; -} - -ssize_t __mqtt_send(struct mqtt_client *client) -{ - uint8_t inspected; - ssize_t len; - int inflight_qos2 = 0; - int i = 0; - - MQTT_PAL_MUTEX_LOCK(&client->mutex); - - if (client->error < 0 && client->error != MQTT_ERROR_SEND_BUFFER_IS_FULL) { - MQTT_PAL_MUTEX_UNLOCK(&client->mutex); - return client->error; - } - - /* loop through all messages in the queue */ - len = mqtt_mq_length(&client->mq); - for(; i < len; ++i) { - struct mqtt_queued_message *msg = mqtt_mq_get(&client->mq, i); - int resend = 0; - if (msg->state == MQTT_QUEUED_UNSENT) { - /* message has not been sent to lets send it */ - resend = 1; - } else if (msg->state == MQTT_QUEUED_AWAITING_ACK) { - /* check for timeout */ - if (MQTT_PAL_TIME() > msg->time_sent + client->response_timeout) { - resend = 1; - client->number_of_timeouts += 1; - client->send_offset = 0; - } - } - - /* only send QoS 2 message if there are no inflight QoS 2 PUBLISH messages */ - if (msg->control_type == MQTT_CONTROL_PUBLISH - && (msg->state == MQTT_QUEUED_UNSENT || msg->state == MQTT_QUEUED_AWAITING_ACK)) - { - inspected = 0x03 & ((msg->start[0]) >> 1); /* qos */ - if (inspected == 2) { - if (inflight_qos2) { - resend = 0; - } - inflight_qos2 = 1; - } - } - - /* goto next message if we don't need to send */ - if (!resend) { - continue; - } - - /* we're sending the message */ - { - ssize_t tmp = mqtt_pal_sendall(client->socketfd, msg->start + client->send_offset, msg->size - client->send_offset, 0); - if (tmp < 0) { - client->error = (enum MQTTErrors)tmp; - MQTT_PAL_MUTEX_UNLOCK(&client->mutex); - return tmp; - } else { - client->send_offset += (unsigned long)tmp; - if(client->send_offset < msg->size) { - /* partial sent. Await additional calls */ - break; - } else { - /* whole message has been sent */ - client->send_offset = 0; - } - - } - - } - - /* update timeout watcher */ - client->time_of_last_send = MQTT_PAL_TIME(); - msg->time_sent = client->time_of_last_send; - - /* - Determine the state to put the message in. - Control Types: - MQTT_CONTROL_CONNECT -> awaiting - MQTT_CONTROL_CONNACK -> n/a - MQTT_CONTROL_PUBLISH -> qos == 0 ? complete : awaiting - MQTT_CONTROL_PUBACK -> complete - MQTT_CONTROL_PUBREC -> awaiting - MQTT_CONTROL_PUBREL -> awaiting - MQTT_CONTROL_PUBCOMP -> complete - MQTT_CONTROL_SUBSCRIBE -> awaiting - MQTT_CONTROL_SUBACK -> n/a - MQTT_CONTROL_UNSUBSCRIBE -> awaiting - MQTT_CONTROL_UNSUBACK -> n/a - MQTT_CONTROL_PINGREQ -> awaiting - MQTT_CONTROL_PINGRESP -> n/a - MQTT_CONTROL_DISCONNECT -> complete - */ - switch (msg->control_type) { - case MQTT_CONTROL_PUBACK: - case MQTT_CONTROL_PUBCOMP: - case MQTT_CONTROL_DISCONNECT: - msg->state = MQTT_QUEUED_COMPLETE; - break; - case MQTT_CONTROL_PUBLISH: - inspected = ( MQTT_PUBLISH_QOS_MASK & (msg->start[0]) ) >> 1; /* qos */ - if (inspected == 0) { - msg->state = MQTT_QUEUED_COMPLETE; - } else if (inspected == 1) { - msg->state = MQTT_QUEUED_AWAITING_ACK; - /*set DUP flag for subsequent sends [Spec MQTT-3.3.1-1] */ - msg->start[0] |= MQTT_PUBLISH_DUP; - } else { - msg->state = MQTT_QUEUED_AWAITING_ACK; - } - break; - case MQTT_CONTROL_CONNECT: - case MQTT_CONTROL_PUBREC: - case MQTT_CONTROL_PUBREL: - case MQTT_CONTROL_SUBSCRIBE: - case MQTT_CONTROL_UNSUBSCRIBE: - case MQTT_CONTROL_PINGREQ: - msg->state = MQTT_QUEUED_AWAITING_ACK; - break; - default: - client->error = MQTT_ERROR_MALFORMED_REQUEST; - MQTT_PAL_MUTEX_UNLOCK(&client->mutex); - return MQTT_ERROR_MALFORMED_REQUEST; - } - } - - /* check for keep-alive */ - { - mqtt_pal_time_t keep_alive_timeout = client->time_of_last_send + (mqtt_pal_time_t)((float)(client->keep_alive) * 0.75); - if (MQTT_PAL_TIME() > keep_alive_timeout) { - ssize_t rv = mqtt_ping(client); - if (rv != MQTT_OK) { - client->error = (enum MQTTErrors)rv; - MQTT_PAL_MUTEX_UNLOCK(&client->mutex); - return rv; - } - } - } - - MQTT_PAL_MUTEX_UNLOCK(&client->mutex); - return MQTT_OK; -} - -ssize_t __mqtt_recv(struct mqtt_client *client) -{ - struct mqtt_response response; - ssize_t mqtt_recv_ret = MQTT_OK; - MQTT_PAL_MUTEX_LOCK(&client->mutex); - - /* read until there is nothing left to read, or there was an error */ - while(mqtt_recv_ret == MQTT_OK) { - /* read in as many bytes as possible */ - ssize_t rv, consumed; - struct mqtt_queued_message *msg = NULL; - - rv = mqtt_pal_recvall(client->socketfd, client->recv_buffer.curr, client->recv_buffer.curr_sz, 0); - if (rv < 0) { - /* an error occurred */ - client->error = (enum MQTTErrors)rv; - MQTT_PAL_MUTEX_UNLOCK(&client->mutex); - return rv; - } else { - client->recv_buffer.curr += rv; - client->recv_buffer.curr_sz -= (unsigned long)rv; - } - - /* attempt to parse */ - consumed = mqtt_unpack_response(&response, client->recv_buffer.mem_start, - (size_t)(client->recv_buffer.curr - client->recv_buffer.mem_start)); - - if (consumed < 0) { - client->error = (enum MQTTErrors)consumed; - MQTT_PAL_MUTEX_UNLOCK(&client->mutex); - return consumed; - } else if (consumed == 0) { - /* if curr_sz is 0 then the buffer is too small to ever fit the message */ - if (client->recv_buffer.curr_sz == 0) { - client->error = MQTT_ERROR_RECV_BUFFER_TOO_SMALL; - MQTT_PAL_MUTEX_UNLOCK(&client->mutex); - return MQTT_ERROR_RECV_BUFFER_TOO_SMALL; - } - - /* just need to wait for the rest of the data */ - MQTT_PAL_MUTEX_UNLOCK(&client->mutex); - return MQTT_OK; - } - - /* response was unpacked successfully */ - - /* - The switch statement below manages how the client responds to messages from the broker. - - Control Types (that we expect to receive from the broker): - MQTT_CONTROL_CONNACK: - -> release associated CONNECT - -> handle response - MQTT_CONTROL_PUBLISH: - -> stage response, none if qos==0, PUBACK if qos==1, PUBREC if qos==2 - -> call publish callback - MQTT_CONTROL_PUBACK: - -> release associated PUBLISH - MQTT_CONTROL_PUBREC: - -> release PUBLISH - -> stage PUBREL - MQTT_CONTROL_PUBREL: - -> release associated PUBREC - -> stage PUBCOMP - MQTT_CONTROL_PUBCOMP: - -> release PUBREL - MQTT_CONTROL_SUBACK: - -> release SUBSCRIBE - -> handle response - MQTT_CONTROL_UNSUBACK: - -> release UNSUBSCRIBE - MQTT_CONTROL_PINGRESP: - -> release PINGREQ - */ - switch (response.fixed_header.control_type) { - case MQTT_CONTROL_CONNACK: - /* release associated CONNECT */ - msg = mqtt_mq_find(&client->mq, MQTT_CONTROL_CONNECT, NULL); - if (msg == NULL) { - client->error = MQTT_ERROR_ACK_OF_UNKNOWN; - mqtt_recv_ret = MQTT_ERROR_ACK_OF_UNKNOWN; - break; - } - msg->state = MQTT_QUEUED_COMPLETE; - /* initialize typical response time */ - client->typical_response_time = (double) (MQTT_PAL_TIME() - msg->time_sent); - /* check that connection was successful */ - if (response.decoded.connack.return_code != MQTT_CONNACK_ACCEPTED) { - if (response.decoded.connack.return_code == MQTT_CONNACK_REFUSED_IDENTIFIER_REJECTED) { - client->error = MQTT_ERROR_CONNECT_CLIENT_ID_REFUSED; - mqtt_recv_ret = MQTT_ERROR_CONNECT_CLIENT_ID_REFUSED; - } else { - client->error = MQTT_ERROR_CONNECTION_REFUSED; - mqtt_recv_ret = MQTT_ERROR_CONNECTION_REFUSED; - } - break; - } - break; - case MQTT_CONTROL_PUBLISH: - /* stage response, none if qos==0, PUBACK if qos==1, PUBREC if qos==2 */ - if (response.decoded.publish.qos_level == 1) { - rv = __mqtt_puback(client, response.decoded.publish.packet_id); - if (rv != MQTT_OK) { - client->error = (enum MQTTErrors)rv; - mqtt_recv_ret = rv; - break; - } - } else if (response.decoded.publish.qos_level == 2) { - /* check if this is a duplicate */ - if (mqtt_mq_find(&client->mq, MQTT_CONTROL_PUBREC, &response.decoded.publish.packet_id) != NULL) { - break; - } - - rv = __mqtt_pubrec(client, response.decoded.publish.packet_id); - if (rv != MQTT_OK) { - client->error = (enum MQTTErrors)rv; - mqtt_recv_ret = rv; - break; - } - } - /* call publish callback */ - client->publish_response_callback(&client->publish_response_callback_state, &response.decoded.publish); - break; - case MQTT_CONTROL_PUBACK: - /* release associated PUBLISH */ - msg = mqtt_mq_find(&client->mq, MQTT_CONTROL_PUBLISH, &response.decoded.puback.packet_id); - if (msg == NULL) { - client->error = MQTT_ERROR_ACK_OF_UNKNOWN; - mqtt_recv_ret = MQTT_ERROR_ACK_OF_UNKNOWN; - break; - } - msg->state = MQTT_QUEUED_COMPLETE; - /* update response time */ - client->typical_response_time = 0.875 * (client->typical_response_time) + 0.125 * (double) (MQTT_PAL_TIME() - msg->time_sent); - break; - case MQTT_CONTROL_PUBREC: - /* check if this is a duplicate */ - if (mqtt_mq_find(&client->mq, MQTT_CONTROL_PUBREL, &response.decoded.pubrec.packet_id) != NULL) { - break; - } - /* release associated PUBLISH */ - msg = mqtt_mq_find(&client->mq, MQTT_CONTROL_PUBLISH, &response.decoded.pubrec.packet_id); - if (msg == NULL) { - client->error = MQTT_ERROR_ACK_OF_UNKNOWN; - mqtt_recv_ret = MQTT_ERROR_ACK_OF_UNKNOWN; - break; - } - msg->state = MQTT_QUEUED_COMPLETE; - /* update response time */ - client->typical_response_time = 0.875 * (client->typical_response_time) + 0.125 * (double) (MQTT_PAL_TIME() - msg->time_sent); - /* stage PUBREL */ - rv = __mqtt_pubrel(client, response.decoded.pubrec.packet_id); - if (rv != MQTT_OK) { - client->error = (enum MQTTErrors)rv; - mqtt_recv_ret = rv; - break; - } - break; - case MQTT_CONTROL_PUBREL: - /* release associated PUBREC */ - msg = mqtt_mq_find(&client->mq, MQTT_CONTROL_PUBREC, &response.decoded.pubrel.packet_id); - if (msg == NULL) { - client->error = MQTT_ERROR_ACK_OF_UNKNOWN; - mqtt_recv_ret = MQTT_ERROR_ACK_OF_UNKNOWN; - break; - } - msg->state = MQTT_QUEUED_COMPLETE; - /* update response time */ - client->typical_response_time = 0.875 * (client->typical_response_time) + 0.125 * (double) (MQTT_PAL_TIME() - msg->time_sent); - /* stage PUBCOMP */ - rv = __mqtt_pubcomp(client, response.decoded.pubrec.packet_id); - if (rv != MQTT_OK) { - client->error = (enum MQTTErrors)rv; - mqtt_recv_ret = rv; - break; - } - break; - case MQTT_CONTROL_PUBCOMP: - /* release associated PUBREL */ - msg = mqtt_mq_find(&client->mq, MQTT_CONTROL_PUBREL, &response.decoded.pubcomp.packet_id); - if (msg == NULL) { - client->error = MQTT_ERROR_ACK_OF_UNKNOWN; - mqtt_recv_ret = MQTT_ERROR_ACK_OF_UNKNOWN; - break; - } - msg->state = MQTT_QUEUED_COMPLETE; - /* update response time */ - client->typical_response_time = 0.875 * (client->typical_response_time) + 0.125 * (double) (MQTT_PAL_TIME() - msg->time_sent); - break; - case MQTT_CONTROL_SUBACK: - /* release associated SUBSCRIBE */ - msg = mqtt_mq_find(&client->mq, MQTT_CONTROL_SUBSCRIBE, &response.decoded.suback.packet_id); - if (msg == NULL) { - client->error = MQTT_ERROR_ACK_OF_UNKNOWN; - mqtt_recv_ret = MQTT_ERROR_ACK_OF_UNKNOWN; - break; - } - msg->state = MQTT_QUEUED_COMPLETE; - /* update response time */ - client->typical_response_time = 0.875 * (client->typical_response_time) + 0.125 * (double) (MQTT_PAL_TIME() - msg->time_sent); - /* check that subscription was successful (not currently only one subscribe at a time) */ - if (response.decoded.suback.return_codes[0] == MQTT_SUBACK_FAILURE) { - client->error = MQTT_ERROR_SUBSCRIBE_FAILED; - mqtt_recv_ret = MQTT_ERROR_SUBSCRIBE_FAILED; - break; - } - break; - case MQTT_CONTROL_UNSUBACK: - /* release associated UNSUBSCRIBE */ - msg = mqtt_mq_find(&client->mq, MQTT_CONTROL_UNSUBSCRIBE, &response.decoded.unsuback.packet_id); - if (msg == NULL) { - client->error = MQTT_ERROR_ACK_OF_UNKNOWN; - mqtt_recv_ret = MQTT_ERROR_ACK_OF_UNKNOWN; - break; - } - msg->state = MQTT_QUEUED_COMPLETE; - /* update response time */ - client->typical_response_time = 0.875 * (client->typical_response_time) + 0.125 * (double) (MQTT_PAL_TIME() - msg->time_sent); - break; - case MQTT_CONTROL_PINGRESP: - /* release associated PINGREQ */ - msg = mqtt_mq_find(&client->mq, MQTT_CONTROL_PINGREQ, NULL); - if (msg == NULL) { - client->error = MQTT_ERROR_ACK_OF_UNKNOWN; - mqtt_recv_ret = MQTT_ERROR_ACK_OF_UNKNOWN; - break; - } - msg->state = MQTT_QUEUED_COMPLETE; - /* update response time */ - client->typical_response_time = 0.875 * (client->typical_response_time) + 0.125 * (double) (MQTT_PAL_TIME() - msg->time_sent); - break; - default: - client->error = MQTT_ERROR_MALFORMED_RESPONSE; - mqtt_recv_ret = MQTT_ERROR_MALFORMED_RESPONSE; - break; - } - { - /* we've handled the response, now clean the buffer */ - void* dest = (unsigned char*)client->recv_buffer.mem_start; - void* src = (unsigned char*)client->recv_buffer.mem_start + consumed; - size_t n = (size_t)(client->recv_buffer.curr - client->recv_buffer.mem_start - - consumed); - memmove(dest, src, n); - client->recv_buffer.curr -= consumed; - client->recv_buffer.curr_sz += (unsigned long)consumed; - } - } - - /* In case there was some error handling the (well formed) message, we end up here */ - MQTT_PAL_MUTEX_UNLOCK(&client->mutex); - return mqtt_recv_ret; -} - -/* FIXED HEADER */ - -#define MQTT_BITFIELD_RULE_VIOLOATION(bitfield, rule_value, rule_mask) ((bitfield ^ rule_value) & rule_mask) - -struct mqtt_fixed_header_rules_s{ - const uint8_t control_type_is_valid[16]; - const uint8_t required_flags[16]; - const uint8_t mask_required_flags[16]; -} ; - -struct mqtt_fixed_header_rules_s mqtt_fixed_header_rules = -{ - { /* boolean value, true if type is valid */ - 0x00, /* MQTT_CONTROL_RESERVED */ - 0x01, /* MQTT_CONTROL_CONNECT */ - 0x01, /* MQTT_CONTROL_CONNACK */ - 0x01, /* MQTT_CONTROL_PUBLISH */ - 0x01, /* MQTT_CONTROL_PUBACK */ - 0x01, /* MQTT_CONTROL_PUBREC */ - 0x01, /* MQTT_CONTROL_PUBREL */ - 0x01, /* MQTT_CONTROL_PUBCOMP */ - 0x01, /* MQTT_CONTROL_SUBSCRIBE */ - 0x01, /* MQTT_CONTROL_SUBACK */ - 0x01, /* MQTT_CONTROL_UNSUBSCRIBE */ - 0x01, /* MQTT_CONTROL_UNSUBACK */ - 0x01, /* MQTT_CONTROL_PINGREQ */ - 0x01, /* MQTT_CONTROL_PINGRESP */ - 0x01, /* MQTT_CONTROL_DISCONNECT */ - 0x00 /* MQTT_CONTROL_RESERVED */ - }, - { /* flags that must be set for the associated control type */ - 0x00, /* MQTT_CONTROL_RESERVED */ - 0x00, /* MQTT_CONTROL_CONNECT */ - 0x00, /* MQTT_CONTROL_CONNACK */ - 0x00, /* MQTT_CONTROL_PUBLISH */ - 0x00, /* MQTT_CONTROL_PUBACK */ - 0x00, /* MQTT_CONTROL_PUBREC */ - 0x02, /* MQTT_CONTROL_PUBREL */ - 0x00, /* MQTT_CONTROL_PUBCOMP */ - 0x02, /* MQTT_CONTROL_SUBSCRIBE */ - 0x00, /* MQTT_CONTROL_SUBACK */ - 0x02, /* MQTT_CONTROL_UNSUBSCRIBE */ - 0x00, /* MQTT_CONTROL_UNSUBACK */ - 0x00, /* MQTT_CONTROL_PINGREQ */ - 0x00, /* MQTT_CONTROL_PINGRESP */ - 0x00, /* MQTT_CONTROL_DISCONNECT */ - 0x00 /* MQTT_CONTROL_RESERVED */ - }, - { /* mask of flags that must be specific values for the associated control type*/ - 0x00, /* MQTT_CONTROL_RESERVED */ - 0x0F, /* MQTT_CONTROL_CONNECT */ - 0x0F, /* MQTT_CONTROL_CONNACK */ - 0x00, /* MQTT_CONTROL_PUBLISH */ - 0x0F, /* MQTT_CONTROL_PUBACK */ - 0x0F, /* MQTT_CONTROL_PUBREC */ - 0x0F, /* MQTT_CONTROL_PUBREL */ - 0x0F, /* MQTT_CONTROL_PUBCOMP */ - 0x0F, /* MQTT_CONTROL_SUBSCRIBE */ - 0x0F, /* MQTT_CONTROL_SUBACK */ - 0x0F, /* MQTT_CONTROL_UNSUBSCRIBE */ - 0x0F, /* MQTT_CONTROL_UNSUBACK */ - 0x0F, /* MQTT_CONTROL_PINGREQ */ - 0x0F, /* MQTT_CONTROL_PINGRESP */ - 0x0F, /* MQTT_CONTROL_DISCONNECT */ - 0x00 /* MQTT_CONTROL_RESERVED */ - } -}; - -static ssize_t mqtt_fixed_header_rule_violation(const struct mqtt_fixed_header *fixed_header) { - uint8_t control_type; - uint8_t control_flags; - uint8_t required_flags; - uint8_t mask_required_flags; - - /* get value and rules */ - control_type = (uint8_t)fixed_header->control_type; - control_flags = fixed_header->control_flags; - required_flags = mqtt_fixed_header_rules.required_flags[control_type]; - mask_required_flags = mqtt_fixed_header_rules.mask_required_flags[control_type]; - - /* check for valid type */ - if (!mqtt_fixed_header_rules.control_type_is_valid[control_type]) { - return MQTT_ERROR_CONTROL_FORBIDDEN_TYPE; - } - - /* check that flags are appropriate */ - if(MQTT_BITFIELD_RULE_VIOLOATION(control_flags, required_flags, mask_required_flags)) { - return MQTT_ERROR_CONTROL_INVALID_FLAGS; - } - - return 0; -} - -ssize_t mqtt_unpack_fixed_header(struct mqtt_response *response, const uint8_t *buf, size_t bufsz) { - struct mqtt_fixed_header *fixed_header; - const uint8_t *start = buf; - int lshift; - ssize_t errcode; - - /* check for null pointers or empty buffer */ - if (response == NULL || buf == NULL) { - return MQTT_ERROR_NULLPTR; - } - fixed_header = &(response->fixed_header); - - /* check that bufsz is not zero */ - if (bufsz == 0) return 0; - - /* parse control type and flags */ - fixed_header->control_type = (enum MQTTControlPacketType)(*buf >> 4); - fixed_header->control_flags = (uint8_t)(*buf & 0x0F); - - /* parse remaining size */ - fixed_header->remaining_length = 0; - - lshift = 0; - do { - - /* MQTT spec (2.2.3) says the maximum length is 28 bits */ - if(lshift == 28) - return MQTT_ERROR_INVALID_REMAINING_LENGTH; - - /* consume byte and assert at least 1 byte left */ - --bufsz; - ++buf; - if (bufsz == 0) return 0; - - /* parse next byte*/ - fixed_header->remaining_length += (uint32_t)((*buf & 0x7F) << lshift); - lshift += 7; - } while(*buf & 0x80); /* while continue bit is set */ - - /* consume last byte */ - --bufsz; - ++buf; - - /* check that the fixed header is valid */ - errcode = mqtt_fixed_header_rule_violation(fixed_header); - if (errcode) { - return errcode; - } - - /* check that the buffer size if GT remaining length */ - if (bufsz < fixed_header->remaining_length) { - return 0; - } - - /* return how many bytes were consumed */ - return buf - start; -} - -ssize_t mqtt_pack_fixed_header(uint8_t *buf, size_t bufsz, const struct mqtt_fixed_header *fixed_header) { - const uint8_t *start = buf; - ssize_t errcode; - uint32_t remaining_length; - - /* check for null pointers or empty buffer */ - if (fixed_header == NULL || buf == NULL) { - return MQTT_ERROR_NULLPTR; - } - - /* check that the fixed header is valid */ - errcode = mqtt_fixed_header_rule_violation(fixed_header); - if (errcode) { - return errcode; - } - - /* check that bufsz is not zero */ - if (bufsz == 0) return 0; - - /* pack control type and flags */ - *buf = (uint8_t)((((uint8_t) fixed_header->control_type) << 4) & 0xF0); - *buf = (uint8_t)(*buf | (((uint8_t) fixed_header->control_flags) & 0x0F)); - - remaining_length = fixed_header->remaining_length; - - /* MQTT spec (2.2.3) says maximum remaining length is 2^28-1 */ - if(remaining_length >= 256*1024*1024) - return MQTT_ERROR_INVALID_REMAINING_LENGTH; - - do { - /* consume byte and assert at least 1 byte left */ - --bufsz; - ++buf; - if (bufsz == 0) return 0; - - /* pack next byte */ - *buf = remaining_length & 0x7F; - if(remaining_length > 127) *buf |= 0x80; - remaining_length = remaining_length >> 7; - } while(*buf & 0x80); - - /* consume last byte */ - --bufsz; - ++buf; - - /* check that there's still enough space in buffer for packet */ - if (bufsz < fixed_header->remaining_length) { - return 0; - } - - /* return how many bytes were consumed */ - return buf - start; -} - -/* CONNECT */ -ssize_t mqtt_pack_connection_request(uint8_t* buf, size_t bufsz, - const char* client_id, - const char* will_topic, - const void* will_message, - size_t will_message_size, - const char* user_name, - const char* password, -#ifdef UA_ENABLE_MQTT_TLS_OPENSSL - const char* caFilePath, - const char* caPath, - const char* clientCertPath, - const char* clientKeyPath, - const bool useTLS, -#endif - uint8_t connect_flags, - uint16_t keep_alive) -{ - struct mqtt_fixed_header fixed_header; - size_t remaining_length; - const uint8_t *const start = buf; - ssize_t rv; -#ifdef UA_ENABLE_MQTT_TLS_OPENSSL - char strTLS[2]; -#endif - /* pack the fixed headr */ - fixed_header.control_type = MQTT_CONTROL_CONNECT; - fixed_header.control_flags = 0x00; - - /* calculate remaining length and build connect_flags at the same time */ - connect_flags = (uint8_t)(connect_flags & ~MQTT_CONNECT_RESERVED); - remaining_length = 10; /* size of variable header */ - - if (client_id == NULL) { - client_id = ""; - } - /* For an empty client_id, a clean session is required */ - if (client_id[0] == '\0' && !(connect_flags & MQTT_CONNECT_CLEAN_SESSION)) { - return MQTT_ERROR_CLEAN_SESSION_IS_REQUIRED; - } - /* mqtt_string length is strlen + 2 */ - remaining_length += __mqtt_packed_cstrlen(client_id); - - if (will_topic != NULL) { - uint8_t temp; - /* there is a will */ - connect_flags |= MQTT_CONNECT_WILL_FLAG; - remaining_length += __mqtt_packed_cstrlen(will_topic); - - if (will_message == NULL) { - /* if there's a will there MUST be a will message */ - return MQTT_ERROR_CONNECT_NULL_WILL_MESSAGE; - } - remaining_length += 2 + will_message_size; /* size of will_message */ - - /* assert that the will QOS is valid (i.e. not 3) */ - temp = connect_flags & 0x18; /* mask to QOS */ - if (temp == 0x18) { - /* bitwise equality with QoS 3 (invalid)*/ - return MQTT_ERROR_CONNECT_FORBIDDEN_WILL_QOS; - } - } else { - /* there is no will so set all will flags to zero */ - connect_flags &= (uint8_t)~MQTT_CONNECT_WILL_FLAG; - connect_flags &= (uint8_t)~0x18; - connect_flags &= (uint8_t)~MQTT_CONNECT_WILL_RETAIN; - } - - if (user_name != NULL) { - /* a user name is present */ - connect_flags |= MQTT_CONNECT_USER_NAME; - remaining_length += (uint32_t)__mqtt_packed_cstrlen(user_name); - } else { - connect_flags &= (uint8_t)~MQTT_CONNECT_USER_NAME; - } - - if (password != NULL) { - /* a password is present */ - connect_flags |= MQTT_CONNECT_PASSWORD; - remaining_length += (uint32_t)__mqtt_packed_cstrlen(password); - } else { - connect_flags &= (uint8_t)~MQTT_CONNECT_PASSWORD; - } - -#ifdef UA_ENABLE_MQTT_TLS_OPENSSL - if (useTLS) { - if (caFilePath != NULL) { - /* a caFilePath is present */ - connect_flags |= (uint8_t)MQTT_CONNECT_CAFILEPATH; - remaining_length += (uint32_t)__mqtt_packed_cstrlen(caFilePath); - } else { - connect_flags &= (uint8_t)~MQTT_CONNECT_CAFILEPATH; - } - - if (caPath != NULL) { - /* a caPath is present */ - connect_flags |= (uint8_t)MQTT_CONNECT_CAPATH; - remaining_length += (uint32_t)__mqtt_packed_cstrlen(caPath); - } else { - connect_flags &= (uint8_t)~MQTT_CONNECT_CAPATH; - } - - if (clientCertPath != NULL) { - /* a clientCertPath is present */ - connect_flags |= (uint8_t)MQTT_CONNECT_CLIENTCERTPATH; - remaining_length += (uint32_t)__mqtt_packed_cstrlen(clientCertPath); - } else { - connect_flags &= (uint8_t)~MQTT_CONNECT_CLIENTCERTPATH; - } - - if (clientKeyPath != NULL) { - /* a clientKeyPath is present */ - connect_flags |= (uint8_t)MQTT_CONNECT_CLIENTKEYPATH; - remaining_length += (uint32_t)__mqtt_packed_cstrlen(clientKeyPath); - } else { - connect_flags &= (uint8_t)~MQTT_CONNECT_CLIENTKEYPATH; - } - - /* Call the strncpy function with a size of two to zero-terminate the strTLS value */ - if (useTLS) /* useTLS = true */ - strncpy(strTLS, "1", 2); - else - strncpy(strTLS, "0", 2); - connect_flags |= (uint8_t)MQTT_CONNECT_USETLS; - remaining_length += (uint32_t)__mqtt_packed_cstrlen(strTLS); - } -#endif - - /* fixed header length is now calculated*/ - fixed_header.remaining_length = (uint32_t)remaining_length; - - /* pack fixed header and perform error checks */ - rv = mqtt_pack_fixed_header(buf, bufsz, &fixed_header); - if (rv <= 0) { - /* something went wrong */ - return rv; - } - buf += rv; - bufsz -= (size_t)rv; - - /* check that the buffer has enough space to fit the remaining length */ - if (bufsz < fixed_header.remaining_length) { - return 0; - } - - /* pack the variable header */ - *buf++ = 0x00; - *buf++ = 0x04; - *buf++ = (uint8_t) 'M'; - *buf++ = (uint8_t) 'Q'; - *buf++ = (uint8_t) 'T'; - *buf++ = (uint8_t) 'T'; - *buf++ = MQTT_PROTOCOL_LEVEL; - *buf++ = connect_flags; - buf += __mqtt_pack_uint16(buf, keep_alive); - - /* pack the payload */ - buf += __mqtt_pack_str(buf, client_id); - if (connect_flags & MQTT_CONNECT_WILL_FLAG) { - buf += __mqtt_pack_str(buf, will_topic); - buf += __mqtt_pack_uint16(buf, (uint16_t)will_message_size); - memcpy(buf, will_message, will_message_size); - buf += will_message_size; - } - if (connect_flags & MQTT_CONNECT_USER_NAME) { - buf += __mqtt_pack_str(buf, user_name); - } - if (connect_flags & MQTT_CONNECT_PASSWORD) { - buf += __mqtt_pack_str(buf, password); - } -#ifdef UA_ENABLE_MQTT_TLS_OPENSSL - if (connect_flags & MQTT_CONNECT_CAFILEPATH) { - buf += __mqtt_pack_str(buf, caFilePath); - } - if (connect_flags & MQTT_CONNECT_CAPATH) { - buf += __mqtt_pack_str(buf, caPath); - } - if (useTLS) { - if (connect_flags & MQTT_CONNECT_CLIENTCERTPATH) { - buf += __mqtt_pack_str(buf, clientCertPath); - } - if (connect_flags & MQTT_CONNECT_CLIENTKEYPATH) { - buf += __mqtt_pack_str(buf, clientKeyPath); - } - - if (connect_flags & MQTT_CONNECT_USETLS) { - buf += __mqtt_pack_str(buf, strTLS); - } - } -#endif - /* return the number of bytes that were consumed */ - return buf - start; -} - -/* CONNACK */ -ssize_t mqtt_unpack_connack_response(struct mqtt_response *mqtt_response, const uint8_t *buf) { - const uint8_t *const start = buf; - struct mqtt_response_connack *response; - - /* check that remaining length is 2 */ - if (mqtt_response->fixed_header.remaining_length != 2) { - return MQTT_ERROR_MALFORMED_RESPONSE; - } - - response = &(mqtt_response->decoded.connack); - /* unpack */ - if (*buf & 0xFE) { - /* only bit 1 can be set */ - return MQTT_ERROR_CONNACK_FORBIDDEN_FLAGS; - } else { - response->session_present_flag = *buf++; - } - - if (*buf > 5u) { - /* only bit 1 can be set */ - return MQTT_ERROR_CONNACK_FORBIDDEN_CODE; - } else { - response->return_code = (enum MQTTConnackReturnCode) *buf++; - } - return buf - start; -} - -/* DISCONNECT */ -ssize_t mqtt_pack_disconnect(uint8_t *buf, size_t bufsz) { - struct mqtt_fixed_header fixed_header; - fixed_header.control_type = MQTT_CONTROL_DISCONNECT; - fixed_header.control_flags = 0; - fixed_header.remaining_length = 0; - return mqtt_pack_fixed_header(buf, bufsz, &fixed_header); -} - -/* PING */ -ssize_t mqtt_pack_ping_request(uint8_t *buf, size_t bufsz) { - struct mqtt_fixed_header fixed_header; - fixed_header.control_type = MQTT_CONTROL_PINGREQ; - fixed_header.control_flags = 0; - fixed_header.remaining_length = 0; - return mqtt_pack_fixed_header(buf, bufsz, &fixed_header); -} - -/* PUBLISH */ -ssize_t mqtt_pack_publish_request(uint8_t *buf, size_t bufsz, - const char* topic_name, - uint16_t packet_id, - void* application_message, - size_t application_message_size, - uint8_t publish_flags) -{ - const uint8_t *const start = buf; - ssize_t rv; - struct mqtt_fixed_header fixed_header; - uint32_t remaining_length; - uint8_t inspected_qos; - - /* check for null pointers */ - if(buf == NULL || topic_name == NULL) { - return MQTT_ERROR_NULLPTR; - } - - /* inspect QoS level */ - inspected_qos = (uint8_t)((publish_flags & MQTT_PUBLISH_QOS_MASK) >> 1); /* mask */ - - /* build the fixed header */ - fixed_header.control_type = MQTT_CONTROL_PUBLISH; - - /* calculate remaining length */ - remaining_length = (uint16_t)__mqtt_packed_cstrlen(topic_name); - if (inspected_qos > 0) { - remaining_length += 2; - } - remaining_length += (uint16_t)application_message_size; - fixed_header.remaining_length = remaining_length; - - /* force dup to 0 if qos is 0 [Spec MQTT-3.3.1-2] */ - if (inspected_qos == 0) { - publish_flags &= (uint8_t)~MQTT_PUBLISH_DUP; - } - - /* make sure that qos is not 3 [Spec MQTT-3.3.1-4] */ - if (inspected_qos == 3) { - return MQTT_ERROR_PUBLISH_FORBIDDEN_QOS; - } - fixed_header.control_flags = publish_flags & 0x7; //@@@ - - /* pack fixed header */ - rv = mqtt_pack_fixed_header(buf, bufsz, &fixed_header); - if (rv <= 0) { - /* something went wrong */ - return rv; - } - buf += rv; - bufsz -= (size_t)rv; - - /* check that buffer is big enough */ - if (bufsz < remaining_length) { - return 0; - } - - - /* pack variable header */ - buf += __mqtt_pack_str(buf, topic_name); - if (inspected_qos > 0) { - buf += __mqtt_pack_uint16(buf, packet_id); - } - - /* pack payload */ - memcpy(buf, application_message, application_message_size); - buf += application_message_size; - - return buf - start; -} - -ssize_t mqtt_unpack_publish_response(struct mqtt_response *mqtt_response, const uint8_t *buf) -{ - const uint8_t *const start = buf; - struct mqtt_fixed_header *fixed_header; - struct mqtt_response_publish *response; - - fixed_header = &(mqtt_response->fixed_header); - response = &(mqtt_response->decoded.publish); - - /* get flags */ - response->dup_flag = (uint8_t)((fixed_header->control_flags & MQTT_PUBLISH_DUP) >> 3); - response->qos_level = (uint8_t)((fixed_header->control_flags & MQTT_PUBLISH_QOS_MASK) >> 1); - response->retain_flag = fixed_header->control_flags & MQTT_PUBLISH_RETAIN; - - /* make sure that remaining length is valid */ - if (mqtt_response->fixed_header.remaining_length < 4) { - return MQTT_ERROR_MALFORMED_RESPONSE; - } - - /* parse variable header */ - response->topic_name_size = __mqtt_unpack_uint16(buf); - buf += 2; - response->topic_name = buf; - buf += response->topic_name_size; - - if (response->qos_level > 0) { - response->packet_id = __mqtt_unpack_uint16(buf); - buf += 2; - } - - /* get payload */ - response->application_message = buf; - if (response->qos_level == 0) { - response->application_message_size = fixed_header->remaining_length - response->topic_name_size - 2; - } else { - response->application_message_size = fixed_header->remaining_length - response->topic_name_size - 4; - } - buf += response->application_message_size; - - /* return number of bytes consumed */ - return buf - start; -} - -/* PUBXXX */ -ssize_t mqtt_pack_pubxxx_request(uint8_t *buf, size_t bufsz, - enum MQTTControlPacketType control_type, - uint16_t packet_id) -{ - const uint8_t *const start = buf; - struct mqtt_fixed_header fixed_header; - ssize_t rv; - if (buf == NULL) { - return MQTT_ERROR_NULLPTR; - } - - /* pack fixed header */ - fixed_header.control_type = control_type; - if (control_type == MQTT_CONTROL_PUBREL) { - fixed_header.control_flags = 0x02; - } else { - fixed_header.control_flags = 0; - } - fixed_header.remaining_length = 2; - rv = mqtt_pack_fixed_header(buf, bufsz, &fixed_header); - if (rv <= 0) { - return rv; - } - buf += rv; - bufsz -= (size_t)rv; - - if (bufsz < fixed_header.remaining_length) { - return 0; - } - - buf += __mqtt_pack_uint16(buf, packet_id); - - return buf - start; -} - -ssize_t mqtt_unpack_pubxxx_response(struct mqtt_response *mqtt_response, const uint8_t *buf) -{ - const uint8_t *const start = buf; - uint16_t packet_id; - - /* assert remaining length is correct */ - if (mqtt_response->fixed_header.remaining_length != 2) { - return MQTT_ERROR_MALFORMED_RESPONSE; - } - - /* parse packet_id */ - packet_id = __mqtt_unpack_uint16(buf); - buf += 2; - - if (mqtt_response->fixed_header.control_type == MQTT_CONTROL_PUBACK) { - mqtt_response->decoded.puback.packet_id = packet_id; - } else if (mqtt_response->fixed_header.control_type == MQTT_CONTROL_PUBREC) { - mqtt_response->decoded.pubrec.packet_id = packet_id; - } else if (mqtt_response->fixed_header.control_type == MQTT_CONTROL_PUBREL) { - mqtt_response->decoded.pubrel.packet_id = packet_id; - } else { - mqtt_response->decoded.pubcomp.packet_id = packet_id; - } - - return buf - start; -} - -/* SUBACK */ -ssize_t mqtt_unpack_suback_response (struct mqtt_response *mqtt_response, const uint8_t *buf) { - const uint8_t *const start = buf; - uint32_t remaining_length = mqtt_response->fixed_header.remaining_length; - - /* assert remaining length is at least 3 (for packet id and at least 1 topic) */ - if (remaining_length < 3) { - return MQTT_ERROR_MALFORMED_RESPONSE; - } - - /* unpack packet_id */ - mqtt_response->decoded.suback.packet_id = __mqtt_unpack_uint16(buf); - buf += 2; - remaining_length -= 2; - - /* unpack return codes */ - mqtt_response->decoded.suback.num_return_codes = (size_t) remaining_length; - mqtt_response->decoded.suback.return_codes = buf; - buf += remaining_length; - - return buf - start; -} - -/* SUBSCRIBE */ -ssize_t mqtt_pack_subscribe_request(uint8_t *buf, size_t bufsz, unsigned int packet_id, ...) { - va_list args; - const uint8_t *const start = buf; - ssize_t rv; - struct mqtt_fixed_header fixed_header; - unsigned int num_subs = 0; - unsigned int i; - const char *topic[MQTT_SUBSCRIBE_REQUEST_MAX_NUM_TOPICS]; - uint8_t max_qos[MQTT_SUBSCRIBE_REQUEST_MAX_NUM_TOPICS]; - - /* parse all subscriptions */ - va_start(args, packet_id); - while(1) { - topic[num_subs] = va_arg(args, const char*); - if (topic[num_subs] == NULL) { - /* end of list */ - break; - } - - max_qos[num_subs] = (uint8_t) va_arg(args, unsigned int); - - ++num_subs; - if (num_subs >= MQTT_SUBSCRIBE_REQUEST_MAX_NUM_TOPICS) { - return MQTT_ERROR_SUBSCRIBE_TOO_MANY_TOPICS; - } - } - va_end(args); - - /* build the fixed header */ - fixed_header.control_type = MQTT_CONTROL_SUBSCRIBE; - fixed_header.control_flags = 2u; - fixed_header.remaining_length = 2u; /* size of variable header */ - for(i = 0; i < num_subs; ++i) { - /* payload is topic name + max qos (1 byte) */ - fixed_header.remaining_length += __mqtt_packed_cstrlen(topic[i]) + 1; - } - - /* pack the fixed header */ - rv = mqtt_pack_fixed_header(buf, bufsz, &fixed_header); - if (rv <= 0) { - return rv; - } - buf += rv; - bufsz -= (unsigned long)rv; - - /* check that the buffer has enough space */ - if (bufsz < fixed_header.remaining_length) { - return 0; - } - - - /* pack variable header */ - buf += __mqtt_pack_uint16(buf, (uint16_t)packet_id); - - - /* pack payload */ - for(i = 0; i < num_subs; ++i) { - buf += __mqtt_pack_str(buf, topic[i]); - *buf++ = max_qos[i]; - } - - return buf - start; -} - -/* UNSUBACK */ -ssize_t mqtt_unpack_unsuback_response(struct mqtt_response *mqtt_response, const uint8_t *buf) -{ - const uint8_t *const start = buf; - - if (mqtt_response->fixed_header.remaining_length != 2) { - return MQTT_ERROR_MALFORMED_RESPONSE; - } - - /* parse packet_id */ - mqtt_response->decoded.unsuback.packet_id = __mqtt_unpack_uint16(buf); - buf += 2; - - return buf - start; -} - -/* UNSUBSCRIBE */ -ssize_t mqtt_pack_unsubscribe_request(uint8_t *buf, size_t bufsz, unsigned int packet_id, ...) { - va_list args; - const uint8_t *const start = buf; - ssize_t rv; - struct mqtt_fixed_header fixed_header; - unsigned int num_subs = 0; - unsigned int i; - const char *topic[MQTT_UNSUBSCRIBE_REQUEST_MAX_NUM_TOPICS]; - - /* parse all subscriptions */ - va_start(args, packet_id); - while(1) { - topic[num_subs] = va_arg(args, const char*); - if (topic[num_subs] == NULL) { - /* end of list */ - break; - } - - ++num_subs; - if (num_subs >= MQTT_UNSUBSCRIBE_REQUEST_MAX_NUM_TOPICS) { - return MQTT_ERROR_UNSUBSCRIBE_TOO_MANY_TOPICS; - } - } - va_end(args); - - /* build the fixed header */ - fixed_header.control_type = MQTT_CONTROL_UNSUBSCRIBE; - fixed_header.control_flags = 2u; - fixed_header.remaining_length = 2u; /* size of variable header */ - for(i = 0; i < num_subs; ++i) { - /* payload is topic name */ - fixed_header.remaining_length += __mqtt_packed_cstrlen(topic[i]); - } - - /* pack the fixed header */ - rv = mqtt_pack_fixed_header(buf, bufsz, &fixed_header); - if (rv <= 0) { - return rv; - } - buf += rv; - bufsz -= (unsigned long)rv; - - /* check that the buffer has enough space */ - if (bufsz < fixed_header.remaining_length) { - return 0; - } - - /* pack variable header */ - buf += __mqtt_pack_uint16(buf, (uint16_t)packet_id); - - - /* pack payload */ - for(i = 0; i < num_subs; ++i) { - buf += __mqtt_pack_str(buf, topic[i]); - } - - return buf - start; -} - -/* MESSAGE QUEUE */ -void mqtt_mq_init(struct mqtt_message_queue *mq, void *buf, size_t bufsz) -{ - mq->mem_start = buf; - mq->mem_end = (uint8_t *)buf + bufsz; - mq->curr = (uint8_t *)buf; - mq->queue_tail = (struct mqtt_queued_message *)mq->mem_end; - mq->curr_sz = (size_t)(mqtt_mq_currsz(mq)); -} - -struct mqtt_queued_message* mqtt_mq_register(struct mqtt_message_queue *mq, size_t nbytes) -{ - /* make queued message header */ - --(mq->queue_tail); - mq->queue_tail->start = mq->curr; - mq->queue_tail->size = nbytes; - mq->queue_tail->state = MQTT_QUEUED_UNSENT; - - /* move curr and recalculate curr_sz */ - mq->curr += nbytes; - mq->curr_sz = (size_t)(mqtt_mq_currsz(mq)); - - return mq->queue_tail; -} - -void mqtt_mq_clean(struct mqtt_message_queue *mq) { - struct mqtt_queued_message *new_head; - - for(new_head = mqtt_mq_get(mq, 0); new_head >= mq->queue_tail; --new_head) { - if (new_head->state != MQTT_QUEUED_COMPLETE) break; - } - - /* check if everything can be removed */ - if (new_head < mq->queue_tail) { - mq->curr = (uint8_t *)mq->mem_start; - mq->queue_tail = (struct mqtt_queued_message *)mq->mem_end; - mq->curr_sz = (size_t)(mqtt_mq_currsz(mq)); - return; - } else if (new_head == mqtt_mq_get(mq, 0)) { - /* do nothing */ - return; - } - - /* move buffered data */ - { - size_t n = (size_t)(mq->curr - new_head->start); - size_t removing = (size_t)(new_head->start - (uint8_t*) mq->mem_start); - memmove(mq->mem_start, new_head->start, n); - mq->curr = (uint8_t *)((uint8_t *)mq->mem_start + n); - - - /* move queue */ - { - ssize_t new_tail_idx = new_head - mq->queue_tail; - memmove(mqtt_mq_get(mq, new_tail_idx), mq->queue_tail, sizeof(struct mqtt_queued_message) * (size_t)((new_tail_idx + 1))); - mq->queue_tail = mqtt_mq_get(mq, new_tail_idx); - - { - /* bump back start's */ - for(ssize_t i = 0; i < new_tail_idx + 1; ++i) { - mqtt_mq_get(mq, i)->start -= removing; - } - } - } - } - - /* get curr_sz */ - mq->curr_sz = (size_t)(mqtt_mq_currsz(mq)); -} - -struct mqtt_queued_message* mqtt_mq_find(struct mqtt_message_queue *mq, enum MQTTControlPacketType control_type, uint16_t *packet_id) -{ - struct mqtt_queued_message *curr; - for(curr = mqtt_mq_get(mq, 0); curr >= mq->queue_tail; --curr) { - if (curr->control_type == control_type) { - if ((packet_id == NULL && curr->state != MQTT_QUEUED_COMPLETE) || - (packet_id != NULL && *packet_id == curr->packet_id)) { - return curr; - } - } - } - return NULL; -} - - -/* RESPONSE UNPACKING */ -ssize_t mqtt_unpack_response(struct mqtt_response* response, const uint8_t *buf, size_t bufsz) { - const uint8_t *const start = buf; - ssize_t rv = mqtt_unpack_fixed_header(response, buf, bufsz); - if (rv <= 0) return rv; - else buf += rv; - switch(response->fixed_header.control_type) { - case MQTT_CONTROL_CONNACK: - rv = mqtt_unpack_connack_response(response, buf); - break; - case MQTT_CONTROL_PUBLISH: - rv = mqtt_unpack_publish_response(response, buf); - break; - case MQTT_CONTROL_PUBACK: - rv = mqtt_unpack_pubxxx_response(response, buf); - break; - case MQTT_CONTROL_PUBREC: - rv = mqtt_unpack_pubxxx_response(response, buf); - break; - case MQTT_CONTROL_PUBREL: - rv = mqtt_unpack_pubxxx_response(response, buf); - break; - case MQTT_CONTROL_PUBCOMP: - rv = mqtt_unpack_pubxxx_response(response, buf); - break; - case MQTT_CONTROL_SUBACK: - rv = mqtt_unpack_suback_response(response, buf); - break; - case MQTT_CONTROL_UNSUBACK: - rv = mqtt_unpack_unsuback_response(response, buf); - break; - case MQTT_CONTROL_PINGRESP: - return rv; - default: - return MQTT_ERROR_RESPONSE_INVALID_CONTROL_TYPE; - } - - if (rv < 0) return rv; - buf += rv; - return buf - start; -} - -/* EXTRA DETAILS */ -ssize_t __mqtt_pack_uint16(uint8_t *buf, uint16_t integer) -{ - uint16_t integer_htons = MQTT_PAL_HTONS(integer); - memcpy(buf, &integer_htons, 2); - return 2; -} - -uint16_t __mqtt_unpack_uint16(const uint8_t *buf) -{ - uint16_t integer_htons; - memcpy(&integer_htons, buf, 2); - return MQTT_PAL_NTOHS(integer_htons); -} - -ssize_t __mqtt_pack_str(uint8_t *buf, const char* str) { - uint16_t length = (uint16_t)strlen(str); - /* pack string length */ - buf += __mqtt_pack_uint16(buf, length); - - /* pack string */ - for(int i = 0; i < length; ++i) { - *(buf++) = (uint8_t)str[i]; - } - - /* return number of bytes consumed */ - return length + 2; -} - -static const char *MQTT_ERRORS_STR[] = { - "MQTT_UNKNOWN_ERROR", - __ALL_MQTT_ERRORS(GENERATE_STRING) -}; - -const char* mqtt_error_str(enum MQTTErrors error) { - int offset = error - MQTT_ERROR_UNKNOWN; - if (offset >= 0) { - return MQTT_ERRORS_STR[offset]; - } else if (error == 0) { - return "MQTT_ERROR: Buffer too small."; - } else if (error > 0) { - return "MQTT_OK"; - } else { - return MQTT_ERRORS_STR[0]; - } -} - -/** @endcond*/ diff --git a/deps/mqtt-c/mqtt.h b/deps/mqtt-c/mqtt.h deleted file mode 100644 index 377448c7f..000000000 --- a/deps/mqtt-c/mqtt.h +++ /dev/null @@ -1,1590 +0,0 @@ -#ifndef __MQTT_H__ -#define __MQTT_H__ - -/* -MIT License - -Copyright(c) 2018 Liam Bindle - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files(the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions : - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -#include "mqtt_pal.h" - -#ifdef __MINGW32__ - #include -#endif -/** - * @file - * @brief Declares all the MQTT-C functions and datastructures. - * - * @note You should \#include . - * - * @example simple_publisher.c - * A simple program to that publishes the current time whenever ENTER is pressed. - * - * Usage: - * \code{.sh} - * ./bin/simple_publisher [address [port [topic]]] - * \endcode - * - * Where \c address is the address of the MQTT broker, \c port is the port number the - * MQTT broker is running on, and \c topic is the name of the topic to publish with. Note - * that all these arguments are optional and the defaults are \c address = \c "test.mosquitto.org", - * \c port = \c "1883", and \c topic = "datetime". - * - * @example simple_subscriber.c - * A simple program that subscribes to a single topic and prints all updates that are received. - * - * Usage: - * \code{.sh} - * ./bin/simple_subscriber [address [port [topic]]] - * \endcode - * - * Where \c address is the address of the MQTT broker, \c port is the port number the - * MQTT broker is running on, and \c topic is the name of the topic subscribe to. Note - * that all these arguments are optional and the defaults are \c address = \c "test.mosquitto.org", - * \c port = \c "1883", and \c topic = "datetime". - * - * @example reconnect_subscriber.c - * Same program as \ref simple_subscriber.c, but using the automatic reconnect functionality. - * - * @example bio_publisher.c - * Same program as \ref simple_publisher.c, but uses an unencrypted BIO socket. - * - * @example openssl_publisher.c - * Same program as \ref simple_publisher.c, but over an encrypted connection using OpenSSL. - * - * Usage: - * \code{.sh} - * ./bin/openssl_publisher ca_file [address [port [topic]]] - * \endcode - * - * - * @defgroup api API - * @brief Documentation of everything you need to know to use the MQTT-C client. - * - * This module contains everything you need to know to use MQTT-C in your application. - * For usage examples see: - * - @ref simple_publisher.c - * - @ref simple_subscriber.c - * - @ref reconnect_subscriber.c - * - @ref bio_publisher.c - * - @ref openssl_publisher.c - * - * @note MQTT-C can be used in both single-threaded and multi-threaded applications. All - * the functions in \ref api are thread-safe. - * - * @defgroup packers Control Packet Serialization - * @brief Developer documentation of the functions and datastructures used for serializing MQTT - * control packets. - * - * @defgroup unpackers Control Packet Deserialization - * @brief Developer documentation of the functions and datastructures used for deserializing MQTT - * control packets. - * - * @defgroup details Utilities - * @brief Developer documentation for the utilities used to implement the MQTT-C client. - * - * @note To deserialize a packet from a buffer use \ref mqtt_unpack_response (it's the only - * function you need). - */ - - - /** - * @brief An enumeration of the MQTT control packet types. - * @ingroup unpackers - * - * @see - * MQTT v3.1.1: MQTT Control Packet Types - * - */ - enum MQTTControlPacketType { - MQTT_CONTROL_CONNECT=1u, - MQTT_CONTROL_CONNACK=2u, - MQTT_CONTROL_PUBLISH=3u, - MQTT_CONTROL_PUBACK=4u, - MQTT_CONTROL_PUBREC=5u, - MQTT_CONTROL_PUBREL=6u, - MQTT_CONTROL_PUBCOMP=7u, - MQTT_CONTROL_SUBSCRIBE=8u, - MQTT_CONTROL_SUBACK=9u, - MQTT_CONTROL_UNSUBSCRIBE=10u, - MQTT_CONTROL_UNSUBACK=11u, - MQTT_CONTROL_PINGREQ=12u, - MQTT_CONTROL_PINGRESP=13u, - MQTT_CONTROL_DISCONNECT=14u -}; - -/** - * @brief The fixed header of an MQTT control packet. - * @ingroup unpackers - * - * @see - * MQTT v3.1.1: Fixed Header - * - */ -struct mqtt_fixed_header { - /** The type of packet. */ - enum MQTTControlPacketType control_type; - - /** The packets control flags.*/ - uint32_t control_flags: 4; - - /** The remaining size of the packet in bytes (i.e. the size of variable header and payload).*/ - uint32_t remaining_length; -}; - -/** - * @brief The protocol identifier for MQTT v3.1.1. - * @ingroup packers - * - * @see - * MQTT v3.1.1: CONNECT Variable Header. - * - */ -#define MQTT_PROTOCOL_LEVEL 0x04 - -/** - * @brief A macro used to declare the enum MQTTErrors and associated - * error messages (the members of the num) at the same time. - */ -#define __ALL_MQTT_ERRORS(MQTT_ERROR) \ - MQTT_ERROR(MQTT_ERROR_NULLPTR) \ - MQTT_ERROR(MQTT_ERROR_CONTROL_FORBIDDEN_TYPE) \ - MQTT_ERROR(MQTT_ERROR_CONTROL_INVALID_FLAGS) \ - MQTT_ERROR(MQTT_ERROR_CONTROL_WRONG_TYPE) \ - MQTT_ERROR(MQTT_ERROR_CONNECT_CLIENT_ID_REFUSED) \ - MQTT_ERROR(MQTT_ERROR_CONNECT_NULL_WILL_MESSAGE) \ - MQTT_ERROR(MQTT_ERROR_CONNECT_FORBIDDEN_WILL_QOS) \ - MQTT_ERROR(MQTT_ERROR_CONNACK_FORBIDDEN_FLAGS) \ - MQTT_ERROR(MQTT_ERROR_CONNACK_FORBIDDEN_CODE) \ - MQTT_ERROR(MQTT_ERROR_PUBLISH_FORBIDDEN_QOS) \ - MQTT_ERROR(MQTT_ERROR_SUBSCRIBE_TOO_MANY_TOPICS) \ - MQTT_ERROR(MQTT_ERROR_MALFORMED_RESPONSE) \ - MQTT_ERROR(MQTT_ERROR_UNSUBSCRIBE_TOO_MANY_TOPICS) \ - MQTT_ERROR(MQTT_ERROR_RESPONSE_INVALID_CONTROL_TYPE) \ - MQTT_ERROR(MQTT_ERROR_CONNECT_NOT_CALLED) \ - MQTT_ERROR(MQTT_ERROR_SEND_BUFFER_IS_FULL) \ - MQTT_ERROR(MQTT_ERROR_SOCKET_ERROR) \ - MQTT_ERROR(MQTT_ERROR_MALFORMED_REQUEST) \ - MQTT_ERROR(MQTT_ERROR_RECV_BUFFER_TOO_SMALL) \ - MQTT_ERROR(MQTT_ERROR_ACK_OF_UNKNOWN) \ - MQTT_ERROR(MQTT_ERROR_NOT_IMPLEMENTED) \ - MQTT_ERROR(MQTT_ERROR_CONNECTION_REFUSED) \ - MQTT_ERROR(MQTT_ERROR_SUBSCRIBE_FAILED) \ - MQTT_ERROR(MQTT_ERROR_CONNECTION_CLOSED) \ - MQTT_ERROR(MQTT_ERROR_INITIAL_RECONNECT) \ - MQTT_ERROR(MQTT_ERROR_INVALID_REMAINING_LENGTH) \ - MQTT_ERROR(MQTT_ERROR_CLEAN_SESSION_IS_REQUIRED) - -/* todo: add more connection refused errors */ - -/** - * @brief A macro used to generate the enum MQTTErrors from - * \ref __ALL_MQTT_ERRORS - * @see __ALL_MQTT_ERRORS -*/ -#define GENERATE_ENUM(ENUM) ENUM, - -/** - * @brief A macro used to generate the error messages associated with - * MQTTErrors from \ref __ALL_MQTT_ERRORS - * @see __ALL_MQTT_ERRORS -*/ -#define GENERATE_STRING(STRING) #STRING, - - -/** - * @brief An enumeration of error codes. Error messages can be retrieved by calling \ref mqtt_error_str. - * @ingroup api - * - * @see mqtt_error_str - */ -enum MQTTErrors { - MQTT_ERROR_UNKNOWN=INT_MIN, - __ALL_MQTT_ERRORS(GENERATE_ENUM) - MQTT_OK = 1 -}; - -/** - * @brief Returns an error message for error code, \p error. - * @ingroup api - * - * @param[in] error the error code. - * - * @returns The associated error message. - */ -const char* mqtt_error_str(enum MQTTErrors error); - -/** - * @brief Pack a MQTT 16 bit integer, given a native 16 bit integer . - * - * @param[out] buf the buffer that the MQTT integer will be written to. - * @param[in] integer the native integer to be written to \p buf. - * - * @warning This function provides no error checking. - * - * @returns 2 -*/ -ssize_t __mqtt_pack_uint16(uint8_t *buf, uint16_t integer); - -/** - * @brief Unpack a MQTT 16 bit integer to a native 16 bit integer. - * - * @param[in] buf the buffer that the MQTT integer will be read from. - * - * @warning This function provides no error checking and does not modify \p buf. - * - * @returns The native integer -*/ -uint16_t __mqtt_unpack_uint16(const uint8_t *buf); - -/** - * @brief Pack a MQTT string, given a c-string \p str. - * - * @param[out] buf the buffer that the MQTT string will be written to. - * @param[in] str the c-string to be written to \p buf. - * - * @warning This function provides no error checking. - * - * @returns strlen(str) + 2 -*/ -ssize_t __mqtt_pack_str(uint8_t *buf, const char* str); - -/** @brief A macro to get the MQTT string length from a c-string. */ -#define __mqtt_packed_cstrlen(x) (2 + (unsigned int) strlen(x)) - -/* RESPONSES */ - -/** - * @brief An enumeration of the return codes returned in a CONNACK packet. - * @ingroup unpackers - * - * @see - * MQTT v3.1.1: CONNACK return codes. - * - */ -enum MQTTConnackReturnCode { - MQTT_CONNACK_ACCEPTED = 0u, - MQTT_CONNACK_REFUSED_PROTOCOL_VERSION = 1u, - MQTT_CONNACK_REFUSED_IDENTIFIER_REJECTED = 2u, - MQTT_CONNACK_REFUSED_SERVER_UNAVAILABLE = 3u, - MQTT_CONNACK_REFUSED_BAD_USER_NAME_OR_PASSWORD = 4u, - MQTT_CONNACK_REFUSED_NOT_AUTHORIZED = 5u -}; - -/** - * @brief A connection response datastructure. - * @ingroup unpackers - * - * @see - * MQTT v3.1.1: CONNACK - Acknowledgement connection response. - * - */ -struct mqtt_response_connack { - /** - * @brief Allows client and broker to check if they have a consistent view about whether there is - * already a stored session state. - */ - uint8_t session_present_flag; - - /** - * @brief The return code of the connection request. - * - * @see MQTTConnackReturnCode - */ - enum MQTTConnackReturnCode return_code; -}; - - /** - * @brief A publish packet received from the broker. - * @ingroup unpackers - * - * A publish packet is received from the broker when a client publishes to a topic that the - * \em {local client} is subscribed to. - * - * @see - * MQTT v3.1.1: PUBLISH - Publish Message. - * - */ -struct mqtt_response_publish { - /** - * @brief The DUP flag. DUP flag is 0 if its the first attempt to send this publish packet. A DUP flag - * of 1 means that this might be a re-delivery of the packet. - */ - uint8_t dup_flag; - - /** - * @brief The quality of service level. - * - * @see - * MQTT v3.1.1: QoS Definitions - * - */ - uint8_t qos_level; - - /** @brief The retain flag of this publish message. */ - uint8_t retain_flag; - - /** @brief Size of the topic name (number of characters). */ - uint16_t topic_name_size; - - /** - * @brief The topic name. - * @note topic_name is not null terminated. Therefore topic_name_size must be used to get the - * string length. - */ - const void* topic_name; - - /** @brief The publish message's packet ID. */ - uint16_t packet_id; - - /** @brief The publish message's application message.*/ - const void* application_message; - - /** @brief The size of the application message in bytes. */ - size_t application_message_size; -}; - -/** - * @brief A publish acknowledgement for messages that were published with QoS level 1. - * @ingroup unpackers - * - * @see - * MQTT v3.1.1: PUBACK - Publish Acknowledgement. - * - * - */ -struct mqtt_response_puback { - /** @brief The published messages packet ID. */ - uint16_t packet_id; -}; - -/** - * @brief The response packet to a PUBLISH packet with QoS level 2. - * @ingroup unpackers - * - * @see - * MQTT v3.1.1: PUBREC - Publish Received. - * - * - */ -struct mqtt_response_pubrec { - /** @brief The published messages packet ID. */ - uint16_t packet_id; -}; - -/** - * @brief The response to a PUBREC packet. - * @ingroup unpackers - * - * @see - * MQTT v3.1.1: PUBREL - Publish Release. - * - * - */ -struct mqtt_response_pubrel { - /** @brief The published messages packet ID. */ - uint16_t packet_id; -}; - -/** - * @brief The response to a PUBREL packet. - * @ingroup unpackers - * - * @see - * MQTT v3.1.1: PUBCOMP - Publish Complete. - * - * - */ -struct mqtt_response_pubcomp { - /** T@brief he published messages packet ID. */ - uint16_t packet_id; -}; - -/** - * @brief An enumeration of subscription acknowledgement return codes. - * @ingroup unpackers - * - * @see - * MQTT v3.1.1: SUBACK Return Codes. - * - */ -enum MQTTSubackReturnCodes { - MQTT_SUBACK_SUCCESS_MAX_QOS_0 = 0u, - MQTT_SUBACK_SUCCESS_MAX_QOS_1 = 1u, - MQTT_SUBACK_SUCCESS_MAX_QOS_2 = 2u, - MQTT_SUBACK_FAILURE = 128u -}; - -/** - * @brief The response to a subscription request. - * @ingroup unpackers - * - * @see - * MQTT v3.1.1: SUBACK - Subscription Acknowledgement. - * - */ -struct mqtt_response_suback { - /** @brief The published messages packet ID. */ - uint16_t packet_id; - - /** - * Array of return codes corresponding to the requested subscribe topics. - * - * @see MQTTSubackReturnCodes - */ - const uint8_t *return_codes; - - /** The number of return codes. */ - size_t num_return_codes; -}; - -/** - * @brief The brokers response to a UNSUBSCRIBE request. - * @ingroup unpackers - * - * @see - * MQTT v3.1.1: UNSUBACK - Unsubscribe Acknowledgement. - * - */ -struct mqtt_response_unsuback { - /** @brief The published messages packet ID. */ - uint16_t packet_id; -}; - -/** - * @brief The response to a ping request. - * @ingroup unpackers - * - * @note This response contains no members. - * - * @see - * MQTT v3.1.1: PINGRESP - Ping Response. - * - */ -struct mqtt_response_pingresp { - int dummy; -}; - -/** - * @brief A struct used to deserialize/interpret an incoming packet from the broker. - * @ingroup unpackers - */ -struct mqtt_response { - /** @brief The mqtt_fixed_header of the deserialized packet. */ - struct mqtt_fixed_header fixed_header; - - /** - * @brief A union of the possible responses from the broker. - * - * @note The fixed_header contains the control type. This control type corresponds to the - * member of this union that should be accessed. For example if - * fixed_header#control_type == \c MQTT_CONTROL_PUBLISH then - * decoded#publish should be accessed. - */ - union { - struct mqtt_response_connack connack; - struct mqtt_response_publish publish; - struct mqtt_response_puback puback; - struct mqtt_response_pubrec pubrec; - struct mqtt_response_pubrel pubrel; - struct mqtt_response_pubcomp pubcomp; - struct mqtt_response_suback suback; - struct mqtt_response_unsuback unsuback; - struct mqtt_response_pingresp pingresp; - } decoded; -}; - -/** - * @brief Deserialize the contents of \p buf into an mqtt_fixed_header object. - * @ingroup unpackers - * - * @note This function performs complete error checking and a positive return value - * means the entire mqtt_response can be deserialized from \p buf. - * - * @param[out] response the response who's \ref mqtt_response.fixed_header will be initialized. - * @param[in] buf the buffer. - * @param[in] bufsz the total number of bytes in the buffer. - * - * @returns The number of bytes that were consumed, or 0 if the buffer does not contain enough - * bytes to parse the packet, or a negative value if there was a protocol violation. - */ -ssize_t mqtt_unpack_fixed_header(struct mqtt_response *response, const uint8_t *buf, size_t bufsz); - -/** - * @brief Deserialize a CONNACK response from \p buf. - * @ingroup unpackers - * - * @pre \ref mqtt_unpack_fixed_header must have returned a positive value and the control packet type - * must be \c MQTT_CONTROL_CONNACK. - * - * @param[out] mqtt_response the mqtt_response that will be initialized. - * @param[in] buf the buffer that contains the variable header and payload of the packet. The - * first byte of \p buf should be the first byte of the variable header. - * - * @relates mqtt_response_connack - * - * @returns The number of bytes that were consumed, or 0 if the buffer does not contain enough - * bytes to parse the packet, or a negative value if there was a protocol violation. - */ -ssize_t mqtt_unpack_connack_response (struct mqtt_response *mqtt_response, const uint8_t *buf); - -/** - * @brief Deserialize a publish response from \p buf. - * @ingroup unpackers - * - * @pre \ref mqtt_unpack_fixed_header must have returned a positive value and the mqtt_response must - * have a control type of \c MQTT_CONTROL_PUBLISH. - * - * @param[out] mqtt_response the response that is initialized from the contents of \p buf. - * @param[in] buf the buffer with the incoming data. - * - * @relates mqtt_response_publish - * - * @returns The number of bytes that were consumed, or 0 if the buffer does not contain enough - * bytes to parse the packet, or a negative value if there was a protocol violation. - */ -ssize_t mqtt_unpack_publish_response (struct mqtt_response *mqtt_response, const uint8_t *buf); - -/** - * @brief Deserialize a PUBACK/PUBREC/PUBREL/PUBCOMP packet from \p buf. - * @ingroup unpackers - * - * @pre \ref mqtt_unpack_fixed_header must have returned a positive value and the mqtt_response must - * have a control type of \c MQTT_CONTROL_PUBACK, \c MQTT_CONTROL_PUBREC, \c MQTT_CONTROL_PUBREL - * or \c MQTT_CONTROL_PUBCOMP. - * - * @param[out] mqtt_response the response that is initialized from the contents of \p buf. - * @param[in] buf the buffer with the incoming data. - * - * @relates mqtt_response_puback mqtt_response_pubrec mqtt_response_pubrel mqtt_response_pubcomp - * - * @returns The number of bytes that were consumed, or 0 if the buffer does not contain enough - * bytes to parse the packet, or a negative value if there was a protocol violation. - */ -ssize_t mqtt_unpack_pubxxx_response(struct mqtt_response *mqtt_response, const uint8_t *buf); - -/** - * @brief Deserialize a SUBACK packet from \p buf. - * @ingroup unpacker - * - * @pre \ref mqtt_unpack_fixed_header must have returned a positive value and the mqtt_response must - * have a control type of \c MQTT_CONTROL_SUBACK. - * - * @param[out] mqtt_response the response that is initialized from the contents of \p buf. - * @param[in] buf the buffer with the incoming data. - * - * @relates mqtt_response_suback - * - * @returns The number of bytes that were consumed, or 0 if the buffer does not contain enough - * bytes to parse the packet, or a negative value if there was a protocol violation. - */ -ssize_t mqtt_unpack_suback_response(struct mqtt_response *mqtt_response, const uint8_t *buf); - -/** - * @brief Deserialize an UNSUBACK packet from \p buf. - * @ingroup unpacker - * - * @pre \ref mqtt_unpack_fixed_header must have returned a positive value and the mqtt_response must - * have a control type of \c MQTT_CONTROL_UNSUBACK. - * - * @param[out] mqtt_response the response that is initialized from the contents of \p buf. - * @param[in] buf the buffer with the incoming data. - * - * @relates mqtt_response_unsuback - * - * @returns The number of bytes that were consumed, or 0 if the buffer does not contain enough - * bytes to parse the packet, or a negative value if there was a protocol violation. - */ -ssize_t mqtt_unpack_unsuback_response(struct mqtt_response *mqtt_response, const uint8_t *buf); - -/** - * @brief Deserialize a packet from the broker. - * @ingroup unpackers - * - * @param[out] response the mqtt_response that will be initialize from \p buf. - * @param[in] buf the incoming data buffer. - * @param[in] bufsz the number of bytes available in the buffer. - * - * @relates mqtt_response - * - * @returns The number of bytes consumed on success, zero \p buf does not contain enough bytes - * to deserialize the packet, a negative value if a protocol violation was encountered. - */ -ssize_t mqtt_unpack_response(struct mqtt_response* response, const uint8_t *buf, size_t bufsz); - -/* REQUESTS */ - - /** - * @brief Serialize an mqtt_fixed_header and write it to \p buf. - * @ingroup packers - * - * @note This function performs complete error checking and a positive return value - * guarantees the entire packet will fit into the given buffer. - * - * @param[out] buf the buffer to write to. - * @param[in] bufsz the maximum number of bytes that can be put in to \p buf. - * @param[in] fixed_header the fixed header that will be serialized. - * - * @returns The number of bytes written to \p buf, or 0 if \p buf is too small, or a - * negative value if there was a protocol violation. - */ -ssize_t mqtt_pack_fixed_header(uint8_t *buf, size_t bufsz, const struct mqtt_fixed_header *fixed_header); - -/** - * @brief An enumeration of CONNECT packet flags. - * @ingroup packers - * - * @see - * MQTT v3.1.1: CONNECT Variable Header. - * - */ -enum MQTTConnectFlags { - MQTT_CONNECT_RESERVED = 1u, - MQTT_CONNECT_CLEAN_SESSION = 2u, - MQTT_CONNECT_WILL_FLAG = 4u, - MQTT_CONNECT_WILL_QOS_0 = (0u & 0x03) << 3, - MQTT_CONNECT_WILL_QOS_1 = (1u & 0x03) << 3, - MQTT_CONNECT_WILL_QOS_2 = (2u & 0x03) << 3, - MQTT_CONNECT_WILL_RETAIN = 32u, - MQTT_CONNECT_PASSWORD = 64u, - MQTT_CONNECT_USER_NAME = 128u, - MQTT_CONNECT_CAFILEPATH = 256u, - MQTT_CONNECT_CAPATH = 512u, - MQTT_CONNECT_CLIENTCERTPATH = 1024u, - MQTT_CONNECT_CLIENTKEYPATH = 2048u, - MQTT_CONNECT_USETLS = 4096u -}; - -/** - * @brief Serialize a connection request into a buffer. - * @ingroup packers - * - * @param[out] buf the buffer to pack the connection request packet into. - * @param[in] bufsz the number of bytes left in \p buf. - * @param[in] client_id the ID that identifies the local client. \p client_id can be NULL or an empty - * string for Anonymous clients. - * @param[in] will_topic the topic under which the local client's will message will be published. - * Set to \c NULL for no will message. If \p will_topic is not \c NULL a - * \p will_message must also be provided. - * @param[in] will_message the will message to be published upon an unsuccessful disconnection of - * the local client. Set to \c NULL if \p will_topic is \c NULL. - * \p will_message must \em not be \c NULL if \p will_topic is not - * \c NULL. - * @param[in] will_message_size The size of \p will_message in bytes. - * @param[in] user_name the username to be used to connect to the broker with. Set to \c NULL if - * no username is required. - * @param[in] password the password to be used to connect to the broker with. Set to \c NULL if - * no password is required. - * @param[in] connect_flags additional MQTTConnectFlags to be set. The only flags that need to be - * set manually are \c MQTT_CONNECT_CLEAN_SESSION, - * \c MQTT_CONNECT_WILL_QOS_X (for \c X ∈ {0, 1, 2}), and - * \c MQTT_CONNECT_WILL_RETAIN. Set to 0 if no additional flags are - * required. - * @param[in] keep_alive the keep alive time in seconds. It is the responsibility of the clinet - * to ensure packets are sent to the server \em {at least} this frequently. - * - * @note If there is a \p will_topic and no additional \p connect_flags are given, then by - * default \p will_message will be published at QoS level 0. - * - * @see - * MQTT v3.1.1: CONNECT - Client Requests a Connection to a Server. - * - * - * @returns The number of bytes put into \p buf, 0 if \p buf is too small to fit the CONNECT - * packet, a negative value if there was a protocol violation. - */ -ssize_t mqtt_pack_connection_request(uint8_t* buf, size_t bufsz, - const char* client_id, - const char* will_topic, - const void* will_message, - size_t will_message_size, - const char* user_name, - const char* password, -#ifdef UA_ENABLE_MQTT_TLS_OPENSSL - const char* caFilePath, - const char* caPath, - const char* clientCertPath, - const char* clientKeyPath, - const bool useTLS, -#endif - uint8_t connect_flags, - uint16_t keep_alive); - -/** - * @brief An enumeration of the PUBLISH flags. - * @ingroup packers - * - * @see - * MQTT v3.1.1: PUBLISH - Publish Message. - * - */ -enum MQTTPublishFlags { - MQTT_PUBLISH_DUP = 8u, - MQTT_PUBLISH_QOS_0 = ((0u << 1) & 0x06), - MQTT_PUBLISH_QOS_1 = ((1u << 1) & 0x06), - MQTT_PUBLISH_QOS_2 = ((2u << 1) & 0x06), - MQTT_PUBLISH_QOS_MASK = ((3u << 1) & 0x06), - MQTT_PUBLISH_RETAIN = 0x01 -}; - -/** - * @brief Serialize a PUBLISH request and put it in \p buf. - * @ingroup packers - * - * @param[out] buf the buffer to put the PUBLISH packet in. - * @param[in] bufsz the maximum number of bytes that can be put into \p buf. - * @param[in] topic_name the topic to publish \p application_message under. - * @param[in] packet_id this packets packet ID. - * @param[in] application_message the application message to be published. - * @param[in] application_message_size the size of \p application_message in bytes. - * @param[in] publish_flags The flags to publish \p application_message with. These include - * the \c MQTT_PUBLISH_DUP flag, \c MQTT_PUBLISH_QOS_X (\c X ∈ - * {0, 1, 2}), and \c MQTT_PUBLISH_RETAIN flag. - * - * @note The default QoS is level 0. - * - * @see - * MQTT v3.1.1: PUBLISH - Publish Message. - * - * - * @returns The number of bytes put into \p buf, 0 if \p buf is too small to fit the PUBLISH - * packet, a negative value if there was a protocol violation. - */ -ssize_t mqtt_pack_publish_request(uint8_t *buf, size_t bufsz, - const char* topic_name, - uint16_t packet_id, - void* application_message, - size_t application_message_size, - uint8_t publish_flags); - -/** - * @brief Serialize a PUBACK, PUBREC, PUBREL, or PUBCOMP packet and put it in \p buf. - * @ingroup packers - * - * @param[out] buf the buffer to put the PUBXXX packet in. - * @param[in] bufsz the maximum number of bytes that can be put into \p buf. - * @param[in] control_type the type of packet. Must be one of: \c MQTT_CONTROL_PUBACK, - * \c MQTT_CONTROL_PUBREC, \c MQTT_CONTROL_PUBREL, - * or \c MQTT_CONTROL_PUBCOMP. - * @param[in] packet_id the packet ID of the packet being acknowledged. - * - * - * @see - * MQTT v3.1.1: PUBACK - Publish Acknowledgement. - * - * @see - * MQTT v3.1.1: PUBREC - Publish Received. - * - * @see - * MQTT v3.1.1: PUBREL - Publish Released. - * - * @see - * MQTT v3.1.1: PUBCOMP - Publish Complete. - * - * - * @returns The number of bytes put into \p buf, 0 if \p buf is too small to fit the PUBXXX - * packet, a negative value if there was a protocol violation. - */ -ssize_t mqtt_pack_pubxxx_request(uint8_t *buf, size_t bufsz, - enum MQTTControlPacketType control_type, - uint16_t packet_id); - -/** - * @brief The maximum number topics that can be subscribed to in a single call to - * mqtt_pack_subscribe_request. - * @ingroup packers - * - * @see mqtt_pack_subscribe_request - */ -#define MQTT_SUBSCRIBE_REQUEST_MAX_NUM_TOPICS 8 - -/** - * @brief Serialize a SUBSCRIBE packet and put it in \p buf. - * @ingroup packers - * - * @param[out] buf the buffer to put the SUBSCRIBE packet in. - * @param[in] bufsz the maximum number of bytes that can be put into \p buf. - * @param[in] packet_id the packet ID to be used. - * @param[in] ... \c NULL terminated list of (\c {const char *topic_name}, \c {int max_qos_level}) - * pairs. - * - * @note The variadic arguments, \p ..., \em must be followed by a \c NULL. For example: - * @code - * ssize_t n = mqtt_pack_subscribe_request(buf, bufsz, 1234, "topic_1", 0, "topic_2", 2, NULL); - * @endcode - * - * @see - * MQTT v3.1.1: SUBSCRIBE - Subscribe to Topics. - * - * - * @returns The number of bytes put into \p buf, 0 if \p buf is too small to fit the SUBSCRIBE - * packet, a negative value if there was a protocol violation. - */ -ssize_t mqtt_pack_subscribe_request(uint8_t *buf, size_t bufsz, - unsigned int packet_id, - ...); /* null terminated */ - -/** - * @brief The maximum number topics that can be subscribed to in a single call to - * mqtt_pack_unsubscribe_request. - * @ingroup packers - * - * @see mqtt_pack_unsubscribe_request - */ -#define MQTT_UNSUBSCRIBE_REQUEST_MAX_NUM_TOPICS 8 - -/** - * @brief Serialize a UNSUBSCRIBE packet and put it in \p buf. - * @ingroup packers - * - * @param[out] buf the buffer to put the UNSUBSCRIBE packet in. - * @param[in] bufsz the maximum number of bytes that can be put into \p buf. - * @param[in] packet_id the packet ID to be used. - * @param[in] ... \c NULL terminated list of \c {const char *topic_name}'s to unsubscribe from. - * - * @note The variadic arguments, \p ..., \em must be followed by a \c NULL. For example: - * @code - * ssize_t n = mqtt_pack_unsubscribe_request(buf, bufsz, 4321, "topic_1", "topic_2", NULL); - * @endcode - * - * @see - * MQTT v3.1.1: UNSUBSCRIBE - Unsubscribe from Topics. - * - * - * @returns The number of bytes put into \p buf, 0 if \p buf is too small to fit the UNSUBSCRIBE - * packet, a negative value if there was a protocol violation. - */ -ssize_t mqtt_pack_unsubscribe_request(uint8_t *buf, size_t bufsz, - unsigned int packet_id, - ...); /* null terminated */ - -/** - * @brief Serialize a PINGREQ and put it into \p buf. - * @ingroup packers - * - * @param[out] buf the buffer to put the PINGREQ packet in. - * @param[in] bufsz the maximum number of bytes that can be put into \p buf. - * - * @see - * MQTT v3.1.1: PINGREQ - Ping Request. - * - * - * @returns The number of bytes put into \p buf, 0 if \p buf is too small to fit the PINGREQ - * packet, a negative value if there was a protocol violation. - */ -ssize_t mqtt_pack_ping_request(uint8_t *buf, size_t bufsz); - -/** - * @brief Serialize a DISCONNECT and put it into \p buf. - * @ingroup packers - * - * @param[out] buf the buffer to put the DISCONNECT packet in. - * @param[in] bufsz the maximum number of bytes that can be put into \p buf. - * - * @see - * MQTT v3.1.1: DISCONNECT - Disconnect Notification. - * - * - * @returns The number of bytes put into \p buf, 0 if \p buf is too small to fit the DISCONNECT - * packet, a negative value if there was a protocol violation. - */ -ssize_t mqtt_pack_disconnect(uint8_t *buf, size_t bufsz); - - -/** - * @brief An enumeration of queued message states. - * @ingroup details - */ -enum MQTTQueuedMessageState { - MQTT_QUEUED_UNSENT, - MQTT_QUEUED_AWAITING_ACK, - MQTT_QUEUED_COMPLETE -}; - -/** - * @brief A message in a mqtt_message_queue. - * @ingroup details - */ -struct mqtt_queued_message { - /** @brief A pointer to the start of the message. */ - uint8_t *start; - - /** @brief The number of bytes in the message. */ - size_t size; - - - /** @brief The state of the message. */ - enum MQTTQueuedMessageState state; - - /** - * @brief The time at which the message was sent.. - * - * @note A timeout will only occur if the message is in - * the MQTT_QUEUED_AWAITING_ACK \c state. - */ - mqtt_pal_time_t time_sent; - - /** - * @brief The control type of the message. - */ - enum MQTTControlPacketType control_type; - - /** - * @brief The packet id of the message. - * - * @note This field is only used if the associate \c control_type has a - * \c packet_id field. - */ - uint16_t packet_id; -}; - -/** - * @brief A message queue. - * @ingroup details - * - * @note This struct is used internally to manage sending messages. - * @note The only members the user should use are \c curr and \c curr_sz. - */ -struct mqtt_message_queue { - /** - * @brief The start of the message queue's memory block. - * - * @warning This member should \em not be manually changed. - */ - void *mem_start; - - /** @brief The end of the message queue's memory block. */ - void *mem_end; - - /** - * @brief A pointer to the position in the buffer you can pack bytes at. - * - * @note Immediately after packing bytes at \c curr you \em must call - * mqtt_mq_register. - */ - uint8_t *curr; - - /** - * @brief The number of bytes that can be written to \c curr. - * - * @note curr_sz will decrease by more than the number of bytes you write to - * \c curr. This is because the mqtt_queued_message structs share the - * same memory (and thus, a mqtt_queued_message must be allocated in - * the message queue's memory whenever a new message is registered). - */ - size_t curr_sz; - - /** - * @brief The tail of the array of mqtt_queued_messages's. - * - * @note This member should not be used manually. - */ - struct mqtt_queued_message *queue_tail; -}; - -/** - * @brief Initialize a message queue. - * @ingroup details - * - * @param[out] mq The message queue to initialize. - * @param[in] buf The buffer for this message queue. - * @param[in] bufsz The number of bytes in the buffer. - * - * @relates mqtt_message_queue - */ -void mqtt_mq_init(struct mqtt_message_queue *mq, void *buf, size_t bufsz); - -/** - * @brief Clear as many messages from the front of the queue as possible. - * @ingroup details - * - * @note Calls to this function are the \em only way to remove messages from the queue. - * - * @param mq The message queue. - * - * @relates mqtt_message_queue - */ -void mqtt_mq_clean(struct mqtt_message_queue *mq); - -/** - * @brief Register a message that was just added to the buffer. - * @ingroup details - * - * @note This function should be called immediately following a call to a packer function - * that returned a positive value. The positive value (number of bytes packed) should - * be passed to this function. - * - * @param mq The message queue. - * @param[in] nbytes The number of bytes that were just packed. - * - * @note This function will step mqtt_message_queue::curr and update mqtt_message_queue::curr_sz. - * @relates mqtt_message_queue - * - * @returns The newly added struct mqtt_queued_message. - */ -struct mqtt_queued_message* mqtt_mq_register(struct mqtt_message_queue *mq, size_t nbytes); - -/** - * @brief Find a message in the message queue. - * @ingroup details - * - * @param mq The message queue. - * @param[in] control_type The control type of the message you want to find. - * @param[in] packet_id The packet ID of the message you want to find. Set to \c NULL if you - * don't want to specify a packet ID. - * - * @relates mqtt_message_queue - * @returns The found message. \c NULL if the message was not found. - */ -struct mqtt_queued_message* mqtt_mq_find(struct mqtt_message_queue *mq, enum MQTTControlPacketType control_type, uint16_t *packet_id); - -/** - * @brief Returns the mqtt_queued_message at \p index. - * @ingroup details - * - * @param mq_ptr A pointer to the message queue. - * @param index The index of the message. - * - * @returns The mqtt_queued_message at \p index. - */ -#define mqtt_mq_get(mq_ptr, index) (((struct mqtt_queued_message*) ((mq_ptr)->mem_end)) - 1 - index) - -/** - * @brief Returns the number of messages in the message queue, \p mq_ptr. - * @ingroup details - */ -#define mqtt_mq_length(mq_ptr) (((struct mqtt_queued_message*) ((mq_ptr)->mem_end)) - (mq_ptr)->queue_tail) - -/** - * @brief Used internally to recalculate the \c curr_sz. - * @ingroup details - */ -#define mqtt_mq_currsz(mq_ptr) (mq_ptr->curr >= (uint8_t*) ((mq_ptr)->queue_tail - 1)) ? 0 : ((uint8_t*) ((mq_ptr)->queue_tail - 1)) - (mq_ptr)->curr - -/* CLIENT */ - -/** - * @brief An MQTT client. - * @ingroup details - * - * @note All members can be manipulated via the related functions. - */ -struct mqtt_client { - /** @brief The socket connecting to the MQTT broker. */ - mqtt_pal_socket_handle socketfd; - - /** @brief The LFSR state used to generate packet ID's. */ - uint16_t pid_lfsr; - - /** @brief The keep-alive time in seconds. */ - uint16_t keep_alive; - - /** - * @brief A counter counting pings that have been sent to keep the connection alive. - * @see keep_alive - */ - int number_of_keep_alives; - - /** - * @brief The current sent offset. - * - * This is used to allow partial send commands. - */ - size_t send_offset; - - /** - * @brief The timestamp of the last message sent to the buffer. - * - * This is used to detect the need for keep-alive pings. - * - * @see keep_alive - */ - mqtt_pal_time_t time_of_last_send; - - /** - * @brief The error state of the client. - * - * error should be MQTT_OK for the entirety of the connection. - * - * @note The error state will be MQTT_ERROR_CONNECT_NOT_CALLED until - * you call mqtt_connect. - */ - enum MQTTErrors error; - - /** - * @brief The timeout period in seconds. - * - * If the broker doesn't return an ACK within response_timeout seconds a timeout - * will occur and the message will be retransmitted. - * - * @note The default value is 30 [seconds] but you can change it at any time. - */ - int response_timeout; - - /** @brief A counter counting the number of timeouts that have occurred. */ - int number_of_timeouts; - - /** - * @brief Approximately much time it has typically taken to receive responses from the - * broker. - * - * @note This is tracked using an exponential-averaging. - */ - double typical_response_time; - - /** - * @brief The callback that is called whenever a publish is received from the broker. - * - * Any topics that you have subscribed to will be returned from the broker as - * mqtt_response_publish messages. All the publishes received from the broker will - * be passed to this function. - * - * @note A pointer to publish_response_callback_state is always passed to the callback. - * Use publish_response_callback_state to keep track of any state information you - * need. - */ - void (*publish_response_callback)(void** state, struct mqtt_response_publish *publish); - - /** - * @brief A pointer to any publish_response_callback state information you need. - * - * @note A pointer to this pointer will always be publish_response_callback upon - * receiving a publish message from the broker. - */ - void* publish_response_callback_state; - - /** - * @brief A user-specified callback, triggered on each \ref mqtt_sync, allowing - * the user to perform state inspections (and custom socket error detection) - * on the client. - * - * This callback is triggered on each call to \ref mqtt_sync. If it returns MQTT_OK - * then \ref mqtt_sync will continue normally (performing reads and writes). If it - * returns an error then \ref mqtt_sync will not call reads and writes. - * - * This callback can be used to perform custom error detection, namely platform - * specific socket error detection, and force the client into an error state. - * - * This member is always initialized to NULL but it can be manually set at any - * time. - */ - enum MQTTErrors (*inspector_callback)(struct mqtt_client*); - - /** - * @brief A callback that is called whenever the client is in an error state. - * - * This callback is responsible for: application level error handling, closing - * previous sockets, and reestabilishing the connection to the broker and - * session configurations (i.e. subscriptions). - */ - void (*reconnect_callback)(struct mqtt_client*, void**); - - /** - * @brief A pointer to some state. A pointer to this member is passed to - * \ref mqtt_client.reconnect_callback. - */ - void* reconnect_state; - - /** - * @brief The buffer where ingress data is temporarily stored. - */ - struct { - /** @brief The start of the receive buffer's memory. */ - uint8_t *mem_start; - - /** @brief The size of the receive buffer's memory. */ - size_t mem_size; - - /** @brief A pointer to the next writtable location in the receive buffer. */ - uint8_t *curr; - - /** @brief The number of bytes that are still writable at curr. */ - size_t curr_sz; - } recv_buffer; - - /** - * @brief A variable passed to support thread-safety. - * - * A pointer to this variable is passed to \c MQTT_PAL_MUTEX_LOCK, and - * \c MQTT_PAL_MUTEX_UNLOCK. - */ - mqtt_pal_mutex_t mutex; - - /** @brief The sending message queue. */ - struct mqtt_message_queue mq; -}; - -/** - * @brief Generate a new next packet ID. - * @ingroup details - * - * Packet ID's are generated using a max-length LFSR. - * - * @param client The MQTT client. - * - * @returns The new packet ID that should be used. - */ -uint16_t __mqtt_next_pid(struct mqtt_client *client); - -/** - * @brief Handles egress client traffic. - * @ingroup details - * - * @param client The MQTT client. - * - * @returns MQTT_OK upon success, an \ref MQTTErrors otherwise. - */ -ssize_t __mqtt_send(struct mqtt_client *client); - -/** - * @brief Handles ingress client traffic. - * @ingroup details - * - * @param client The MQTT client. - * - * @returns MQTT_OK upon success, an \ref MQTTErrors otherwise. - */ -ssize_t __mqtt_recv(struct mqtt_client *client); - -/** - * @brief Function that does the actual sending and receiving of - * traffic from the network. - * @ingroup api - * - * All the other functions in the @ref api simply stage messages for - * being sent to the broker. This function does the actual sending of - * those messages. Additionally this function receives traffic (responses and - * acknowledgements) from the broker and responds to that traffic accordingly. - * Lastly this function also calls the \c publish_response_callback when - * any \c MQTT_CONTROL_PUBLISH messages are received. - * - * @pre mqtt_init must have been called. - * - * @param[in,out] client The MQTT client. - * - * @attention It is the responsibility of the application programmer to - * call this function periodically. All functions in the @ref api are - * thread-safe so it is perfectly reasonable to have a thread dedicated - * to calling this function every 200 ms or so. MQTT-C can be used in single - * threaded application though by simply calling this functino periodically - * inside your main thread. See @ref simple_publisher.c and @ref simple_subscriber.c - * for examples (specifically the \c client_refresher functions). - * - * @returns MQTT_OK upon success, an \ref MQTTErrors otherwise. - */ -enum MQTTErrors mqtt_sync(struct mqtt_client *client); - -/** - * @brief Initializes an MQTT client. - * @ingroup api - * - * This function \em must be called before any other API function calls. - * - * @pre None. - * - * @param[out] client The MQTT client. - * @param[in] sockfd The socket file descriptor (or equivalent socket handle, e.g. BIO pointer - * for OpenSSL sockets) connected to the MQTT broker. - * @param[in] sendbuf A buffer that will be used for sending messages to the broker. - * @param[in] sendbufsz The size of \p sendbuf in bytes. - * @param[in] recvbuf A buffer that will be used for receiving messages from the broker. - * @param[in] recvbufsz The size of \p recvbuf in bytes. - * @param[in] publish_response_callback The callback to call whenever application messages - * are received from the broker. - * - * @post mqtt_connect must be called. - * - * @note \p sockfd is a non-blocking TCP connection. - * @note If \p sendbuf fills up completely during runtime a \c MQTT_ERROR_SEND_BUFFER_IS_FULL - * error will be set. Similarly if \p recvbuf is ever to small to receive a message from - * the broker an MQTT_ERROR_RECV_BUFFER_TOO_SMALL error will be set. - * @note A pointer to \ref mqtt_client.publish_response_callback_state is always passed as the - * \c state argument to \p publish_response_callback. Note that the second argument is - * the mqtt_response_publish that was received from the broker. - * - * @attention Only initialize an MQTT client once (i.e. don't call \ref mqtt_init or - * \ref mqtt_init_reconnect more than once per client). - * - * @returns \c MQTT_OK upon success, an \ref MQTTErrors otherwise. - */ -enum MQTTErrors mqtt_init(struct mqtt_client *client, - mqtt_pal_socket_handle sockfd, - uint8_t *sendbuf, size_t sendbufsz, - uint8_t *recvbuf, size_t recvbufsz, - void (*publish_response_callback)(void** state, struct mqtt_response_publish *publish)); - -/** - * @brief Initializes an MQTT client and enables automatic reconnections. - * @ingroup api - * - * An alternative to \ref mqtt_init that allows the client to automatically reconnect to the - * broker after an error occurs (e.g. socket error or internal buffer overflows). - * - * This is accomplished by calling the \p reconnect_callback whenever the client enters an error - * state. The job of the \p reconnect_callback is to: (1) perform error handling/logging, - * (2) clean up the old connection (i.e. close client->socketfd), (3) \ref mqtt_reinit the - * client, and (4) reconfigure the MQTT session by calling \ref mqtt_connect followed by other - * API calls such as \ref mqtt_subscribe. - * - * The first argument to the \p reconnect_callback is the client (which will be in an error - * state) and the second argument is a pointer to a void pointer where you can store some state - * information. Internally, MQTT-C calls the reconnect callback like so: - * - * \code - * client->reconnect_callback(client, &client->reconnect_state) - * \endcode - * - * Note that the \p reconnect_callback is also called to setup the initial session. After - * calling \ref mqtt_init_reconnect the client will be in the error state - * \c MQTT_ERROR_INITIAL_RECONNECT. - * - * @pre None. - * - * @param[in,out] client The MQTT client that will be initialized. - * @param[in] reconnect_callback The callback that will be called to connect/reconnect the - * client to the broker and perform application level error handling. - * @param[in] reconnect_state A pointer to some state data for your \p reconnect_callback. - * If your \p reconnect_callback does not require any state information set this - * to NULL. A pointer to the memory address where the client stores a copy of this - * pointer is passed as the second argumnet to \p reconnect_callback. - * @param[in] publish_response_callback The callback to call whenever application messages - * are received from the broker. - * - * @post Call \p reconnect_callback yourself, or call \ref mqtt_sync - * (which will trigger the call to \p reconnect_callback). - * - * @attention Only initialize an MQTT client once (i.e. don't call \ref mqtt_init or - * \ref mqtt_init_reconnect more than once per client). - * - */ -void mqtt_init_reconnect(struct mqtt_client *client, - void (*reconnect_callback)(struct mqtt_client *client, void** state), - void *reconnect_state, - void (*publish_response_callback)(void** state, struct mqtt_response_publish *publish)); - -/** - * @brief Safely assign/reassign a socket and buffers to an new/existing client. - * @ingroup api - * - * This function also clears the \p client error state. Upon exiting this function - * \c client->error will be \c MQTT_ERROR_CONNECT_NOT_CALLED (which will be cleared) - * as soon as \ref mqtt_connect is called. - * - * @pre This function must be called BEFORE \ref mqtt_connect. - * - * @param[in,out] client The MQTT client. - * @param[in] socketfd The new socket connected to the broker. - * @param[in] sendbuf The buffer that will be used to buffer egress traffic to the broker. - * @param[in] sendbufsz The size of \p sendbuf in bytes. - * @param[in] recvbuf The buffer that will be used to buffer ingress traffic from the broker. - * @param[in] recvbufsz The size of \p recvbuf in bytes. - * - * @post Call \ref mqtt_connect. - * - * @attention This function should be used in conjunction with clients that have been - * initialzed with \ref mqtt_init_reconnect. - */ -void mqtt_reinit(struct mqtt_client* client, - mqtt_pal_socket_handle socketfd, - uint8_t *sendbuf, size_t sendbufsz, - uint8_t *recvbuf, size_t recvbufsz); - -/** - * @brief Establishes a session with the MQTT broker. - * @ingroup api - * - * @pre mqtt_init must have been called. - * - * @param[in,out] client The MQTT client. - * @param[in] client_id The unique name identifying the client. (or NULL) - * @param[in] will_topic The topic name of client's \p will_message. If no will message is - * desired set to \c NULL. - * @param[in] will_message The application message (data) to be published in the event the - * client ungracefully disconnects. Set to \c NULL if \p will_topic is \c NULL. - * @param[in] will_message_size The size of \p will_message in bytes. - * @param[in] user_name The username to use when establishing the session with the MQTT broker. - * Set to \c NULL if a username is not required. - * @param[in] password The password to use when establishing the session with the MQTT broker. - * Set to \c NULL if a password is not required. - * @param[in] connect_flags Additional \ref MQTTConnectFlags to use when establishing the connection. - * These flags are for forcing the session to start clean, - * \c MQTT_CONNECT_CLEAN_SESSION, the QOS level to publish the \p will_message with - * (provided \c will_message != \c NULL), MQTT_CONNECT_WILL_QOS_[0,1,2], and whether - * or not the broker should retain the \c will_message, MQTT_CONNECT_WILL_RETAIN. - * @param[in] keep_alive The keep-alive time in seconds. A reasonable value for this is 400 [seconds]. - * - * @returns \c MQTT_OK upon success, an \ref MQTTErrors otherwise. - */ -enum MQTTErrors mqtt_connect(struct mqtt_client *client, - const char* client_id, - const char* will_topic, - const void* will_message, - size_t will_message_size, - const char* user_name, - const char* password, -#ifdef UA_ENABLE_MQTT_TLS_OPENSSL - const char* caFilePath, - const char* caPath, - const char* clientCertPath, - const char* clientKeyPath, - bool useTLS, -#endif - uint8_t connect_flags, - uint16_t keep_alive); - -/* - todo: will_message should be a void* -*/ - -/** - * @brief Publish an application message. - * @ingroup api - * - * Publishes an application message to the MQTT broker. - * - * @pre mqtt_connect must have been called. - * - * @param[in,out] client The MQTT client. - * @param[in] topic_name The name of the topic. - * @param[in] application_message The data to be published. - * @param[in] application_message_size The size of \p application_message in bytes. - * @param[in] publish_flags \ref MQTTPublishFlags to be used, namely the QOS level to - * publish at (MQTT_PUBLISH_QOS_[0,1,2]) or whether or not the broker should - * retain the publish (MQTT_PUBLISH_RETAIN). - * - * @returns \c MQTT_OK upon success, an \ref MQTTErrors otherwise. - */ -enum MQTTErrors mqtt_publish(struct mqtt_client *client, - const char* topic_name, - void* application_message, - size_t application_message_size, - uint8_t publish_flags); - -/** - * @brief Acknowledge an ingree publish with QOS==1. - * @ingroup details - * - * @param[in,out] client The MQTT client. - * @param[in] packet_id The packet ID of the ingress publish being acknowledged. - * - * @returns \c MQTT_OK upon success, an \ref MQTTErrors otherwise. - */ -ssize_t __mqtt_puback(struct mqtt_client *client, uint16_t packet_id); - -/** - * @brief Acknowledge an ingree publish with QOS==2. - * @ingroup details - * - * @param[in,out] client The MQTT client. - * @param[in] packet_id The packet ID of the ingress publish being acknowledged. - * - * @returns \c MQTT_OK upon success, an \ref MQTTErrors otherwise. - */ -ssize_t __mqtt_pubrec(struct mqtt_client *client, uint16_t packet_id); - -/** - * @brief Acknowledge an ingree PUBREC packet. - * @ingroup details - * - * @param[in,out] client The MQTT client. - * @param[in] packet_id The packet ID of the ingress PUBREC being acknowledged. - * - * @returns \c MQTT_OK upon success, an \ref MQTTErrors otherwise. - */ -ssize_t __mqtt_pubrel(struct mqtt_client *client, uint16_t packet_id); - -/** - * @brief Acknowledge an ingree PUBREL packet. - * @ingroup details - * - * @param[in,out] client The MQTT client. - * @param[in] packet_id The packet ID of the ingress PUBREL being acknowledged. - * - * @returns \c MQTT_OK upon success, an \ref MQTTErrors otherwise. - */ -ssize_t __mqtt_pubcomp(struct mqtt_client *client, uint16_t packet_id); - - -/** - * @brief Subscribe to a topic. - * @ingroup api - * - * @pre mqtt_connect must have been called. - * - * @param[in,out] client The MQTT client. - * @param[in] topic_name The name of the topic to subscribe to. - * @param[in] max_qos_level The maximum QOS level with which the broker can send application - * messages for this topic. - * - * @returns \c MQTT_OK upon success, an \ref MQTTErrors otherwise. - */ -enum MQTTErrors mqtt_subscribe(struct mqtt_client *client, - const char* topic_name, - int max_qos_level); - -/** - * @brief Unsubscribe from a topic. - * @ingroup api - * - * @pre mqtt_connect must have been called. - * - * @param[in,out] client The MQTT client. - * @param[in] topic_name The name of the topic to unsubscribe from. - * - * @returns \c MQTT_OK upon success, an \ref MQTTErrors otherwise. - */ -enum MQTTErrors mqtt_unsubscribe(struct mqtt_client *client, - const char* topic_name); - -/** - * @brief Ping the broker. - * @ingroup api - * - * @pre mqtt_connect must have been called. - * - * @param[in,out] client The MQTT client. - * - * @returns \c MQTT_OK upon success, an \ref MQTTErrors otherwise. - */ -enum MQTTErrors mqtt_ping(struct mqtt_client *client); - -/** - * @brief Ping the broker without locking/unlocking the mutex. - * @see mqtt_ping - */ -enum MQTTErrors __mqtt_ping(struct mqtt_client *client); - -/** - * @brief Terminate the session with the MQTT broker. - * @ingroup api - * - * @pre mqtt_connect must have been called. - * - * @param[in,out] client The MQTT client. - * - * @note To re-establish the session, mqtt_connect must be called. - * - * @returns \c MQTT_OK upon success, an \ref MQTTErrors otherwise. - */ -enum MQTTErrors mqtt_disconnect(struct mqtt_client *client); - -#endif diff --git a/deps/mqtt-c/mqtt_pal.h b/deps/mqtt-c/mqtt_pal.h deleted file mode 100644 index c8581c174..000000000 --- a/deps/mqtt-c/mqtt_pal.h +++ /dev/null @@ -1,115 +0,0 @@ -#ifndef __MQTT_PAL_H__ -#define __MQTT_PAL_H__ - -/** - * Copyright (c) 2019 Kalycito Infotech Private Limited - * @file - * @brief Includes/supports the types/calls required by the MQTT-C client. - * - * @note This is the \em only file included in mqtt.h, and mqtt.c. It is therefore - * responsible for including/supporting all the required types and calls. - * - * @defgroup pal Platform abstraction layer - * @brief Documentation of the types and calls required to port MQTT-C to a new platform. - * - * mqtt_pal.h is the \em only header file included in mqtt.c. Therefore, to port MQTT-C to a - * new platform the following types, functions, constants, and macros must be defined in - * mqtt_pal.h: - * - Types: - * - \c size_t, \c ssize_t - * - \c uint8_t, \c uint16_t, \c uint32_t - * - \c va_list - * - \c mqtt_pal_time_t : return type of \c MQTT_PAL_TIME() - * - \c mqtt_pal_mutex_t : type of the argument that is passed to \c MQTT_PAL_MUTEX_LOCK and - * \c MQTT_PAL_MUTEX_RELEASE - * - Functions: - * - \c memcpy, \c strlen - * - \c va_start, \c va_arg, \c va_end - * - Constants: - * - \c INT_MIN - * - * Additionally, three macro's are required: - * - \c MQTT_PAL_HTONS(s) : host-to-network endian conversion for uint16_t. - * - \c MQTT_PAL_NTOHS(s) : network-to-host endian conversion for uint16_t. - * - \c MQTT_PAL_TIME() : returns [type: \c mqtt_pal_time_t] current time in seconds. - * - \c MQTT_PAL_MUTEX_LOCK(mtx_pointer) : macro that locks the mutex pointed to by \c mtx_pointer. - * - \c MQTT_PAL_MUTEX_RELEASE(mtx_pointer) : macro that unlocks the mutex pointed to by - * \c mtx_pointer. - * - * Lastly, \ref mqtt_pal_sendall and \ref mqtt_pal_recvall, must be implemented in mqtt_pal.c - * for sending and receiving data using the platforms socket calls. - */ - -#include - -/* UNIX-like platform support */ -#ifdef __unix__ - #include - #include - #include - #include - #include - #include - - /*#ifdef MQTT_USE_BIO - #include - typedef BIO* mqtt_pal_socket_handle; - #else - typedef int mqtt_pal_socket_handle; - #endif*/ -#endif - -#ifdef __MINGW32__ - #include -#endif - - - #define MQTT_PAL_HTONS(s) htons(s) - #define MQTT_PAL_NTOHS(s) ntohs(s) - - #define MQTT_PAL_TIME() time(NULL) - - typedef time_t mqtt_pal_time_t; - typedef pthread_mutex_t mqtt_pal_mutex_t; - - #define MQTT_PAL_MUTEX_INIT(mtx_ptr) pthread_mutex_init(mtx_ptr, NULL) - #define MQTT_PAL_MUTEX_LOCK(mtx_ptr) pthread_mutex_lock(mtx_ptr) - #define MQTT_PAL_MUTEX_UNLOCK(mtx_ptr) pthread_mutex_unlock(mtx_ptr) - - struct my_custom_socket_handle { - void* client; - void* connection; -#ifdef UA_ENABLE_MQTT_TLS - void* tls; -#endif - uint16_t timeout; - }; - - typedef struct my_custom_socket_handle* mqtt_pal_socket_handle; -/** - * @brief Sends all the bytes in a buffer. - * @ingroup pal - * - * @param[in] fd The file-descriptor (or handle) of the socket. - * @param[in] buf A pointer to the first byte in the buffer to send. - * @param[in] len The number of bytes to send (starting at \p buf). - * @param[in] flags Flags which are passed to the underlying socket. - * - * @returns The number of bytes sent if successful, an \ref MQTTErrors otherwise. - */ -ssize_t mqtt_pal_sendall(mqtt_pal_socket_handle fd, const void* buf, size_t len, int flags); - -/** - * @brief Non-blocking receive all the byte available. - * @ingroup pal - * - * @param[in] fd The file-descriptor (or handle) of the socket. - * @param[in] buf A pointer to the receive buffer. - * @param[in] bufsz The max number of bytes that can be put into \p buf. - * @param[in] flags Flags which are passed to the underlying socket. - * - * @returns The number of bytes received if successful, an \ref MQTTErrors otherwise. - */ -ssize_t mqtt_pal_recvall(mqtt_pal_socket_handle fd, void* buf, size_t bufsz, int flags); - -#endif /* __MQTT_PAL_H__ */ diff --git a/include/open62541/config.h.in b/include/open62541/config.h.in index 3a546e5d3..8167a4071 100644 --- a/include/open62541/config.h.in +++ b/include/open62541/config.h.in @@ -44,6 +44,7 @@ #cmakedefine UA_ENABLE_PUBSUB_MQTT #cmakedefine UA_ENABLE_MQTT_TLS #cmakedefine UA_ENABLE_MQTT_TLS_OPENSSL +#cmakedefine UA_ENABLE_MQTT_TLS_MBEDTLS #cmakedefine UA_ENABLE_ENCRYPTION_MBEDTLS #cmakedefine UA_ENABLE_TPM2_SECURITY #cmakedefine UA_ENABLE_ENCRYPTION_OPENSSL diff --git a/plugins/ua_network_pubsub_mqtt.h b/plugins/include/open62541/plugin/pubsub_mqtt.h similarity index 78% rename from plugins/ua_network_pubsub_mqtt.h rename to plugins/include/open62541/plugin/pubsub_mqtt.h index 4285faa51..809e93ba6 100644 --- a/plugins/ua_network_pubsub_mqtt.h +++ b/plugins/include/open62541/plugin/pubsub_mqtt.h @@ -12,11 +12,13 @@ extern "C" { #endif -#include "open62541/plugin/pubsub.h" -#include "open62541/network_tcp.h" +#include +#include -#ifdef UA_ENABLE_MQTT_TLS +#if defined(UA_ENABLE_MQTT_TLS_OPENSSL) #include +#elif defined(UA_ENABLE_MQTT_TLS_MBEDTLS) +#include #endif /* mqtt network layer specific internal data */ @@ -28,8 +30,12 @@ typedef struct { uint8_t *mqttRecvBuffer; UA_String *mqttClientId; UA_Connection *connection; -#ifdef UA_ENABLE_MQTT_TLS_OPENSSL - SSL *ssl; +#if defined(UA_ENABLE_MQTT_TLS_OPENSSL) + BIO *sockfd; +#elif defined(UA_ENABLE_MQTT_TLS_MBEDTLS) + mqtt_pal_socket_handle sockfd; +#else + int sockfd; #endif void * mqttClient; void (*callback)(UA_ByteString *encodedBuffer, UA_ByteString *topic); diff --git a/plugins/mqtt/templates/mbedtls_sockets.h b/plugins/mqtt/templates/mbedtls_sockets.h new file mode 100644 index 000000000..5d3935dba --- /dev/null +++ b/plugins/mqtt/templates/mbedtls_sockets.h @@ -0,0 +1,159 @@ +#if !defined(__MBEDTLS_SOCKET_TEMPLATE_H__) +#define __MBEDTLS_SOCKET_TEMPLATE_H__ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#if !defined(MBEDTLS_NET_POLL_READ) +/* compat for older mbedtls */ +#define MBEDTLS_NET_POLL_READ 1 +#define MBEDTLS_NET_POLL_WRITE 1 + +int +mbedtls_net_poll(mbedtls_net_context * ctx, uint32_t rw, uint32_t timeout) +{ + /* XXX this is not ideal but good enough for an example */ + usleep(300); + return 1; +} +#endif + +struct mbedtls_context { + mbedtls_net_context net_ctx; + mbedtls_ssl_context ssl_ctx; + mbedtls_ssl_config ssl_conf; + mbedtls_x509_crt ca_crt; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; +}; + +UA_StatusCode failed(const char *fn, int rv); +UA_StatusCode cert_verify_failed(uint32_t rv); +UA_StatusCode open_nb_socket(struct mbedtls_context *ctx, + const char *hostname, + const char *port, + const char *ca_file); + + +UA_StatusCode failed(const char *fn, int rv) { + char buf[100]; + mbedtls_strerror(rv, buf, sizeof(buf)); + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, + "MQTT PubSub: %s failed with %x %s", fn, -rv, buf); + return UA_STATUSCODE_BADINTERNALERROR; +} + +UA_StatusCode cert_verify_failed(uint32_t rv) { + char buf[512]; + mbedtls_x509_crt_verify_info(buf, sizeof(buf), "\t", rv); + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, + "MQTT PubSub: Certificate verification failed (%x) %s", rv, buf); + return UA_STATUSCODE_BADINTERNALERROR; +} + +/* + A template for opening a non-blocking mbed TLS connection. +*/ +UA_StatusCode open_nb_socket(struct mbedtls_context *ctx, + const char *hostname, + const char *port, + const char *ca_file) { + const unsigned char *additional = (const unsigned char *)"MQTT-C"; + size_t additional_len = 6; + int rv; + + mbedtls_net_context *net_ctx = &ctx->net_ctx; + mbedtls_ssl_context *ssl_ctx = &ctx->ssl_ctx; + mbedtls_ssl_config *ssl_conf = &ctx->ssl_conf; + mbedtls_x509_crt *ca_crt = &ctx->ca_crt; + mbedtls_entropy_context *entropy = &ctx->entropy; + mbedtls_ctr_drbg_context *ctr_drbg = &ctx->ctr_drbg; + + mbedtls_entropy_init(entropy); + mbedtls_ctr_drbg_init(ctr_drbg); + rv = mbedtls_ctr_drbg_seed(ctr_drbg, mbedtls_entropy_func, entropy, + additional, additional_len); + if (rv != 0) { + return failed("mbedtls_ctr_drbg_seed", rv); + } + + mbedtls_x509_crt_init(ca_crt); + rv = mbedtls_x509_crt_parse_file(ca_crt, ca_file); + if (rv != 0) { + return failed("mbedtls_x509_crt_parse_file", rv); + } + + mbedtls_ssl_config_init(ssl_conf); + rv = mbedtls_ssl_config_defaults(ssl_conf, MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT); + if (rv != 0) { + return failed("mbedtls_ssl_config_defaults", rv); + } + mbedtls_ssl_conf_ca_chain(ssl_conf, ca_crt, NULL); + mbedtls_ssl_conf_authmode(ssl_conf, MBEDTLS_SSL_VERIFY_OPTIONAL); + mbedtls_ssl_conf_rng(ssl_conf, mbedtls_ctr_drbg_random, ctr_drbg); + + mbedtls_net_init(net_ctx); + rv = mbedtls_net_connect(net_ctx, hostname, port, MBEDTLS_NET_PROTO_TCP); + if (rv != 0) { + return failed("mbedtls_net_connect", rv); + } + rv = mbedtls_net_set_nonblock(net_ctx); + if (rv != 0) { + return failed("mbedtls_net_set_nonblock", rv); + } + + mbedtls_ssl_init(ssl_ctx); + rv = mbedtls_ssl_setup(ssl_ctx, ssl_conf); + if (rv != 0) { + return failed("mbedtls_ssl_setup", rv); + } + rv = mbedtls_ssl_set_hostname(ssl_ctx, hostname); + if (rv != 0) { + return failed("mbedtls_ssl_set_hostname", rv); + } + mbedtls_ssl_set_bio(ssl_ctx, net_ctx, + mbedtls_net_send, mbedtls_net_recv, NULL); + + for (;;) { + rv = mbedtls_ssl_handshake(ssl_ctx); + uint32_t want = 0; + if (rv == MBEDTLS_ERR_SSL_WANT_READ) { + want |= MBEDTLS_NET_POLL_READ; + } else if (rv == MBEDTLS_ERR_SSL_WANT_WRITE) { + want |= MBEDTLS_NET_POLL_WRITE; + } else { + break; + } + rv = mbedtls_net_poll(net_ctx, want, (uint32_t)-1); + if (rv < 0) { + return failed("mbedtls_net_poll", rv); + } + } + if (rv != 0) { + return failed("mbedtls_ssl_handshake", rv); + } + uint32_t result = mbedtls_ssl_get_verify_result(ssl_ctx); + if (result != 0) { + if (result == (uint32_t)-1) { + return failed("mbedtls_ssl_get_verify_result", (int)result); + } else { + cert_verify_failed(result); + } + } + return UA_STATUSCODE_GOOD; +} + +#endif diff --git a/plugins/mqtt/templates/openssl_sockets.h b/plugins/mqtt/templates/openssl_sockets.h new file mode 100644 index 000000000..c15c9231a --- /dev/null +++ b/plugins/mqtt/templates/openssl_sockets.h @@ -0,0 +1,102 @@ +#if !defined(__OPENSSL_SOCKET_TEMPLATE_H__) +#define __OPENSSL_SOCKET_TEMPLATE_H__ + +#include +#include +#include + +#include +#include +#include + +/* + A template for opening a non-blocking OpenSSL connection. +*/ +UA_StatusCode open_nb_socket(BIO** bio, + SSL_CTX** ssl_ctx, + const char* addr, + const char* port, + const char* ca_file, + const char* ca_path, + const char* cert_file, + const char* key_file); + +UA_StatusCode open_nb_socket(BIO** bio, + SSL_CTX** ssl_ctx, + const char* addr, + const char* port, + const char* ca_file, + const char* ca_path, + const char* cert_file, + const char* key_file) +{ + *ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + SSL* ssl; + + /* load certificate */ + if (!SSL_CTX_load_verify_locations(*ssl_ctx, ca_file, ca_path)) { + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, + "MQTT PubSub: Failed to load ca certificate"); + return UA_STATUSCODE_BADINTERNALERROR; + } + + if (cert_file && key_file) + { + if (!SSL_CTX_use_certificate_file(*ssl_ctx, cert_file, SSL_FILETYPE_PEM)) + { + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, + "MQTT PubSub: Failed to load client certificate"); + return UA_STATUSCODE_BADINTERNALERROR; + } + + if (!SSL_CTX_use_PrivateKey_file(*ssl_ctx, key_file, SSL_FILETYPE_PEM)) + { + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, + "MQTT PubSub: Failed to load client key"); + return UA_STATUSCODE_BADINTERNALERROR; + } + } + + /* open BIO socket */ + char * addr_copy = (char*)malloc(strlen(addr) + 1); + strcpy(addr_copy,addr); + char * port_copy = (char*)malloc(strlen(port) + 1); + strcpy(port_copy,port); + + *bio = BIO_new_ssl_connect(*ssl_ctx); + BIO_get_ssl(*bio, &ssl); + SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); + BIO_set_conn_hostname(*bio, addr_copy); + BIO_set_nbio(*bio, 1); + BIO_set_conn_port(*bio, port_copy); + + free(addr_copy); + free(port_copy); + + /* wait for connect with 10 second timeout */ + int start_time = (int)time(NULL); + int do_connect_rv = (int)BIO_do_connect(*bio); + while(do_connect_rv <= 0 && BIO_should_retry(*bio) && (int)time(NULL) - start_time < 10) { + do_connect_rv = (int)BIO_do_connect(*bio); + } + if (do_connect_rv <= 0) { + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, + "MQTT PubSub: Failed to open socket: %s", ERR_reason_error_string(ERR_get_error())); + BIO_free_all(*bio); + SSL_CTX_free(*ssl_ctx); + *bio = NULL; + *ssl_ctx=NULL; + return UA_STATUSCODE_BADINTERNALERROR; + } + + /* verify certificate */ + if (SSL_get_verify_result(ssl) != X509_V_OK) { + /* Handle the failed verification */ + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, + "MQTT PubSub: x509 certificate verification failed"); + return UA_STATUSCODE_BADINTERNALERROR; + } + return UA_STATUSCODE_GOOD; +} + +#endif diff --git a/plugins/mqtt/templates/posix_sockets.h b/plugins/mqtt/templates/posix_sockets.h new file mode 100644 index 000000000..433e80e4d --- /dev/null +++ b/plugins/mqtt/templates/posix_sockets.h @@ -0,0 +1,86 @@ +#if !defined(__POSIX_SOCKET_TEMPLATE_H__) +#define __POSIX_SOCKET_TEMPLATE_H__ + +#include +#include +#if !defined(WIN32) +#include +#include +#else +#include +#endif +#if defined(__VMS) +#include +#endif +#include + +#include +#include + +/* + A template for opening a non-blocking POSIX socket. +*/ +UA_StatusCode open_nb_socket(int* sockfd, const char* addr, const char* port); + +UA_StatusCode open_nb_socket(int* sockfd, const char* addr, const char* port) { + struct addrinfo hints = {0}; + + hints.ai_family = AF_UNSPEC; /* IPv4 or IPv6 */ + hints.ai_socktype = SOCK_STREAM; /* Must be TCP */ + *sockfd = -1; + int rv; + struct addrinfo *p, *servinfo; + + /* get address information */ + rv = getaddrinfo(addr, port, &hints, &servinfo); + if(rv != 0) { + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, + "MQTT PubSub: Failed to open socket (getaddrinfo): %s", gai_strerror(rv)); + return UA_STATUSCODE_BADINTERNALERROR; + } + + /* open the first possible socket */ + for(p = servinfo; p != NULL; p = p->ai_next) { + *sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (*sockfd == -1) continue; + + /* connect to server */ + rv = connect(*sockfd, p->ai_addr, p->ai_addrlen); + if(rv == -1) { + close(*sockfd); + *sockfd = -1; + continue; + } + break; + } + + /* free servinfo */ + freeaddrinfo(servinfo); + + /* make non-blocking */ +#if !defined(WIN32) + if (*sockfd != -1) fcntl(*sockfd, F_SETFL, fcntl(*sockfd, F_GETFL) | O_NONBLOCK); +#else + if (*sockfd != INVALID_SOCKET) { + int iMode = 1; + ioctlsocket(*sockfd, FIONBIO, &iMode); + } +#endif +#if defined(__VMS) + /* + OpenVMS only partially implements fcntl. It works on file descriptors + but silently fails on socket descriptors. So we need to fall back on + to the older ioctl system to set non-blocking IO + */ + int on = 1; + if (*sockfd != -1) ioctl(*sockfd, FIONBIO, &on); +#endif + + /* return the new socket fd */ + if(*sockfd == -1) + return UA_STATUSCODE_BADINTERNALERROR; + else + return UA_STATUSCODE_GOOD; +} + +#endif diff --git a/plugins/mqtt/ua_mqtt_adapter.c b/plugins/mqtt/ua_mqtt-c_adapter.c similarity index 52% rename from plugins/mqtt/ua_mqtt_adapter.c rename to plugins/mqtt/ua_mqtt-c_adapter.c index 8203a2f38..963ea8ff1 100644 --- a/plugins/mqtt/ua_mqtt_adapter.c +++ b/plugins/mqtt/ua_mqtt-c_adapter.c @@ -6,28 +6,39 @@ * Copyright (c) 2020 basysKom GmbH */ -#include "ua_mqtt_adapter.h" -#include "../../deps/mqtt-c/mqtt.h" -#include "open62541/plugin/log_stdout.h" -#include "open62541/util.h" +#include "ua_mqtt-c_adapter.h" +#include +#include +#include -#ifdef UA_ENABLE_MQTT_TLS_OPENSSL -#include -#include +#if defined(UA_ENABLE_MQTT_TLS_OPENSSL) +#include +#elif defined(UA_ENABLE_MQTT_TLS_MBEDTLS) +#include +#else +#include #endif +#include + /* forward decl for callback */ void publish_callback(void**, struct mqtt_response_publish*); void freeTLS(UA_PubSubChannelDataMQTT *data) { -#ifdef UA_ENABLE_MQTT_TLS_OPENSSL - if (!data->ssl) +#if defined(UA_ENABLE_MQTT_TLS_OPENSSL) + if (!data->sockfd) return; - SSL_shutdown(data->ssl); - SSL_CTX_free(SSL_get_SSL_CTX(data->ssl)); - SSL_free(data->ssl); - data->ssl = NULL; + BIO_free_all(data->sockfd); + //SSL_CTX_free(SSL_get_SSL_CTX(data->ssl)); + data->sockfd = NULL; +#elif defined(UA_ENABLE_MQTT_TLS_MBEDTLS) + if (!data->sockfd) + return; + mbedtls_ssl_free(data->sockfd); + data->sockfd = NULL; +#else + close(data->sockfd); #endif } @@ -37,7 +48,7 @@ connectMqtt(UA_PubSubChannelDataMQTT* channelData){ return UA_STATUSCODE_BADINVALIDARGUMENT; } -#if defined(UA_ENABLE_MQTT_TLS_OPENSSL) // Extend condition when mbedTLS support is added +#if defined(UA_ENABLE_MQTT_TLS_OPENSSL) || defined(UA_ENABLE_MQTT_TLS_MBEDTLS) // Extend condition when mbedTLS support is added if ((channelData->mqttClientCertPath.length && !channelData->mqttClientKeyPath.length) || (channelData->mqttClientKeyPath.length && !channelData->mqttClientCertPath.length)) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, @@ -52,8 +63,7 @@ connectMqtt(UA_PubSubChannelDataMQTT* channelData){ } #endif - /* Get address and replace mqtt with tcp - * because we use a tcp UA_ClientConnectionTCP for mqtt */ + /* Get address and remove 'opc.mqtt://' from the address */ UA_NetworkAddressUrlDataType address = channelData->address; UA_String hostname, path; @@ -64,59 +74,30 @@ connectMqtt(UA_PubSubChannelDataMQTT* channelData){ return UA_STATUSCODE_BADINVALIDARGUMENT; } - /* Build the url, replace mqtt with tcp */ - UA_STACKARRAY(UA_Byte, addressAsChar, 10 + (sizeof(char) * path.length)); - memcpy((char*)addressAsChar, "opc.tcp://", 10); - memcpy((char*)&addressAsChar[10],(char*)path.data, path.length); - address.url.data = addressAsChar; - address.url.length = 10 + (sizeof(char) * path.length); + /* Build the url and port*/ + char rest[512]; + memcpy(rest, path.data, path.length); + rest[path.length] = '\0'; + char *rest2 = rest; + const char *addr = strtok_r(rest2, ":", &rest2); + const char *port = strtok_r(rest2, ":", &rest2); - /* check if buffers are correct */ - if(!(channelData->mqttRecvBufferSize > 0 && channelData->mqttRecvBuffer != NULL - && channelData->mqttSendBufferSize > 0 && channelData->mqttSendBuffer != NULL)){ - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "MQTT PubSub Connection creation failed. No Mqtt buffer allocated."); - return UA_STATUSCODE_BADARGUMENTSMISSING; - } - - /* Config with default parameters */ - UA_ConnectionConfig conf; - memset(&conf, 0, sizeof(UA_ConnectionConfig)); - conf.protocolVersion = 0; - conf.sendBufferSize = channelData->mqttSendBufferSize; - conf.recvBufferSize = channelData->mqttRecvBufferSize; - conf.localMaxMessageSize = 1000; - conf.remoteMaxMessageSize = 1000; - conf.localMaxChunkCount = 1; - conf.remoteMaxChunkCount = 1; - - /* Create TCP connection: open the blocking TCP socket (connecting to the broker) */ - UA_Connection connection = UA_ClientConnectionTCP_init(conf, address.url, - 1000, UA_Log_Stdout); - UA_ClientConnectionTCP_poll(&connection, 1000, UA_Log_Stdout); - if(connection.state != UA_CONNECTIONSTATE_ESTABLISHED && - connection.state != UA_CONNECTIONSTATE_OPENING){ - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_NETWORK, - "PubSub MQTT: Connection creation failed. Tcp connection failed!"); - return UA_STATUSCODE_BADCOMMUNICATIONERROR; - } - - /* save connection */ - channelData->connection = (UA_Connection*)UA_calloc(1, sizeof(UA_Connection)); - if(!channelData->connection){ - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: Connection creation failed. Out of memory."); - return UA_STATUSCODE_BADOUTOFMEMORY; - } - - memcpy(channelData->connection, &connection, sizeof(UA_Connection)); - -#if defined(UA_ENABLE_MQTT_TLS_OPENSSL) // Extend condition when mbedTLS support is added +#if defined(UA_ENABLE_MQTT_TLS_OPENSSL) || defined(UA_ENABLE_MQTT_TLS_MBEDTLS) +#if defined(UA_ENABLE_MQTT_TLS_OPENSSL) + BIO *sockfd = NULL; + SSL_CTX *ctx; +#else + struct mbedtls_context ctx; + mqtt_pal_socket_handle sockfd; +#endif if (channelData->mqttUseTLS) { char *mqttCaFilePath = NULL; - if (channelData->mqttCaFilePath.length > 0) { + if(channelData->mqttCaFilePath.length > 0) { /* Convert tls certificate path UA_String to char* null terminated */ - mqttCaFilePath = (char*)calloc(1,channelData->mqttCaFilePath.length + 1); - if(!mqttCaFilePath){ - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: Connection creation failed. Out of memory."); + mqttCaFilePath = (char *)calloc(1, channelData->mqttCaFilePath.length + 1); + if(!mqttCaFilePath) { + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, + "PubSub MQTT: Connection creation failed. Out of memory."); // TODO: There are several places in the existing code where channelData->connection is freed but not set to NULL // The call to disconnectMqtt by the function calling this function in case of error causes heap-use-after-free // Where should channelData->connection be cleaned up? @@ -126,11 +107,12 @@ connectMqtt(UA_PubSubChannelDataMQTT* channelData){ } char *mqttCaPath = NULL; - if (channelData->mqttCaPath.length > 0) { + if(channelData->mqttCaPath.length > 0) { /* Convert tls CA path UA_String to char* null terminated */ - mqttCaPath = (char*)calloc(1,channelData->mqttCaPath.length + 1); - if(!mqttCaPath){ - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: Connection creation failed. Out of memory (mqttCAPath)."); + mqttCaPath = (char *)calloc(1, channelData->mqttCaPath.length + 1); + if(!mqttCaPath) { + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, + "PubSub MQTT: Connection creation failed. Out of memory (mqttCAPath)."); UA_free(mqttCaFilePath); return UA_STATUSCODE_BADOUTOFMEMORY; } @@ -138,11 +120,12 @@ connectMqtt(UA_PubSubChannelDataMQTT* channelData){ } char *mqttClientCertPath = NULL; - if (channelData->mqttClientCertPath.length > 0) { + if(channelData->mqttClientCertPath.length > 0) { /* Convert tls client cert path UA_String to char* null terminated */ - mqttClientCertPath = (char*)calloc(1,channelData->mqttClientCertPath.length + 1); - if(!mqttClientCertPath){ - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: Connection creation failed. Out of memory (mqttClientCertPath)."); + mqttClientCertPath = (char *)calloc(1, channelData->mqttClientCertPath.length + 1); + if(!mqttClientCertPath) { + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, + "PubSub MQTT: Connection creation failed. Out of memory (mqttClientCertPath)."); UA_free(mqttCaFilePath); UA_free(mqttCaPath); return UA_STATUSCODE_BADOUTOFMEMORY; @@ -151,11 +134,12 @@ connectMqtt(UA_PubSubChannelDataMQTT* channelData){ } char *mqttClientKeyPath = NULL; - if (channelData->mqttClientKeyPath.length > 0) { + if(channelData->mqttClientKeyPath.length > 0) { /* Convert tls client key path UA_String to char* null terminated */ - mqttClientKeyPath = (char*)calloc(1,channelData->mqttClientKeyPath.length + 1); - if(!mqttClientKeyPath){ - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: Connection creation failed. Out of memory (mqttClientKeyPath)."); + mqttClientKeyPath = (char *)calloc(1, channelData->mqttClientKeyPath.length + 1); + if(!mqttClientKeyPath) { + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, + "PubSub MQTT: Connection creation failed. Out of memory (mqttClientKeyPath)."); UA_free(mqttCaFilePath); UA_free(mqttCaPath); UA_free(mqttClientCertPath); @@ -164,122 +148,62 @@ connectMqtt(UA_PubSubChannelDataMQTT* channelData){ memcpy(mqttClientKeyPath, channelData->mqttClientKeyPath.data, channelData->mqttClientKeyPath.length); } #endif - -#ifdef UA_ENABLE_MQTT_TLS_OPENSSL - SSL_library_init(); - OpenSSL_add_all_algorithms(); - SSL_load_error_strings(); - ERR_load_crypto_strings(); - - // OpenSSL 1.0 doesn't have TLS_client_method(), use SSLv23 and forbid SSLv2 and SSLv3 - SSL_CTX *ctx = SSL_CTX_new(SSLv23_client_method()); - SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); - - if (!ctx) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: Connection creation failed. Out of memory (SSL_CTX_new)."); +#if defined(UA_ENABLE_MQTT_TLS_OPENSSL) // Extend condition when mbedTLS support is added + /* open the non-blocking TCP socket (connecting to the broker) */ + UA_StatusCode rv = open_nb_socket(&sockfd, &ctx, addr, port, mqttCaFilePath, NULL, NULL, NULL);; + if(rv != UA_STATUSCODE_GOOD){ + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: Connection creation failed."); UA_free(mqttCaFilePath); UA_free(mqttCaPath); UA_free(mqttClientCertPath); UA_free(mqttClientKeyPath); - return UA_STATUSCODE_BADOUTOFMEMORY; + return rv; } - int result = 0; - - if (mqttCaFilePath || mqttCaPath) - result = SSL_CTX_load_verify_locations(ctx, mqttCaFilePath, mqttCaPath); - else - result = SSL_CTX_set_default_verify_paths(ctx); - - UA_free(mqttCaFilePath); - UA_free(mqttCaPath); - - if (!result) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: TLS initialization failed."); - SSL_CTX_free(ctx); + if (!ctx) { + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: Connection creation failed."); + UA_free(mqttCaFilePath); + UA_free(mqttCaPath); UA_free(mqttClientCertPath); UA_free(mqttClientKeyPath); - return UA_STATUSCODE_BADSECURITYCHECKSFAILED; - } - - SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); - - if (mqttClientKeyPath && mqttClientCertPath) { - result = SSL_CTX_use_certificate_file(ctx, mqttClientCertPath, SSL_FILETYPE_PEM); - - if (result != 1) { - result = SSL_CTX_use_certificate_file(ctx, mqttClientCertPath, SSL_FILETYPE_ASN1); - } - - if (SSL_get_error(channelData->ssl, result) != SSL_ERROR_NONE) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: Failed to load client certificate."); - SSL_CTX_free(ctx); - UA_free(mqttClientCertPath); - UA_free(mqttClientKeyPath); - return UA_STATUSCODE_BADCOMMUNICATIONERROR; - } - - result = SSL_CTX_use_PrivateKey_file(ctx, mqttClientKeyPath, SSL_FILETYPE_PEM); - - if (result != 1) { - result = SSL_CTX_use_PrivateKey_file(ctx, mqttClientKeyPath, SSL_FILETYPE_ASN1); - } - - if (SSL_get_error(channelData->ssl, result) != SSL_ERROR_NONE) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: Failed to load client private key."); - SSL_CTX_free(ctx); - UA_free(mqttClientCertPath); - UA_free(mqttClientKeyPath); - return UA_STATUSCODE_BADCOMMUNICATIONERROR; - } - - UA_free(mqttClientCertPath); - UA_free(mqttClientKeyPath); - } - - channelData->ssl = SSL_new(ctx); - - if (!channelData->ssl) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: Connection creation failed. Out of memory (SSL_new)."); - SSL_CTX_free(ctx); - return UA_STATUSCODE_BADOUTOFMEMORY; - } - - SSL_set_fd(channelData->ssl, connection.sockfd); - - int connectError = SSL_ERROR_NONE; - - do { - result = SSL_connect(channelData->ssl); - connectError = SSL_get_error(channelData->ssl, result); - } while (connectError == SSL_ERROR_WANT_READ); - - if (connectError != SSL_ERROR_NONE) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: TLS connect failed"); - const unsigned long err = ERR_get_error(); - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "SSL_connect error code: %lu %s", err, ERR_error_string(err, NULL)); - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Error description: %s", ERR_reason_error_string(err)); - return UA_STATUSCODE_BADCOMMUNICATIONERROR; - } - - long verifyResult = SSL_get_verify_result(channelData->ssl); - - if (verifyResult != SSL_ERROR_NONE) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: TLS certificate verification failed with result %ld.", verifyResult); - freeTLS(channelData); - return UA_STATUSCODE_BADSECURITYCHECKSFAILED; + return UA_STATUSCODE_BADINTERNALERROR; } UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: TLS connection successfully opened."); -#endif + } -#if defined(UA_ENABLE_MQTT_TLS_OPENSSL) // Extend condition when mbedTLS support is added +#elif defined(UA_ENABLE_MQTT_TLS_MBEDTLS) + /* open the non-blocking TCP socket (connecting to the broker) */ + UA_StatusCode rv = open_nb_socket(&ctx,addr,port,mqttCaFilePath); + if(rv != UA_STATUSCODE_GOOD){ + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: Connection creation failed."); + UA_free(mqttCaFilePath); + UA_free(mqttCaPath); + UA_free(mqttClientCertPath); + UA_free(mqttClientKeyPath); + return rv; + } + sockfd = &ctx.ssl_ctx; + + if (sockfd == NULL) { + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: Connection creation failed."); + UA_free(mqttCaFilePath); + UA_free(mqttCaPath); + UA_free(mqttClientCertPath); + UA_free(mqttClientKeyPath); + return UA_STATUSCODE_BADINTERNALERROR; + } + } +#else + int sockfd = -1; + /* open the non-blocking TCP socket (connecting to the broker) */ + UA_StatusCode rv = open_nb_socket(&sockfd,addr, port); + if(rv != UA_STATUSCODE_GOOD){ + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: Connection creation failed."); + return rv; } #endif - /* Set socket to nonblocking!*/ - UA_socket_set_nonblocking(connection.sockfd); - /* calloc mqtt_client */ struct mqtt_client* client = (struct mqtt_client*)UA_calloc(1, sizeof(struct mqtt_client)); if(!client){ @@ -294,25 +218,11 @@ connectMqtt(UA_PubSubChannelDataMQTT* channelData){ /* save reference */ channelData->mqttClient = client; - /* create custom sockethandle */ - struct my_custom_socket_handle* handle = - (struct my_custom_socket_handle*)UA_calloc(1, sizeof(struct my_custom_socket_handle)); - if(!handle){ - freeTLS(channelData); - UA_free(channelData->connection); - UA_free(client); - - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: Connection creation failed. Out of memory (client socket)."); - return UA_STATUSCODE_BADOUTOFMEMORY; - } - handle->client = client; - handle->connection = channelData->connection; -#ifdef UA_ENABLE_MQTT_TLS_OPENSSL - handle->tls = channelData->ssl; -#endif + /* save socket fd*/ + channelData->sockfd = sockfd; /* init mqtt client struct with buffers and callback */ - enum MQTTErrors mqttErr = mqtt_init(client, handle, channelData->mqttSendBuffer, channelData->mqttSendBufferSize, + enum MQTTErrors mqttErr = mqtt_init(client, sockfd, channelData->mqttSendBuffer, channelData->mqttSendBufferSize, channelData->mqttRecvBuffer, channelData->mqttRecvBufferSize, publish_callback); if(mqttErr != MQTT_OK){ freeTLS(channelData); @@ -371,103 +281,12 @@ connectMqtt(UA_PubSubChannelDataMQTT* channelData){ memcpy(password, channelData->mqttPassword.data, channelData->mqttPassword.length); } -#ifdef UA_ENABLE_MQTT_TLS_OPENSSL - char *caFilePath = NULL; - if (channelData->mqttCaFilePath.length > 0) { - /* Convert caFilePath UA_String to char* null terminated */ - caFilePath = (char*)calloc(1, channelData->mqttCaFilePath.length + 1); - if(!caFilePath){ - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "connectMqtt() : PubSub MQTT: Connection creation failed. Out of memory."); - freeTLS(channelData); - UA_free(channelData->connection); - UA_free(client); - UA_free(clientId); - UA_free(username); - UA_free(password); - return UA_STATUSCODE_BADOUTOFMEMORY; - } - memcpy(caFilePath, channelData->mqttCaFilePath.data, channelData->mqttCaFilePath.length); - } - - char *caPath = NULL; - if (channelData->mqttCaPath.length > 0) { - /* Convert caPath UA_String to char* null terminated */ - caPath = (char*)calloc(1, channelData->mqttCaPath.length + 1); - if(!caPath){ - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "connectMqtt() : PubSub MQTT: Connection creation failed. Out of memory."); - freeTLS(channelData); - UA_free(channelData->connection); - UA_free(client); - UA_free(clientId); - UA_free(username); - UA_free(password); - UA_free(caFilePath); - return UA_STATUSCODE_BADOUTOFMEMORY; - } - memcpy(caPath, channelData->mqttCaPath.data, channelData->mqttCaPath.length); - } - - char *clientCertPath = NULL; - if (channelData->mqttClientCertPath.length > 0) { - /* Convert ClientCertPath UA_String to char* null terminated */ - clientCertPath = (char*)calloc(1, channelData->mqttClientCertPath.length + 1); - if (!clientCertPath){ - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "connectMqtt() : PubSub MQTT: Connection creation failed. Out of memory."); - freeTLS(channelData); - UA_free(channelData->connection); - UA_free(client); - UA_free(clientId); - UA_free(username); - UA_free(password); - UA_free(caFilePath); - UA_free(caPath); - return UA_STATUSCODE_BADOUTOFMEMORY; - } - memcpy(clientCertPath, channelData->mqttClientCertPath.data, channelData->mqttClientCertPath.length); - } - - char *clientKeyPath = NULL; - if (channelData->mqttClientKeyPath.length > 0) { - /* Convert ClientKeyPath UA_String to char* null terminated */ - clientKeyPath = (char*)calloc(1, channelData->mqttClientKeyPath.length + 1); - if (!clientKeyPath){ - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "connectMqtt() : PubSub MQTT: Connection creation failed. Out of memory."); - freeTLS(channelData); - UA_free(channelData->connection); - UA_free(client); - UA_free(clientId); - UA_free(username); - UA_free(password); - UA_free(caFilePath); - UA_free(caPath); - UA_free(clientCertPath); - return UA_STATUSCODE_BADOUTOFMEMORY; - } - memcpy(clientKeyPath, channelData->mqttClientKeyPath.data, channelData->mqttClientKeyPath.length); - } - - UA_Boolean useTLS; - useTLS = channelData->mqttUseTLS; -#endif - - /* Connect mqtt with socket fd of networktcp */ -#ifdef UA_ENABLE_MQTT_TLS_OPENSSL - mqttErr = mqtt_connect(client, clientId, NULL, NULL, 0, username, password, - caFilePath, caPath, clientCertPath, clientKeyPath, useTLS, 0, client->keep_alive); - UA_free(clientId); - UA_free(username); - UA_free(password); - UA_free(caFilePath); - UA_free(caPath); - UA_free(clientCertPath); - UA_free(clientKeyPath); -#else + /* Connect mqtt with socket */ mqttErr = mqtt_connect(client, clientId, NULL, NULL, 0, username, password, 0, client->keep_alive); UA_free(clientId); UA_free(username); UA_free(password); -#endif if(mqttErr != MQTT_OK){ freeTLS(channelData); @@ -492,18 +311,13 @@ disconnectMqtt(UA_PubSubChannelDataMQTT* channelData){ if(client){ mqtt_disconnect(client); yieldMqtt(channelData, 10); - UA_free(client->socketfd); +#ifdef UA_ENABLE_MQTT_TLS_OPENSSL //mbedTLS condition is missing + BIO_free_all(client->socketfd); +#endif } freeTLS(channelData); - if(channelData->connection != NULL){ - channelData->connection->close(channelData->connection); - channelData->connection->free(channelData->connection); - UA_free(channelData->connection); - channelData->connection = NULL; - } - UA_free(channelData->mqttRecvBuffer); channelData->mqttRecvBuffer = NULL; UA_free(channelData->mqttSendBuffer); @@ -571,29 +385,28 @@ subscribeMqtt(UA_PubSubChannelDataMQTT* channelData, UA_String topic, UA_Byte qo UA_StatusCode unSubscribeMqtt(UA_PubSubChannelDataMQTT* channelData, UA_String topic){ - return UA_STATUSCODE_BADNOTIMPLEMENTED; + if(channelData == NULL || topic.length == 0){ + return UA_STATUSCODE_BADINVALIDARGUMENT; + } + struct mqtt_client* client = (struct mqtt_client*)channelData->mqttClient; + + UA_STACKARRAY(char, topicStr, sizeof(char) * topic.length +1); + memcpy(topicStr, topic.data, topic.length); + topicStr[topic.length] = '\0'; + + enum MQTTErrors mqttErr = mqtt_unsubscribe(client, topicStr); + if(mqttErr != MQTT_OK){ + const char* errorStr = mqtt_error_str(mqttErr); + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: subscribe: %s", errorStr); + return UA_STATUSCODE_BADCOMMUNICATIONERROR; + } + return UA_STATUSCODE_GOOD; } UA_StatusCode yieldMqtt(UA_PubSubChannelDataMQTT* channelData, UA_UInt16 timeout){ - if(channelData == NULL || timeout == 0){ - return UA_STATUSCODE_BADINVALIDARGUMENT; - } - - UA_Connection *connection = channelData->connection; - if(!connection) { - return UA_STATUSCODE_BADCOMMUNICATIONERROR; - } - - if(connection->state != UA_CONNECTIONSTATE_ESTABLISHED && - connection->state != UA_CONNECTIONSTATE_OPENING) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_NETWORK, - "PubSub MQTT: yield: Tcp Connection not established!"); - return UA_STATUSCODE_BADCOMMUNICATIONERROR; - } struct mqtt_client* client = (struct mqtt_client*)channelData->mqttClient; - client->socketfd->timeout = timeout; enum MQTTErrors error = mqtt_sync(client); if(error == MQTT_OK){ @@ -646,16 +459,17 @@ publishMqtt(UA_PubSubChannelDataMQTT* channelData, UA_String topic, const UA_Byt UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_NETWORK, "PubSub MQTT: publish: Bad Qos Level."); return UA_STATUSCODE_BADINVALIDARGUMENT; } - mqtt_publish(client, topicChar, buf->data, buf->length, (uint8_t)flags); - if (client->error != MQTT_OK) { - if(client->error == MQTT_ERROR_SEND_BUFFER_IS_FULL){ + + enum MQTTErrors mqttErr = mqtt_publish(client, topicChar, buf->data, buf->length, (uint8_t)flags); + if(mqttErr != MQTT_OK){ + if(mqttErr == MQTT_ERROR_SEND_BUFFER_IS_FULL) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: publish: Send buffer is full. " "Possible reasons: send buffer is to small, " "sending to fast, broker not responding."); }else{ - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: publish: %s", mqtt_error_str(client->error)); + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub MQTT: publish: %s", mqtt_error_str(mqttErr)); } - return UA_STATUSCODE_BADCONNECTIONREJECTED; + return UA_STATUSCODE_BADCOMMUNICATIONERROR; } return UA_STATUSCODE_GOOD; } diff --git a/plugins/mqtt/ua_mqtt_adapter.h b/plugins/mqtt/ua_mqtt-c_adapter.h similarity index 95% rename from plugins/mqtt/ua_mqtt_adapter.h rename to plugins/mqtt/ua_mqtt-c_adapter.h index 9e97f1ee2..156a79c85 100644 --- a/plugins/mqtt/ua_mqtt_adapter.h +++ b/plugins/mqtt/ua_mqtt-c_adapter.h @@ -13,7 +13,7 @@ extern "C" { #endif -#include "ua_network_pubsub_mqtt.h" +#include void freeTLS(UA_PubSubChannelDataMQTT *data); diff --git a/plugins/mqtt/ua_mqtt_pal.c b/plugins/mqtt/ua_mqtt_pal.c deleted file mode 100644 index 3d012e5ef..000000000 --- a/plugins/mqtt/ua_mqtt_pal.c +++ /dev/null @@ -1,108 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2018 Fraunhofer IOSB (Author: Lukas Meling) - * Copyright (c) 2019 Kalycito Infotech Private Limited - * Copyright (c) 2020 basysKom GmbH - */ - -#include "../../deps/mqtt-c/mqtt.h" -#include - -#ifdef UA_ENABLE_MQTT_TLS_OPENSSL -#include -#endif - -ssize_t -mqtt_pal_sendall(mqtt_pal_socket_handle fd, const void* buf, size_t len, int flags) { -#ifdef UA_ENABLE_MQTT_TLS - if (fd->tls) { -#ifdef UA_ENABLE_MQTT_TLS_OPENSSL - SSL *ssl = (SSL*) fd->tls; - int written = 0; - const UA_Byte *buffer = (const UA_Byte *) buf; - - while (written < (int) len) { - int rv = SSL_write(ssl, buffer + written, (int) len - written); - if (rv > 0) { - written += rv; - } else { - const int error = SSL_get_error(ssl, rv); - switch (error) { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - case SSL_ERROR_WANT_ACCEPT: - case SSL_ERROR_WANT_CONNECT: - case SSL_ERROR_WANT_X509_LOOKUP: - return written; - default: - return MQTT_ERROR_SOCKET_ERROR; - } - } - } - - return written; -#endif - } -#endif - - UA_Connection *connection = (UA_Connection*) fd->connection; - UA_ByteString sendBuffer; - sendBuffer.data = (UA_Byte*)UA_malloc(len); - sendBuffer.length = len; - memcpy(sendBuffer.data, buf, len); - UA_StatusCode ret = connection->send(connection, &sendBuffer); - if(ret != UA_STATUSCODE_GOOD) - return -1; - return (ssize_t)len; -} - -ssize_t -mqtt_pal_recvall(mqtt_pal_socket_handle fd, void* buf, size_t bufsz, int flags) { -#ifdef UA_ENABLE_MQTT_TLS - if (fd->tls) { -#ifdef UA_ENABLE_MQTT_TLS_OPENSSL - SSL *ssl = (SSL*) fd->tls; - - int read = 0; - - UA_Byte * buffer = (UA_Byte *) buf; - - do { - int rv = SSL_read(ssl, buffer + read, (int) bufsz - read); - if (rv > 0) { - read += rv; - } else { - const int error = SSL_get_error(ssl, rv); - switch (error) { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - case SSL_ERROR_WANT_ACCEPT: - case SSL_ERROR_WANT_CONNECT: - case SSL_ERROR_WANT_X509_LOOKUP: - return read; - default: - return MQTT_ERROR_SOCKET_ERROR; - } - } - } while (SSL_pending(ssl) && read < (int) bufsz); - - return read; -#endif - } -#endif - - UA_Connection *connection = (UA_Connection*)fd->connection; - UA_ByteString inBuffer; - inBuffer.data = (UA_Byte*)buf; - inBuffer.length = bufsz; - UA_StatusCode ret = connection->recv(connection, &inBuffer, fd->timeout); - if(ret == UA_STATUSCODE_GOOD ) { - return (ssize_t)inBuffer.length; - } else if(ret == UA_STATUSCODE_GOODNONCRITICALTIMEOUT) { - return 0; - } else { - return -1; //error case, no free necessary - } -} diff --git a/plugins/ua_network_pubsub_mqtt.c b/plugins/ua_pubsub_mqtt.c similarity index 97% rename from plugins/ua_network_pubsub_mqtt.c rename to plugins/ua_pubsub_mqtt.c index 0a51c7589..58b49eb57 100644 --- a/plugins/ua_network_pubsub_mqtt.c +++ b/plugins/ua_pubsub_mqtt.c @@ -11,13 +11,13 @@ * Currently only ua_mqtt_adapter.c implements this * interface and maps them to the specific "MQTT-C" library functions. * Another mqtt lib could be used. - * "ua_mqtt_pal.c" forwards the network calls (send/recv) to UA_Connection (TCP). + * "mqtt_pal.c" forwards the network calls (send/recv) to UA_Connection (TCP). */ #include #include -#include "mqtt/ua_mqtt_adapter.h" +#include "mqtt/ua_mqtt-c_adapter.h" #include "open62541/plugin/log_stdout.h" static UA_StatusCode @@ -67,8 +67,12 @@ UA_PubSubChannelMQTT_open(const UA_PubSubConnectionConfig *connectionConfig) { /* set default values */ UA_String mqttClientId = UA_STRING("open62541_pub"); memcpy(channelDataMQTT, &(UA_PubSubChannelDataMQTT){address, 2000, 2000, NULL, NULL, &mqttClientId, NULL, - #ifdef UA_ENABLE_MQTT_TLS_OPENSSL // Initialize the "ssl" member + #if defined(UA_ENABLE_MQTT_TLS_OPENSSL) // Initialize the "ssl" member NULL, + #elif defined(UA_ENABLE_MQTT_TLS_MBEDTLS) + NULL, + #else + -1, #endif NULL, NULL, UA_STRING_NULL, UA_STRING_NULL, UA_STRING_NULL, UA_STRING_NULL, diff --git a/src/pubsub/ua_pubsub_networkmessage.c b/src/pubsub/ua_pubsub_networkmessage.c index 025eb59bc..2f9f101bb 100644 --- a/src/pubsub/ua_pubsub_networkmessage.c +++ b/src/pubsub/ua_pubsub_networkmessage.c @@ -840,18 +840,18 @@ UA_NetworkMessage_decodePayload(const UA_ByteString *src, size_t *offset, UA_Net UA_CHECK_MEM(dst->payload.dataSetPayload.dataSetMessages, return UA_STATUSCODE_BADOUTOFMEMORY); - for(UA_Byte i = 0; i < count; i++) { - if(&dst->payload.dataSetPayload.sizes[i] == NULL){ - rv = UA_DataSetMessage_decodeBinary(src, offset, - &(dst->payload.dataSetPayload.dataSetMessages[i]), - 0); - } else { + if(count == 1) + rv = UA_DataSetMessage_decodeBinary(src, offset, + &(dst->payload.dataSetPayload.dataSetMessages[0]), + 0); + else { + for(UA_Byte i = 0; i < count; i++) { rv = UA_DataSetMessage_decodeBinary(src, offset, &(dst->payload.dataSetPayload.dataSetMessages[i]), dst->payload.dataSetPayload.sizes[i]); } - UA_CHECK_STATUS(rv, return rv); } + UA_CHECK_STATUS(rv, return rv); return UA_STATUSCODE_GOOD; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3e8de733e..5bb776eaa 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -130,14 +130,14 @@ if(UA_ENABLE_PUBSUB) endif() if(UA_ENABLE_PUBSUB_MQTT) - list(APPEND test_plugin_sources ${PROJECT_SOURCE_DIR}/deps/mqtt-c/mqtt_pal.h - ${PROJECT_SOURCE_DIR}/deps/mqtt-c/mqtt.h - ${PROJECT_SOURCE_DIR}/plugins/ua_network_pubsub_mqtt.h - ${PROJECT_SOURCE_DIR}/plugins/mqtt/ua_mqtt_adapter.h - ${PROJECT_SOURCE_DIR}/plugins/mqtt/ua_mqtt_pal.c - ${PROJECT_SOURCE_DIR}/deps/mqtt-c/mqtt.c - ${PROJECT_SOURCE_DIR}/plugins/ua_network_pubsub_mqtt.c - ${PROJECT_SOURCE_DIR}/plugins/mqtt/ua_mqtt_adapter.c) + list(APPEND test_plugin_sources ${PROJECT_SOURCE_DIR}/deps/mqtt-c/include/mqtt_pal.h + ${PROJECT_SOURCE_DIR}/deps/mqtt-c/include/mqtt.h + ${PROJECT_SOURCE_DIR}/plugins/include/open62541/plugin/pubsub_mqtt.h + ${PROJECT_SOURCE_DIR}/plugins/mqtt/ua_mqtt-c_adapter.h + ${PROJECT_SOURCE_DIR}/deps/mqtt-c/src/mqtt.c + ${PROJECT_SOURCE_DIR}/deps/mqtt-c/src/mqtt_pal.c + ${PROJECT_SOURCE_DIR}/plugins/ua_pubsub_mqtt.c + ${PROJECT_SOURCE_DIR}/plugins/mqtt/ua_mqtt-c_adapter.c) endif() endif() diff --git a/tests/pubsub/check_pubsub_connection_mqtt.c b/tests/pubsub/check_pubsub_connection_mqtt.c index 60caf338f..834caa5c2 100644 --- a/tests/pubsub/check_pubsub_connection_mqtt.c +++ b/tests/pubsub/check_pubsub_connection_mqtt.c @@ -10,13 +10,14 @@ #include #include -#include "ua_network_pubsub_mqtt.h" +#include + #include "ua_server_internal.h" #include -//#define TEST_MQTT_SERVER "opc.mqtt://test.mosquitto.org:1883/" -#define TEST_MQTT_SERVER "opc.mqtt://localhost:1883/" +//#define TEST_MQTT_SERVER "opc.mqtt://test.mosquitto.org:1883" +#define TEST_MQTT_SERVER "opc.mqtt://localhost:1883" UA_Server *server = NULL; UA_ServerConfig *config = NULL;