diff --git a/CHANGELOG.md b/CHANGELOG.md index 73dcf3e25..5875edeec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] +- Added `mqtt` - the pm3 client can now send and receive MQTT messages or json files. (@iceman1001) - Changed `hf iclass wrbl` - replay behavior to use privilege escalation if the macs field is not passed empty(@antiklesys) - Changed `hf iclass restore` - it now supports privilege escalation to restore card content using replay (@antiklesys) - Fixed `hf 15 dump` - now reads sysinfo response correct (@iceman1001) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 854828df9..1f0410e31 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -402,6 +402,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/cmdlfvisa2000.c ${PM3_ROOT}/client/src/cmdlfzx8211.c ${PM3_ROOT}/client/src/cmdmain.c + ${PM3_ROOT}/client/src/cmdmqtt.c ${PM3_ROOT}/client/src/cmdnfc.c ${PM3_ROOT}/client/src/cmdparser.c ${PM3_ROOT}/client/src/cmdpiv.c @@ -772,6 +773,7 @@ target_link_libraries(proxmark3 PRIVATE pm3rrg_rdv4_reveng pm3rrg_rdv4_hardnested pm3rrg_rdv4_id48 + pm3rrg_rdv4_mqtt ${ADDITIONAL_LNK}) if (NOT SKIPPTHREAD EQUAL 1) diff --git a/client/Makefile b/client/Makefile index 014211325..2c0f7ef04 100644 --- a/client/Makefile +++ b/client/Makefile @@ -131,6 +131,12 @@ WHEREAMILIBINC = -I$(WHEREAMILIBPATH) WHEREAMILIB = $(WHEREAMILIBPATH)/libwhereami.a WHEREAMILIBLD = +## MQTT +MQTTLIBPATH = ./deps/mqtt +MQTTLIBINC = -I$(MQTTLIBPATH) +MQTTLIB = $(MQTTLIBPATH)/mqtt.a +MQTTLIBLD = + ########################## # common local libraries # ########################## @@ -239,6 +245,12 @@ STATICLIBS += $(WHEREAMILIB) LDLIBS += $(WHEREAMILIBLD) PM3INCLUDES += $(WHEREAMILIBINC) +## MQTT +# not distributed as system library +STATICLIBS += $(MQTTLIB) +LDLIBS += $(MQTTLIBLD) +PM3INCLUDES += $(MQTTLIBINC) + #################### # system libraries # #################### @@ -682,6 +694,7 @@ SRCS = mifare/aiddesfire.c \ cmdlfvisa2000.c \ cmdlfzx8211.c \ cmdmain.c \ + cmdmqtt.c \ cmdnfc.c \ cmdparser.c \ cmdpiv.c \ @@ -877,6 +890,7 @@ endif $(Q)$(MAKE) --no-print-directory -C $(REVENGLIBPATH) clean $(Q)$(MAKE) --no-print-directory -C $(TINYCBORLIBPATH) clean $(Q)$(MAKE) --no-print-directory -C $(WHEREAMILIBPATH) clean + $(Q)$(MAKE) --no-print-directory -C $(MQTTLIBPATH) clean @# Just in case someone compiled within these dirs: $(Q)$(MAKE) --no-print-directory -C $(MBEDTLSLIBPATH) clean @@ -974,6 +988,10 @@ ifneq ($(WHEREAMI_FOUND),1) $(Q)$(MAKE) --no-print-directory -C $(WHEREAMILIBPATH) all endif +$(MQTTLIB): .FORCE + $(info [*] MAKE $@) + $(Q)$(MAKE) --no-print-directory -C $(MQTTLIBPATH) all + ######## # SWIG # ######## diff --git a/client/deps/CMakeLists.txt b/client/deps/CMakeLists.txt index 99a843d66..c8f5b78c5 100644 --- a/client/deps/CMakeLists.txt +++ b/client/deps/CMakeLists.txt @@ -31,3 +31,6 @@ endif() if (NOT TARGET pm3rrg_rdv4_whereami) include(whereami.cmake) endif() +if (NOT TARGET pm3rrg_rdv4_mqtt) + include(mqtt.cmake) +endif() \ No newline at end of file diff --git a/client/deps/mqtt.cmake b/client/deps/mqtt.cmake new file mode 100644 index 000000000..f30e761c3 --- /dev/null +++ b/client/deps/mqtt.cmake @@ -0,0 +1,9 @@ +add_library(pm3rrg_rdv4_mqtt STATIC + mqtt/mqtt.c + mqtt/mqtt_pal.c + ) + +target_compile_definitions(pm3rrg_rdv4_mqtt PRIVATE WAI_PM3_TUNED) +target_include_directories(pm3rrg_rdv4_mqtt INTERFACE mqtt) +target_compile_options(pm3rrg_rdv4_mqtt PRIVATE -Wall -Werror -O3) +set_property(TARGET pm3rrg_rdv4_mqtt PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/client/deps/mqtt/LICENSE b/client/deps/mqtt/LICENSE new file mode 100644 index 000000000..0bbb84557 --- /dev/null +++ b/client/deps/mqtt/LICENSE @@ -0,0 +1,21 @@ +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. diff --git a/client/deps/mqtt/Makefile b/client/deps/mqtt/Makefile new file mode 100644 index 000000000..af4679674 --- /dev/null +++ b/client/deps/mqtt/Makefile @@ -0,0 +1,14 @@ +MYSRCPATHS = +MYINCLUDES = +MYCFLAGS = -Wno-bad-function-cast -Wno-switch-enum +MYDEFS = -DWAI_PM3_TUNED +MYSRCS = \ + mqtt.c \ + mqtt_pal.c \ + +LIB_A = mqtt.a + +# Transition: remove old directories and objects +MYCLEANOLDPATH = ../../mqtt + +include ../../../Makefile.host diff --git a/client/deps/mqtt/mbedtls_sockets.h b/client/deps/mqtt/mbedtls_sockets.h new file mode 100644 index 000000000..1295152f9 --- /dev/null +++ b/client/deps/mqtt/mbedtls_sockets.h @@ -0,0 +1,152 @@ +#if !defined(__MBEDTLS_SOCKET_TEMPLATE_H__) +#define __MBEDTLS_SOCKET_TEMPLATE_H__ + +#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 */ + msleep(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; +}; + +void failed(const char *fn, int rv); +void cert_verify_failed(uint32_t rv); +void open_nb_socket(struct mbedtls_context *ctx, + const char *hostname, + const char *port, + const char *ca_file); + + +void failed(const char *fn, int rv) { + char buf[100]; + mbedtls_strerror(rv, buf, sizeof(buf)); + printf("%s failed with %x (%s)\n", fn, -rv, buf); + exit(1); +} + +void cert_verify_failed(uint32_t rv) { + char buf[512]; + mbedtls_x509_crt_verify_info(buf, sizeof(buf), "\t", rv); + printf("Certificate verification failed (%0" PRIx32 ")\n%s\n", rv, buf); + exit(1); +} + +/* + A template for opening a non-blocking mbed TLS connection. +*/ +void open_nb_socket(struct mbedtls_context *ctx, + const char *hostname, + const char *port, + const char *ca_file) { + + const unsigned char *additional = (const unsigned char *)"Pm3 Client"; + 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) { + 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) { + 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) { + 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) { + failed("mbedtls_net_connect", rv); + } + rv = mbedtls_net_set_nonblock(net_ctx); + if (rv != 0) { + failed("mbedtls_net_set_nonblock", rv); + } + + mbedtls_ssl_init(ssl_ctx); + rv = mbedtls_ssl_setup(ssl_ctx, ssl_conf); + if (rv != 0) { + failed("mbedtls_ssl_setup", rv); + } + rv = mbedtls_ssl_set_hostname(ssl_ctx, hostname); + if (rv != 0) { + 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) { + failed("mbedtls_net_poll", rv); + } + } + if (rv != 0) { + failed("mbedtls_ssl_handshake", rv); + } + uint32_t result = mbedtls_ssl_get_verify_result(ssl_ctx); + if (result != 0) { + if (result == (uint32_t) -1) { + failed("mbedtls_ssl_get_verify_result", (int)result); + } else { + cert_verify_failed(result); + } + } +} + +#endif diff --git a/client/deps/mqtt/mqtt.c b/client/deps/mqtt/mqtt.c new file mode 100644 index 000000000..b29f9d9d4 --- /dev/null +++ b/client/deps/mqtt/mqtt.c @@ -0,0 +1,1770 @@ +/* +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; + int reconnecting = 0; + MQTT_PAL_MUTEX_LOCK(&client->mutex); + if (client->error != MQTT_ERROR_RECONNECTING && client->error != MQTT_OK && client->reconnect_callback != NULL) { + client->reconnect_callback(client, &client->reconnect_state); + if (client->error != MQTT_OK) { + client->error = MQTT_ERROR_RECONNECT_FAILED; + + /* normally unlocked during CONNECT */ + MQTT_PAL_MUTEX_UNLOCK(&client->mutex); + } + err = client->error; + + if (err != MQTT_OK) return err; + } else { + /* mqtt_reconnect will have queued the disconnect packet - that needs to be sent and then call reconnect */ + if (client->error == MQTT_ERROR_RECONNECTING) { + reconnecting = 1; + client->error = MQTT_OK; + } + 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); + + /* mqtt_reconnect will essentially be a disconnect if there is no callback */ + if (reconnecting && client->reconnect_callback != NULL) { + MQTT_PAL_MUTEX_LOCK(&client->mutex); + client->reconnect_callback(client, &client->reconnect_state); + } + + 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.0f; + 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, 0uL); + + 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.0f; + client->publish_response_callback = publish_response_callback; + client->pid_lfsr = 0; + 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, + 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; + } + + /* 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 + ); + /* 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, + const 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_reconnect(struct mqtt_client *client) { + enum MQTTErrors err = mqtt_disconnect(client); + + if (err == MQTT_OK) { + MQTT_PAL_MUTEX_LOCK(&client->mutex); + client->error = MQTT_ERROR_RECONNECTING; + MQTT_PAL_MUTEX_UNLOCK(&client->mutex); + } + return err; +} + +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)); + 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 = (float)(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.875f * (client->typical_response_time) + 0.125f * (float)(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.875f * (client->typical_response_time) + 0.125f * (float)(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.875f * (client->typical_response_time) + 0.125f * (float)(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.875f * (client->typical_response_time) + 0.125f * (float)(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.875f * (client->typical_response_time) + 0.125f * (float)(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.875f * (client->typical_response_time) + 0.125f * (float)(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.875f * (client->typical_response_time) + 0.125f * (float)(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 { + uint8_t control_type_is_valid[16]; + uint8_t required_flags[16]; + uint8_t mask_required_flags[16]; +} ; + +static const 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, + 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; + + /* 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 += __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 += __mqtt_packed_cstrlen(password); + } else { + connect_flags &= (uint8_t)~MQTT_CONNECT_PASSWORD; + } + + /* 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 (will_topic != NULL) { + 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 (user_name != NULL) { + buf += __mqtt_pack_str(buf, user_name); + } + if (password != NULL) { + buf += __mqtt_pack_str(buf, password); + } + + /* 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, + const 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 = (publish_flags & MQTT_PUBLISH_QOS_MASK) >> 1; /* mask */ + + /* build the fixed header */ + fixed_header.control_type = MQTT_CONTROL_PUBLISH; + + /* calculate remaining length */ + remaining_length = (uint32_t)__mqtt_packed_cstrlen(topic_name); + if (inspected_qos > 0) { + remaining_length += 2; + } + remaining_length += (uint32_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 = (fixed_header->control_flags & MQTT_PUBLISH_DUP) >> 3; + response->qos_level = (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); + for (;;) { + 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) { + va_end(args); + 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); + for (;;) { + 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) { + va_end(args); + 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 = buf == NULL ? 0 : 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 = (unsigned char *)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 */ + ssize_t i = 0; + for (; 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(const struct mqtt_message_queue *mq, enum MQTTControlPacketType control_type, const 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, 2uL); + return 2; +} + +uint16_t __mqtt_unpack_uint16(const uint8_t *buf) { + uint16_t integer_htons; + memcpy(&integer_htons, buf, 2uL); + return MQTT_PAL_NTOHS(integer_htons); +} + +ssize_t __mqtt_pack_str(uint8_t *buf, const char *str) { + uint16_t length = (uint16_t)strlen(str); + int i = 0; + /* pack string length */ + buf += __mqtt_pack_uint16(buf, length); + + /* pack string */ + for (; i < length; ++i) { + *(buf++) = (uint8_t)str[i]; + } + + /* return number of bytes consumed */ + return length + 2; +} + +static const char *const 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/client/deps/mqtt/mqtt.h b/client/deps/mqtt/mqtt.h new file mode 100644 index 000000000..d3467cd27 --- /dev/null +++ b/client/deps/mqtt/mqtt.h @@ -0,0 +1,1640 @@ +#if !defined(__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. +*/ + +#if defined(__cplusplus) +extern "C" { +#endif + +// Users can override mqtt_pal.h with their own configuration by defining +// MQTTC_PAL_FILE as a header file to include (-DMQTTC_PAL_FILE=my_mqtt_pal.h). +// +// If MQTTC_PAL_FILE is used, none of the default utils will be emitted and must be +// provided by the config file. To start, I would suggest copying mqtt_pal.h +// and modifying as needed. +#if defined(MQTTC_PAL_FILE) +#define MQTTC_STR2(x) #x +#define MQTTC_STR(x) MQTTC_STR2(x) +#include MQTTC_STR(MQTTC_PAL_FILE) +#else +#include "mqtt_pal.h" +#endif /* MQTT_PAL_FILE */ + +/** + * @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 a 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 A structure that I will use to keep track of some data needed + * to setup the connection to the broker. + * + * An instance of this struct will be created in my \c main(). Then, whenever + * \ref reconnect_client is called, this instance will be passed. + */ +struct reconnect_state_t { + const char *hostname; + const char *port; + const char *topic; + uint8_t *sendbuf; + size_t sendbufsz; + uint8_t *recvbuf; + size_t recvbufsz; +}; + +/** + * @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) \ + MQTT_ERROR(MQTT_ERROR_RECONNECT_FAILED) \ + MQTT_ERROR(MQTT_ERROR_RECONNECTING) + +/* 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 +}; + +/** + * @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 a 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, + 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, + const 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(const struct mqtt_message_queue *mq, enum MQTTControlPacketType control_type, const 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 a exponential-averaging. + */ + float 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 writable 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). + * @attention \p sendbuf internally mapped to client's message-to-send queue that actively uses + * pointer access. In the case of unaligned \p sendbuf, that may lead to + * Segmentation/Hard/Memory Faults on systems that do not support unaligned pointer + * access (e.g. ARMv6, ARMv7-M). To avoid that, you may use the following technique: + * \code{.c} + * // example for ARMv7-M that requires pointers to be word aligned (4 byte boundary) + * static unsigned char mqtt_tx_buffer[MAX_TX_BUFFER_SIZE] __attribute__((aligned(4))); + * static unsigned char mqtt_rx_buffer[MAX_RX_BUFFER_SIZE]; + * // ... + * int main(void) { + * // ... + * mqtt_init(p_client, p_client->socketfd, mqtt_tx_buffer, sizeof mqtt_tx_buffer, mqtt_rx_buffer, + * sizeof mqtt_rx_buffer, message_callback); + * // ... + * } + * \endcode + * + * @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, + 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, + const 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); + +/** + * @brief Terminate the session with the MQTT broker and prepare to + * reconnect. Client code should call \ref mqtt_sync immediately + * after this call to prevent message loss. + * @ingroup api + * + * @note The user must provide a reconnect callback function for this to + * work as expected. See \r mqtt_client_reconnect. + * + * @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_reconnect(struct mqtt_client *client); + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/client/deps/mqtt/mqtt_pal.c b/client/deps/mqtt/mqtt_pal.c new file mode 100644 index 000000000..9ebab98b6 --- /dev/null +++ b/client/deps/mqtt/mqtt_pal.c @@ -0,0 +1,235 @@ +/* +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 @ref mqtt_pal_sendall and @ref mqtt_pal_recvall and + * any platform-specific helpers you'd like. + * @cond Doxygen_Suppress + */ + +#if defined(MQTT_USE_CUSTOM_SOCKET_HANDLE) + +/* + * In case of MQTT_USE_CUSTOM_SOCKET_HANDLE, a pal implemantation is + * provided by the user. + */ + +/* Note: Some toolchains complain on an object without symbols */ + +int _mqtt_pal_dummy; + +#else /* defined(MQTT_USE_CUSTOM_SOCKET_HANDLE) */ + +#if defined(MQTT_USE_MBEDTLS) +#include + +ssize_t mqtt_pal_sendall(mqtt_pal_socket_handle fd, const void *buf, size_t len, int flags) { + enum MQTTErrors error = 0; + size_t sent = 0; + while (sent < len) { + int rv = mbedtls_ssl_write(fd, (const unsigned char *)buf + sent, len - sent); + if (rv < 0) { + if (rv == MBEDTLS_ERR_SSL_WANT_READ || + rv == MBEDTLS_ERR_SSL_WANT_WRITE +#if defined(MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS) + || rv == MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS +#endif +#if defined(MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS) + || rv == MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS +#endif + ) { + /* should call mbedtls_ssl_write later again */ + break; + } + error = MQTT_ERROR_SOCKET_ERROR; + break; + } + /* + * Note: rv can be 0 here eg. when mbedtls just flushed + * the previous incomplete record. + * + * Note: we never send an empty TLS record. + */ + sent += (size_t) rv; + } + if (sent == 0) { + return error; + } + return (ssize_t)sent; +} + +ssize_t mqtt_pal_recvall(mqtt_pal_socket_handle fd, void *buf, size_t bufsz, int flags) { + const void *const start = buf; + enum MQTTErrors error = 0; + int rv; + do { + rv = mbedtls_ssl_read(fd, (unsigned char *)buf, bufsz); + if (rv == 0) { + /* + * Note: mbedtls_ssl_read returns 0 when the underlying + * transport was closed without CloseNotify. + * + * Raise an error to trigger a reconnect. + */ + error = MQTT_ERROR_SOCKET_ERROR; + break; + } + if (rv < 0) { + if (rv == MBEDTLS_ERR_SSL_WANT_READ || + rv == MBEDTLS_ERR_SSL_WANT_WRITE +#if defined(MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS) + || rv == MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS +#endif +#if defined(MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS) + || rv == MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS +#endif + ) { + /* should call mbedtls_ssl_read later again */ + break; + } + /* Note: MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY is handled here. */ + error = MQTT_ERROR_SOCKET_ERROR; + break; + } + buf = (char *)buf + rv; + bufsz -= (unsigned long)rv; + } while (bufsz > 0); + if (buf == start) { + return error; + } + return (const char *)buf - (const char *)start; +} + +#elif defined(__unix__) || defined(__APPLE__) || defined(__NuttX__) + +#include + +ssize_t mqtt_pal_sendall(mqtt_pal_socket_handle fd, const void *buf, size_t len, int flags) { + enum MQTTErrors error = 0; + size_t sent = 0; + while (sent < len) { + ssize_t rv = send(fd, (const char *)buf + sent, len - sent, flags); + if (rv < 0) { + if (errno == EAGAIN) { + /* should call send later again */ + break; + } + error = MQTT_ERROR_SOCKET_ERROR; + break; + } + if (rv == 0) { + /* is this possible? maybe OS bug. */ + error = MQTT_ERROR_SOCKET_ERROR; + break; + } + sent += (size_t) rv; + } + if (sent == 0) { + return error; + } + return (ssize_t)sent; +} + +ssize_t mqtt_pal_recvall(mqtt_pal_socket_handle fd, void *buf, size_t bufsz, int flags) { + const void *const start = buf; + enum MQTTErrors error = 0; + ssize_t rv; + do { + rv = recv(fd, buf, bufsz, flags); + if (rv == 0) { + /* + * recv returns 0 when the socket is (half) closed by the peer. + * + * Raise an error to trigger a reconnect. + */ + error = MQTT_ERROR_SOCKET_ERROR; + break; + } + if (rv < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + /* should call recv later again */ + break; + } + /* an error occurred that wasn't "nothing to read". */ + error = MQTT_ERROR_SOCKET_ERROR; + break; + } + buf = (char *)buf + rv; + bufsz -= (unsigned long)rv; + } while (bufsz > 0); + if (buf == start) { + return error; + } + return (char *)buf - (const char *)start; +} + +#elif defined(_MSC_VER) || defined(WIN32) + +#include + +ssize_t mqtt_pal_sendall(mqtt_pal_socket_handle fd, const void *buf, size_t len, int flags) { + size_t sent = 0; + while (sent < len) { + ssize_t tmp = send(fd, (char *)buf + sent, len - sent, flags); + if (tmp < 1) { + return MQTT_ERROR_SOCKET_ERROR; + } + sent += (size_t) tmp; + } + return sent; +} + +ssize_t mqtt_pal_recvall(mqtt_pal_socket_handle fd, void *buf, size_t bufsz, int flags) { + const char *const start = buf; + ssize_t rv; + do { + rv = recv(fd, buf, bufsz, flags); + if (rv > 0) { + /* successfully read bytes from the socket */ + buf = (char *)buf + rv; + bufsz -= rv; + } else if (rv < 0) { + int err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK) { + /* an error occurred that wasn't "nothing to read". */ + return MQTT_ERROR_SOCKET_ERROR; + } + } + } while (rv > 0 && bufsz > 0); + + return (ssize_t)((char *)buf - start); +} + +#else + +#error No PAL! + +#endif + +#endif /* defined(MQTT_USE_CUSTOM_SOCKET_HANDLE) */ + +/** @endcond */ diff --git a/client/deps/mqtt/mqtt_pal.h b/client/deps/mqtt/mqtt_pal.h new file mode 100644 index 000000000..87b84500b --- /dev/null +++ b/client/deps/mqtt/mqtt_pal.h @@ -0,0 +1,173 @@ +#if !defined(__MQTT_PAL_H__) +#define __MQTT_PAL_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. +*/ + +#if defined(__cplusplus) +extern "C" { +#endif + +/** + * @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. + */ + + +/* UNIX-like platform support */ +#if defined(__unix__) || defined(__APPLE__) || defined(__NuttX__) +#include +#include +#include +#include +#include +#include + +#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) + +#if !defined(MQTT_USE_CUSTOM_SOCKET_HANDLE) +#if defined(MQTT_USE_MBEDTLS) +struct mbedtls_ssl_context; +typedef struct mbedtls_ssl_context *mqtt_pal_socket_handle; +#else +typedef int mqtt_pal_socket_handle; +#endif +#endif + +#elif defined(_MSC_VER) || defined(WIN32) +#include +#include +#include +#include +#include + +typedef SSIZE_T ssize_t; +#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 CRITICAL_SECTION mqtt_pal_mutex_t; + +#define MQTT_PAL_MUTEX_INIT(mtx_ptr) InitializeCriticalSection(mtx_ptr) +#define MQTT_PAL_MUTEX_LOCK(mtx_ptr) EnterCriticalSection(mtx_ptr) +#define MQTT_PAL_MUTEX_UNLOCK(mtx_ptr) LeaveCriticalSection(mtx_ptr) + + +#if !defined(MQTT_USE_CUSTOM_SOCKET_HANDLE) +typedef SOCKET mqtt_pal_socket_handle; +#endif + +#endif + +/** + * @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. + * + * Note about the error handling: + * - On an error, if some bytes have been processed already, + * this function should return the number of bytes successfully + * processed. (partial success) + * - Otherwise, if the error is an equivalent of EAGAIN, return 0. + * - Otherwise, return MQTT_ERROR_SOCKET_ERROR. + */ +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. + * + * Note about the error handling: + * - On an error, if some bytes have been processed already, + * this function should return the number of bytes successfully + * processed. (partial success) + * - Otherwise, if the error is an equivalent of EAGAIN, return 0. + * - Otherwise, return MQTT_ERROR_SOCKET_ERROR. + */ +ssize_t mqtt_pal_recvall(mqtt_pal_socket_handle fd, void *buf, size_t bufsz, int flags); + +#if defined(__cplusplus) +} +#endif + + +#endif diff --git a/client/deps/mqtt/posix_sockets.h b/client/deps/mqtt/posix_sockets.h new file mode 100644 index 000000000..ab13f863d --- /dev/null +++ b/client/deps/mqtt/posix_sockets.h @@ -0,0 +1,94 @@ +#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 + +/* + A template for opening a non-blocking POSIX socket. +*/ +int open_nb_socket(const char *addr, const char *port); + +int open_nb_socket(const char *addr, const char *port) { + + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + + hints.ai_family = AF_UNSPEC; /* IPv4 or IPv6 */ + hints.ai_socktype = SOCK_STREAM; /* Must be TCP */ + int sockfd = -1; + int rv; + struct addrinfo *p, *servinfo; + + /* get address information */ + rv = getaddrinfo(addr, port, &hints, &servinfo); + if (rv != 0) { + fprintf(stderr, "Failed to open socket (getaddrinfo): %s\n", gai_strerror(rv)); + return -1; + } + + /* 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 */ + return sockfd; +} + +void close_nb_socket(int sockfd); + +void close_nb_socket(int sockfd) { + if (sockfd != -1) { + close(sockfd); + } +} +#endif diff --git a/client/deps/mqtt/readme.md b/client/deps/mqtt/readme.md new file mode 100644 index 000000000..c7e437b7e --- /dev/null +++ b/client/deps/mqtt/readme.md @@ -0,0 +1,15 @@ + +# Information +Source: https://github.com/LiamBindle/MQTT-C +License: MIT +Authors: + +MQTT-C was initially developed as a CMPT 434 (Winter Term, 2018) final project at the University of Saskatchewan by: + + - Liam Bindle + - Demilade Adeoye + + +# about +MQTT-C is an MQTT v3.1.1 client written in C. MQTT is a lightweight publisher-subscriber-based messaging protocol that is commonly used in IoT and networking applications where high-latency and low data-rate links are expected. The purpose of MQTT-C is to provide a portable MQTT client, written in C, for embedded systems and PC's alike. MQTT-C does this by providing a transparent Platform Abstraction Layer (PAL) which makes porting to new platforms easy. MQTT-C is completely thread-safe but can also run perfectly fine on single-threaded systems making MQTT-C well-suited for embedded systems and microcontrollers. Finally, MQTT-C is small; there are only two source files totalling less than 2000 lines. + diff --git a/client/src/cmdmain.c b/client/src/cmdmain.c index 25bf6cce8..9ab88324b 100644 --- a/client/src/cmdmain.c +++ b/client/src/cmdmain.c @@ -48,6 +48,7 @@ #include "commonutil.h" // ARRAYLEN #include "preferences.h" #include "cliparser.h" +#include "cmdmqtt.h" static int CmdHelp(const char *Cmd); @@ -338,6 +339,7 @@ static command_t CommandTable[] = { {"hw", CmdHW, AlwaysAvailable, "{ Hardware commands... }"}, {"lf", CmdLF, AlwaysAvailable, "{ Low frequency commands... }"}, {"mem", CmdFlashMem, IfPm3Flash, "{ Flash memory manipulation... }"}, + {"mqtt", CmdMqtt, AlwaysAvailable, "{ MQTT commmands... }"}, {"nfc", CmdNFC, AlwaysAvailable, "{ NFC commands... }"}, {"piv", CmdPIV, AlwaysAvailable, "{ PIV commands... }"}, {"reveng", CmdRev, AlwaysAvailable, "{ CRC calculations from RevEng software... }"}, diff --git a/client/src/cmdmqtt.c b/client/src/cmdmqtt.c new file mode 100644 index 000000000..62397a91d --- /dev/null +++ b/client/src/cmdmqtt.c @@ -0,0 +1,385 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// MQTT commands +//----------------------------------------------------------------------------- +#include "cmdmqtt.h" + +#include "cmdparser.h" +#include "cliparser.h" +#include "mqtt.h" // MQTT support +//#include "mbedtls_sockets.h" // MQTT networkings examples +#include "posix_sockets.h" // MQTT networkings examples +#include "util_posix.h" // time +#include "fileutils.h" + + +#define MQTT_BUFFER_SIZE ( 1 << 16 ) + +static int CmdHelp(const char *Cmd); + +static void mqtt_publish_callback(void **unused, struct mqtt_response_publish *published) { + /* note that published->topic_name is NOT null-terminated (here we'll change it to a c-string) */ + char *topic_name = (char *) calloc(published->topic_name_size + 1, 1); + memcpy(topic_name, published->topic_name, published->topic_name_size); + + PrintAndLogEx(INFO, "rec.. %zu", published->application_message_size); + + const char *msg = published->application_message; + + char *ps = strstr(msg, "Created\": \"proxmark3"); + if (ps) { + int res = saveFileTXT("ice_mqtt", ".json", msg, published->application_message_size, spDefault); + if (res == PM3_SUCCESS) { + PrintAndLogEx(INFO, "Got a json file, save OK"); + } + } else { + PrintAndLogEx(SUCCESS, "[" _GREEN_("%s")"] " _YELLOW_("%s"), topic_name, msg); + } + free(topic_name); +} + +static void *mqtt_client_refresher(void *client) { + while (1) { + mqtt_sync((struct mqtt_client *) client); + msleep(100); + } + return NULL; +} + +static int mqtt_exit(int status, int sockfd, pthread_t *client_daemon) { + close_nb_socket(sockfd); + + if (client_daemon != NULL) { + pthread_cancel(*client_daemon); + pthread_join(*client_daemon, NULL); // Wait for the thread to finish + } + return status; +} + +/* +static void mqtt_reconnect_client(struct mqtt_client* client, void **reconnect_state_vptr) { + + struct reconnect_state_t *rs = *((struct reconnect_state_t**) reconnect_state_vptr); + + // Close the clients socket if this isn't the initial reconnect call + if (client->error != MQTT_ERROR_INITIAL_RECONNECT) { + close_nb_socket(client->socketfd); + } + + if (client->error != MQTT_ERROR_INITIAL_RECONNECT) { + PrintAndLogEx(INFO, "reconnect_client: called while client was in error state `%s`", mqtt_error_str(client->error)); + } + + int sockfd = open_nb_socket(rs->hostname, rs->port); + if (sockfd == -1) { + PrintAndLogEx(FAILED, "Failed to open socket"); + mqtt_exit(PM3_EFAILED, sockfd, NULL); + } + + // Reinitialize the client. + mqtt_reinit(client, sockfd, rs->sendbuf, rs->sendbufsz, rs->recvbuf, rs->recvbufsz); + + const char* client_id = NULL; + + uint8_t connect_flags = MQTT_CONNECT_CLEAN_SESSION; + + mqtt_connect(client, client_id, NULL, NULL, 0, NULL, NULL, connect_flags, 400); + + mqtt_subscribe(client, rs->topic, 0); +} +*/ + +static int mqtt_receive(const char *addr, const char *port, const char *topic) { + // open the non-blocking TCP socket (connecting to the broker) + int sockfd = open_nb_socket(addr, port); + if (sockfd == -1) { + PrintAndLogEx(FAILED, "Failed to open socket"); + return mqtt_exit(PM3_EFAILED, sockfd, NULL); + } + + uint8_t sendbuf[MQTT_BUFFER_SIZE]; // 64kb sendbuf should be large enough to hold multiple whole mqtt messages + uint8_t recvbuf[MQTT_BUFFER_SIZE]; // 64kb recvbuf should be large enough any whole mqtt message expected to be received + + struct mqtt_client client; + + /* + struct reconnect_state_t rs; + rs.hostname = addr; + rs.port = port; + rs.topic = topic; + rs.sendbuf = sendbuf; + rs.sendbufsz = sizeof(sendbuf); + rs.recvbuf = recvbuf; + rs.recvbufsz = sizeof(recvbuf); + mqtt_init_reconnect(&client, mqtt_reconnect_client, &rs, mqtt_publish_callback); + */ + + mqtt_init(&client, sockfd, sendbuf, sizeof(sendbuf), recvbuf, sizeof(recvbuf), mqtt_publish_callback); + + char cid[20] = "pm3_"; + sprintf(cid + strlen(cid), "%02x%02x%02x%02x" + , rand() % 0xFF + , rand() % 0xFF + , rand() % 0xFF + , rand() % 0xFF + ); + + // Ensure we have a clean session + uint8_t connect_flags = MQTT_CONNECT_CLEAN_SESSION; + // Send connection request to the broker + mqtt_connect(&client, cid, NULL, NULL, 0, NULL, NULL, connect_flags, 400); + + // check that we don't have any errors + if (client.error != MQTT_OK) { + PrintAndLogEx(FAILED, "error: %s", mqtt_error_str(client.error)); + return mqtt_exit(PM3_ESOFT, sockfd, NULL); + } + + // start a thread to refresh the client (handle egress and ingree client traffic) + pthread_t client_daemon; + if (pthread_create(&client_daemon, NULL, mqtt_client_refresher, &client)) { + PrintAndLogEx(FAILED, "Failed to start client daemon"); + return mqtt_exit(PM3_ESOFT, sockfd, NULL); + } + + // subscribe to a topic with a max QoS level of 0 + mqtt_subscribe(&client, topic, 0); + + PrintAndLogEx(INFO, _CYAN_("%s") " listening at " _CYAN_("%s:%s") " for " _YELLOW_("%s") " messages", cid, addr, port, topic); + PrintAndLogEx(INFO, "Press " _GREEN_("") " to exit"); + + while (kbd_enter_pressed() == false) { + msleep(2000); + }; + + PrintAndLogEx(INFO, _CYAN_("%s") " disconnecting from " _CYAN_("%s"), cid, addr); + return mqtt_exit(PM3_SUCCESS, sockfd, &client_daemon); +} + +static int mqtt_send(const char *addr, const char *port, const char *topic, char *msg, const char *fn) { + + uint8_t *data; + size_t bytes_read = 0; + if (fn != NULL) { + int res = loadFile_TXTsafe(fn, "", (void **)&data, &bytes_read, true); + if (res != PM3_SUCCESS) { + return res; + } + } + + // open the non-blocking TCP socket (connecting to the broker) + int sockfd = open_nb_socket(addr, port); + + if (sockfd == -1) { + PrintAndLogEx(FAILED, "Failed to open socket"); + return mqtt_exit(PM3_EFAILED, sockfd, NULL); + } + + struct mqtt_client client; + uint8_t sendbuf[MQTT_BUFFER_SIZE]; + uint8_t recvbuf[MQTT_BUFFER_SIZE]; + mqtt_init(&client, sockfd, sendbuf, sizeof(sendbuf), recvbuf, sizeof(recvbuf), mqtt_publish_callback); + + char cid[20] = "pm3_"; + sprintf(cid + strlen(cid), "%02x%02x%02x%02x" + , rand() % 0xFF + , rand() % 0xFF + , rand() % 0xFF + , rand() % 0xFF + ); + + // Ensure we have a clean session + uint8_t connect_flags = MQTT_CONNECT_CLEAN_SESSION; + // Send connection request to the broker + mqtt_connect(&client, cid, NULL, NULL, 0, NULL, NULL, connect_flags, 400); + + // check that we don't have any errors + if (client.error != MQTT_OK) { + PrintAndLogEx(FAILED, "error: %s", mqtt_error_str(client.error)); + mqtt_exit(PM3_EFAILED, sockfd, NULL); + } + + // start a thread to refresh the client (handle egress and ingree client traffic) + pthread_t client_daemon; + if (pthread_create(&client_daemon, NULL, mqtt_client_refresher, &client)) { + PrintAndLogEx(FAILED, "Failed to start client daemon"); + mqtt_exit(PM3_EFAILED, sockfd, NULL); + + } + + PrintAndLogEx(INFO, _CYAN_("%s") " is ready", cid); + + if (fn != NULL) { + PrintAndLogEx(INFO, "Publishing file..."); + mqtt_publish(&client, topic, data, bytes_read, MQTT_PUBLISH_QOS_0); + } else { + PrintAndLogEx(INFO, "Publishing message..."); + mqtt_publish(&client, topic, msg, strlen(msg) + 1, MQTT_PUBLISH_QOS_0); + } + + if (client.error != MQTT_OK) { + PrintAndLogEx(INFO, "error: %s", mqtt_error_str(client.error)); + mqtt_exit(PM3_ESOFT, sockfd, &client_daemon); + } + + msleep(4000); + + PrintAndLogEx(INFO, _CYAN_("%s") " disconnecting from " _CYAN_("%s"), cid, addr); + return mqtt_exit(PM3_SUCCESS, sockfd, &client_daemon); +} + +static int CmdMqttSend(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "mqtt send", + "This command send MQTT messages. You can send JSON file\n" + "Default server: proxdump.com:1883 topic: proxdump\n", + "mqtt send --msg \"Hello from Pm3\" --> sending msg to default server/port/topic\n" + "mqtt send -f myfile.json --> sending file to default server/port/topic\n" + "mqtt send --addr test.mosquitto.org -p 1883 --topic pm3 --msg \"custom mqtt server \"\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0(NULL, "addr", "", "MQTT server address"), + arg_str0("p", "port", "", "MQTT server port"), + arg_str0(NULL, "topic", "", "MQTT topic"), + arg_str0(NULL, "msg", "", "Message to send over MQTT"), + arg_str0("f", "file", "", "file to send"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + + int alen = 0; + char addr[256] = {0x00}; + int res = CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)addr, sizeof(addr), &alen); + + int plen = 0; + char port[10 + 1] = {0x00}; + res = CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)port, sizeof(port), &plen); + + int tlen = 0; + char topic[128] = {0x00}; + res = CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)topic, sizeof(topic), &tlen); + + int mlen = 0; + char msg[128] = {0x00}; + res = CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)msg, sizeof(msg), &mlen); + + int fnlen = 0; + char filename[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 5), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + + CLIParserFree(ctx); + + // Error message if... an error occured. + if (res) { + PrintAndLogEx(FAILED, "Error parsing input strings"); + return PM3_EINVARG; + } + + if (alen == 0) { + strcpy(addr, "proxdump.com"); + } + if (plen == 0) { + strcpy(port, "1883"); + } + if (tlen == 0) { + strcpy(topic, "proxdump"); + } + + if (fnlen) { + return mqtt_send(addr, port, topic, NULL, filename); + } + + if (mlen) { + return mqtt_send(addr, port, topic, msg, NULL); + } + return PM3_SUCCESS; +} + +static int CmdMqttReceive(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "mqtt receive", + "This command receives MQTT messages. JSON text will be saved to file if detected\n" + "Default server: proxdump.com:1883 topic: proxdump\n", + "mqtt receive --> listening to default server/port/topic\n" + "mqtt receive --addr test.mosquitto.org -p 1883 --topic pm3\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0(NULL, "addr", "", "MQTT server address"), + arg_str0("p", "port", "", "MQTT server port"), + arg_str0(NULL, "topic", "", "MQTT topic"), + arg_str0("f", "file", "", "file to send"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + + int alen = 0; + char addr[256] = {0x00}; + int res = CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)addr, sizeof(addr), &alen); + + int plen = 0; + char port[10 + 1] = {0x00}; + res = CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)port, sizeof(port), &plen); + + int tlen = 0; + char topic[128] = {0x00}; + res = CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)topic, sizeof(topic), &tlen); + + int fnlen = 0; + char filename[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + + CLIParserFree(ctx); + + // Error message if... an error occured. + if (res) { + PrintAndLogEx(FAILED, "Error parsing input strings"); + return PM3_EINVARG; + } + + if (alen == 0) { + strcpy(addr, "proxdump.com"); + } + if (plen == 0) { + strcpy(port, "1883"); + } + if (tlen == 0) { + strcpy(topic, "proxdump"); + } + + return mqtt_receive(addr, port, topic); +} + +static command_t CommandTable[] = { + {"help", CmdHelp, AlwaysAvailable, "This help"}, + {"send", CmdMqttSend, AlwaysAvailable, "Send messages or json file over MQTT"}, + {"receive", CmdMqttReceive, AlwaysAvailable, "Receive message or json file over MQTT"}, + {NULL, NULL, NULL, NULL} +}; + +static int CmdHelp(const char *Cmd) { + (void)Cmd; // Cmd is not used so far + CmdsHelp(CommandTable); + return 0; +} + +int CmdMqtt(const char *Cmd) { + clearCommandBuffer(); + return CmdsParse(CommandTable, Cmd); +} diff --git a/client/src/cmdmqtt.h b/client/src/cmdmqtt.h new file mode 100644 index 000000000..fad58ff8f --- /dev/null +++ b/client/src/cmdmqtt.h @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// MQTT commands +//----------------------------------------------------------------------------- + +#ifndef CMDMQTT_H__ +#define CMDMQTT_H__ + +#include "common.h" + +int CmdMqtt(const char *Cmd); + +#endif diff --git a/client/src/fileutils.c b/client/src/fileutils.c index f4c76f049..805235e42 100644 --- a/client/src/fileutils.c +++ b/client/src/fileutils.c @@ -278,7 +278,7 @@ int saveFileEx(const char *preferredName, const char *suffix, const void *data, // Opening file for writing in binary mode FILE *f = fopen(fileName, "wb"); - if (!f) { + if (f == NULL) { PrintAndLogEx(WARNING, "file not found or locked `" _YELLOW_("%s") "`", fileName); free(fileName); return PM3_EFILE; @@ -291,6 +291,33 @@ int saveFileEx(const char *preferredName, const char *suffix, const void *data, return PM3_SUCCESS; } +int saveFileTXT(const char *preferredName, const char *suffix, const void *data, size_t datalen, savePaths_t e_save_path) { + if (data == NULL || datalen == 0) { + return PM3_EINVARG; + } + + char *fileName = newfilenamemcopyEx(preferredName, suffix, e_save_path); + if (fileName == NULL) { + return PM3_EMALLOC; + } + + // We should have a valid filename now, e.g. dumpdata-3.txt + + // Opening file for writing in text mode + FILE *f = fopen(fileName, "w"); + if (f == NULL) { + PrintAndLogEx(WARNING, "file not found or locked `" _YELLOW_("%s") "`", fileName); + free(fileName); + return PM3_EFILE; + } + fwrite(data, 1, datalen, f); + fflush(f); + fclose(f); + PrintAndLogEx(SUCCESS, "Saved " _YELLOW_("%zu") " bytes to text file `" _YELLOW_("%s") "`", datalen, fileName); + free(fileName); + return PM3_SUCCESS; +} + int prepareJSON(json_t *root, JSONFileType ftype, uint8_t *data, size_t datalen, bool verbose, void (*callback)(json_t *)) { if (ftype != jsfCustom) { if (data == NULL || datalen == 0) { @@ -794,8 +821,9 @@ int saveFileJSONroot(const char *preferredName, void *root, size_t flags, bool v } int saveFileJSONrootEx(const char *preferredName, const void *root, size_t flags, bool verbose, bool overwrite, savePaths_t e_save_path) { - if (root == NULL) + if (root == NULL) { return PM3_EINVARG; + } char *filename = NULL; if (overwrite) @@ -975,7 +1003,7 @@ int loadFile_safeEx(const char *preferredName, const char *suffix, void **pdata, } FILE *f = fopen(path, "rb"); - if (!f) { + if (f == NULL) { PrintAndLogEx(WARNING, "file not found or locked `" _YELLOW_("%s") "`", path); free(path); return PM3_EFILE; @@ -1018,6 +1046,58 @@ int loadFile_safeEx(const char *preferredName, const char *suffix, void **pdata, return PM3_SUCCESS; } +int loadFile_TXTsafe(const char *preferredName, const char *suffix, void **pdata, size_t *datalen, bool verbose) { + + char *path; + int res = searchFile(&path, RESOURCES_SUBDIR, preferredName, suffix, false); + if (res != PM3_SUCCESS) { + return PM3_EFILE; + } + + FILE *f = fopen(path, "r"); + if (f == NULL) { + PrintAndLogEx(WARNING, "file not found or locked `" _YELLOW_("%s") "`", path); + free(path); + return PM3_EFILE; + } + free(path); + + // get filesize in order to malloc memory + fseek(f, 0, SEEK_END); + long fsize = ftell(f); + fseek(f, 0, SEEK_SET); + + if (fsize <= 0) { + PrintAndLogEx(FAILED, "error, when getting filesize"); + fclose(f); + return PM3_EFILE; + } + + *pdata = calloc(fsize, sizeof(uint8_t)); + if (*pdata == NULL) { + PrintAndLogEx(WARNING, "Failed to allocate memory"); + fclose(f); + return PM3_EMALLOC; + } + + size_t bytes_read = fread(*pdata, 1, fsize, f); + + fclose(f); + + if (bytes_read != fsize) { + PrintAndLogEx(FAILED, "error, bytes read mismatch file size"); + free(*pdata); + return PM3_EFILE; + } + + *datalen = bytes_read; + + if (verbose) { + PrintAndLogEx(SUCCESS, "Loaded " _YELLOW_("%zu") " bytes from text file `" _YELLOW_("%s") "`", bytes_read, preferredName); + } + return PM3_SUCCESS; +} + int loadFileEML_safe(const char *preferredName, void **pdata, size_t *datalen) { char *path; int res = searchFile(&path, RESOURCES_SUBDIR, preferredName, "", false); @@ -1121,7 +1201,7 @@ int loadFileNFC_safe(const char *preferredName, void *data, size_t maxdatalen, s } FILE *f = fopen(path, "r"); - if (!f) { + if (f == NULL) { PrintAndLogEx(WARNING, "file not found or locked `" _YELLOW_("%s") "`", path); free(path); return PM3_EFILE; @@ -1446,7 +1526,9 @@ int loadFileJSON(const char *preferredName, void *data, size_t maxdatalen, size_ } int loadFileJSONex(const char *preferredName, void *data, size_t maxdatalen, size_t *datalen, bool verbose, void (*callback)(json_t *)) { - if (data == NULL) return PM3_EINVARG; + if (data == NULL) { + return PM3_EINVARG; + } *datalen = 0; int retval = PM3_SUCCESS; @@ -2629,6 +2711,10 @@ int detect_nfc_dump_format(const char *preferredName, nfc_df_e *dump_type, bool *dump_type = NFC_DF_14_4A; break; } + if (str_startswith(line, "device type: iso15693")) { + *dump_type = NFC_DF_15; + break; + } if (str_startswith(line, "filetype: flipper picopass device")) { *dump_type = NFC_DF_PICOPASS; break; @@ -2661,6 +2747,8 @@ int detect_nfc_dump_format(const char *preferredName, nfc_df_e *dump_type, bool case NFC_DF_PICOPASS: PrintAndLogEx(INFO, "Detected PICOPASS based dump format"); break; + case NFC_DF_15: + PrintAndLogEx(INFO, "Detected ISO15693 based dump format"); case NFC_DF_UNKNOWN: PrintAndLogEx(WARNING, "Failed to detected dump format"); break; @@ -3210,7 +3298,7 @@ int pm3_load_dump(const char *fn, void **pdump, size_t *dumplen, size_t maxdumpl break; } - if (dumptype == NFC_DF_MFC || dumptype == NFC_DF_MFU || dumptype == NFC_DF_PICOPASS) { + if (dumptype == NFC_DF_MFC || dumptype == NFC_DF_MFU || dumptype == NFC_DF_PICOPASS || dumptype == NFC_DF_15) { *pdump = calloc(maxdumplen, sizeof(uint8_t)); if (*pdump == NULL) { @@ -3247,6 +3335,7 @@ int pm3_save_dump(const char *fn, uint8_t *d, size_t n, JSONFileType jsft) { PrintAndLogEx(INFO, "No data to save, skipping..."); return PM3_EINVARG; } + saveFile(fn, ".bin", d, n); saveFileJSON(fn, jsft, d, n, NULL); return PM3_SUCCESS; diff --git a/client/src/fileutils.h b/client/src/fileutils.h index cc6d9ee14..bbb548fb9 100644 --- a/client/src/fileutils.h +++ b/client/src/fileutils.h @@ -100,9 +100,16 @@ typedef enum { NFC_DF_14_3A, NFC_DF_14_3B, NFC_DF_14_4A, + NFC_DF_15, NFC_DF_PICOPASS, } nfc_df_e; +typedef enum { + ISO15_DF_UNKNOWN, + ISO15_DF_V4_BIN, + ISO15_DF_V5_BIN +} iso15_df_e; + int fileExists(const char *filename); // set a path in the path list g_session.defaultPaths @@ -116,7 +123,7 @@ void truncate_filename(char *fn, uint16_t maxlen); /** * @brief Utility function to save data to a binary file. This method takes a preferred name, but if that * file already exists, it tries with another name until it finds something suitable. - * E.g. dumpdata-15.txt + * E.g. dumpdata-15.bin * * @param preferredName * @param suffix the file suffix. Including the ".". @@ -127,6 +134,19 @@ void truncate_filename(char *fn, uint16_t maxlen); int saveFile(const char *preferredName, const char *suffix, const void *data, size_t datalen); int saveFileEx(const char *preferredName, const char *suffix, const void *data, size_t datalen, savePaths_t e_save_path); +/** + * @brief Utility function to save data to a text file. This method takes a preferred name, but if that + * file already exists, it tries with another name until it finds something suitable. + * E.g. dumpdata-15.txt + * + * @param preferredName + * @param suffix the file suffix. Including the ".". + * @param data The binary data to write to the file + * @param datalen the length of the data + * @return 0 for ok, 1 for failz + */ +int saveFileTXT(const char *preferredName, const char *suffix, const void *data, size_t datalen, savePaths_t e_save_path); + /** STUB * @brief Utility function to save JSON data to a file. This method takes a preferred name, but if that * file already exists, it tries with another name until it finds something suitable. @@ -190,6 +210,19 @@ int createMfcKeyDump(const char *preferredName, uint8_t sectorsCnt, const sector */ int loadFile_safe(const char *preferredName, const char *suffix, void **pdata, size_t *datalen); int loadFile_safeEx(const char *preferredName, const char *suffix, void **pdata, size_t *datalen, bool verbose); + +/** + * @brief Utility function to load a text file. This method takes a preferred name. + * E.g. dumpdata-15.json, tries to search for it, and allocated memory. + * + * @param preferredName + * @param suffix the file suffix. Including the ".". + * @param data The data array to store the loaded bytes from file + * @param datalen the number of bytes loaded from file + * @return PM3_SUCCESS for ok, PM3_E* for failz +*/ +int loadFile_TXTsafe(const char *preferredName, const char *suffix, void **pdata, size_t *datalen, bool verbose); + /** * @brief Utility function to load data from a textfile (EML). This method takes a preferred name. * E.g. dumpdata-15.txt diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index a7cb8a8a2..7cae2a36a 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -832,6 +832,9 @@ const static vocabulary_t vocabulary[] = { { 0, "mem spiffs upload" }, { 0, "mem spiffs view" }, { 0, "mem spiffs wipe" }, + { 1, "mqtt help" }, + { 1, "mqtt send" }, + { 1, "mqtt receive" }, { 1, "nfc help" }, { 1, "nfc decode" }, { 0, "nfc type1 read" }, diff --git a/doc/commands.json b/doc/commands.json index bcc254785..0d4ab0c28 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -1136,7 +1136,7 @@ }, "help": { "command": "help", - "description": "help Use ` help` for details of a command prefs { Edit client/device preferences... } -------- ----------------------- Technology ----------------------- analyse { Analyse utils... } data { Plot window / data buffer manipulation... } emv { EMV ISO-14443 / ISO-7816... } hf { High frequency commands... } hw { Hardware commands... } lf { Low frequency commands... } nfc { NFC commands... } piv { PIV commands... } reveng { CRC calculations from RevEng software... } smart { Smart card ISO-7816 commands... } script { Scripting commands... } trace { Trace manipulation... } wiegand { Wiegand format manipulation... } -------- ----------------------- General ----------------------- clear Clear screen hints Turn hints on / off msleep Add a pause in milliseconds rem Add a text line in log file quit exit Exit program --------------------------------------------------------------------------------------- auto available offline: no Run LF SEARCH / HF SEARCH / DATA PLOT / DATA SAVE", + "description": "help Use ` help` for details of a command prefs { Edit client/device preferences... } -------- ----------------------- Technology ----------------------- analyse { Analyse utils... } data { Plot window / data buffer manipulation... } emv { EMV ISO-14443 / ISO-7816... } hf { High frequency commands... } hw { Hardware commands... } lf { Low frequency commands... } mqtt { MQTT commmands... } nfc { NFC commands... } piv { PIV commands... } reveng { CRC calculations from RevEng software... } smart { Smart card ISO-7816 commands... } script { Scripting commands... } trace { Trace manipulation... } wiegand { Wiegand format manipulation... } -------- ----------------------- General ----------------------- clear Clear screen hints Turn hints on / off msleep Add a pause in milliseconds rem Add a text line in log file quit exit Exit program --------------------------------------------------------------------------------------- auto available offline: no Run LF SEARCH / HF SEARCH / DATA PLOT / DATA SAVE", "notes": [ "auto" ], @@ -12331,6 +12331,42 @@ ], "usage": "mem wipe [-h] [-p ]" }, + "mqtt help": { + "command": "mqtt help", + "description": "help This help send Send messages or json file over MQTT receive Receive message or json file over MQTT --------------------------------------------------------------------------------------- mqtt send available offline: yes This command send MQTT messages. You can send JSON file Default server: proxdump.com:1883 topic: proxdump", + "notes": [ + "mqtt send --msg \"Hello from Pm3\" -> sending msg to default server/port/topic", + "mqtt send -f myfile.json -> sending file to default server/port/topic", + "mqtt send --addr test.mosquitto.org -p 1883 --topic pm3 --msg \"custom mqtt server \"" + ], + "offline": true, + "options": [ + "-h, --help This help", + "--addr MQTT server address", + "-p, --port MQTT server port", + "--topic MQTT topic", + "--msg Message to send over MQTT", + "-f, --file file to send" + ], + "usage": "mqtt send [-h] [--addr ] [-p ] [--topic ] [--msg ] [-f ]" + }, + "mqtt receive": { + "command": "mqtt receive", + "description": "This command receives MQTT messages. JSON text will be saved to file if detected Default server: proxdump.com:1883 topic: proxdump", + "notes": [ + "mqtt receive -> listening to default server/port/topic", + "mqtt receive --addr test.mosquitto.org -p 1883 --topic pm3" + ], + "offline": true, + "options": [ + "-h, --help This help", + "--addr MQTT server address", + "-p, --port MQTT server port", + "--topic MQTT topic", + "-f, --file file to send" + ], + "usage": "mqtt receive [-h] [--addr ] [-p ] [--topic ] [-f ]" + }, "msleep": { "command": "msleep", "description": "Sleep for given amount of milliseconds", @@ -13375,8 +13411,8 @@ } }, "metadata": { - "commands_extracted": 768, + "commands_extracted": 770, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-07-06T18:10:18" + "extracted_on": "2025-07-08T19:08:23" } } diff --git a/doc/commands.md b/doc/commands.md index 9cdcf7a47..96bb744d9 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -1414,6 +1414,17 @@ Check column "offline" for their availability. |`mem spiffs wipe `|N |`Wipe all files from SPIFFS file system * dangerous *` +### mqtt + + { MQTT commmands... } + +|command |offline |description +|------- |------- |----------- +|`mqtt help `|Y |`This help` +|`mqtt send `|Y |`Send messages or json file over MQTT` +|`mqtt receive `|Y |`Receive message or json file over MQTT` + + ### nfc { NFC commands... }