diff --git a/.github/workflows/build-platformio-ubuntu.yml b/.github/workflows/build-platformio-ubuntu.yml index acb2a7b..bb02e29 100644 --- a/.github/workflows/build-platformio-ubuntu.yml +++ b/.github/workflows/build-platformio-ubuntu.yml @@ -52,6 +52,6 @@ jobs: working-directory: ./Platformio run: pio run -e esp32 - - name: Build PlatformIO env:windows_linux_64bit + - name: Build PlatformIO env:linux_64bit working-directory: ./Platformio - run: pio run -e windows_linux_64bit + run: pio run -e linux_64bit diff --git a/.github/workflows/build-platformio-windows.yml b/.github/workflows/build-platformio-windows.yml index 1609200..17cc16a 100644 --- a/.github/workflows/build-platformio-windows.yml +++ b/.github/workflows/build-platformio-windows.yml @@ -52,12 +52,12 @@ jobs: working-directory: ./Platformio run: pio run -e esp32 - - name: Build PlatformIO env:windows_linux_64bit + - name: Build PlatformIO env:windows_64bit if: ${{matrix.sys == 'mingw64'}} working-directory: ./Platformio - run: pio run -e windows_linux_64bit + run: pio run -e windows_64bit - - name: Build PlatformIO env:windows_linux_32bit + - name: Build PlatformIO env:windows_32bit if: ${{matrix.sys == 'mingw32'}} working-directory: ./Platformio - run: pio run -e windows_linux_32bit + run: pio run -e windows_32bit diff --git a/Platformio/hardware/ESP32/infrared_receiver_hal_esp32.cpp b/Platformio/hardware/ESP32/infrared_receiver_hal_esp32.cpp index 60ccf39..ce69ccf 100644 --- a/Platformio/hardware/ESP32/infrared_receiver_hal_esp32.cpp +++ b/Platformio/hardware/ESP32/infrared_receiver_hal_esp32.cpp @@ -46,9 +46,9 @@ uint8_t IR_VCC_GPIO = 25; // IR receiver power bool irReceiverEnabled = false; -tShowNewIRmessage_cb thisShowNewIRmessage_cb = NULL; -void set_showNewIRmessage_cb_HAL(tShowNewIRmessage_cb pShowNewIRmessage_cb) { - thisShowNewIRmessage_cb = pShowNewIRmessage_cb; +tAnnounceNewIRmessage_cb thisAnnounceNewIRmessage_cb = NULL; +void set_announceNewIRmessage_cb_HAL(tAnnounceNewIRmessage_cb pAnnounceNewIRmessage_cb) { + thisAnnounceNewIRmessage_cb = pAnnounceNewIRmessage_cb; } // The Serial connection baud rate. @@ -190,8 +190,8 @@ void infraredReceiver_loop_HAL() { message += typeToString((&results)->decode_type, (&results)->repeat); message += " "; message += resultToHexidecimal(&results); - if (thisShowNewIRmessage_cb != NULL) { - thisShowNewIRmessage_cb(std::string(message.c_str())); + if (thisAnnounceNewIRmessage_cb != NULL) { + thisAnnounceNewIRmessage_cb(std::string(message.c_str())); } yield(); // Feed the WDT (again) diff --git a/Platformio/hardware/ESP32/infrared_receiver_hal_esp32.h b/Platformio/hardware/ESP32/infrared_receiver_hal_esp32.h index 73979aa..7bcbc98 100644 --- a/Platformio/hardware/ESP32/infrared_receiver_hal_esp32.h +++ b/Platformio/hardware/ESP32/infrared_receiver_hal_esp32.h @@ -12,5 +12,5 @@ void infraredReceiver_loop_HAL(void); bool get_irReceiverEnabled_HAL(); void set_irReceiverEnabled_HAL(bool aIrReceiverEnabled); -typedef void (*tShowNewIRmessage_cb)(std::string message); -void set_showNewIRmessage_cb_HAL(tShowNewIRmessage_cb pShowNewIRmessage_cb); +typedef void (*tAnnounceNewIRmessage_cb)(std::string message); +void set_announceNewIRmessage_cb_HAL(tAnnounceNewIRmessage_cb pAnnounceNewIRmessage_cb); diff --git a/Platformio/hardware/ESP32/mqtt_hal_esp32.cpp b/Platformio/hardware/ESP32/mqtt_hal_esp32.cpp index 362883b..818b9a4 100644 --- a/Platformio/hardware/ESP32/mqtt_hal_esp32.cpp +++ b/Platformio/hardware/ESP32/mqtt_hal_esp32.cpp @@ -8,9 +8,14 @@ WiFiClient espClient; PubSubClient mqttClient(espClient); bool isWifiConnected = false; -showWiFiconnected_cb thisShowWiFiconnected_cb = NULL; -void set_showWiFiconnected_cb_HAL(showWiFiconnected_cb pShowWiFiconnected_cb) { - thisShowWiFiconnected_cb = pShowWiFiconnected_cb; +tAnnounceWiFiconnected_cb thisAnnounceWiFiconnected_cb = NULL; +void set_announceWiFiconnected_cb_HAL(tAnnounceWiFiconnected_cb pAnnounceWiFiconnected_cb) { + thisAnnounceWiFiconnected_cb = pAnnounceWiFiconnected_cb; +} + +tAnnounceSubscribedTopics_cb thisAnnounceSubscribedTopics_cb = NULL; +void set_announceSubscribedTopics_cb_HAL(tAnnounceSubscribedTopics_cb pAnnounceSubscribedTopics_cb) { + thisAnnounceSubscribedTopics_cb = pAnnounceSubscribedTopics_cb; } bool getIsWifiConnected_HAL() { @@ -30,12 +35,12 @@ void WiFiEvent(WiFiEvent_t event){ // Set status bar icon based on WiFi status if (event == ARDUINO_EVENT_WIFI_STA_GOT_IP || event == ARDUINO_EVENT_WIFI_STA_GOT_IP6) { isWifiConnected = true; - thisShowWiFiconnected_cb(true); + thisAnnounceWiFiconnected_cb(true); Serial.printf("WiFi connected, IP address: %s\r\n", WiFi.localIP().toString().c_str()); } else if (event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) { isWifiConnected = false; - thisShowWiFiconnected_cb(false); + thisAnnounceWiFiconnected_cb(false); // automatically try to reconnect Serial.printf("WiFi got disconnected. Will try to reconnect.\r\n"); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); @@ -44,7 +49,7 @@ void WiFiEvent(WiFiEvent_t event){ // e.g. ARDUINO_EVENT_WIFI_STA_CONNECTED or many others // connected is not enough, will wait for IP isWifiConnected = false; - thisShowWiFiconnected_cb(false); + thisAnnounceWiFiconnected_cb(false); } } @@ -57,6 +62,29 @@ void init_mqtt_HAL(void) { WiFi.setSleep(true); } +std::string subscribeTopicOMOTEtest = "OMOTE/test"; + +void callback(char* topic, byte* payload, unsigned int length) { + // handle message arrived + std::string topicReceived(topic); + std::string strPayload(reinterpret_cast(payload), length); + + if (topicReceived == subscribeTopicOMOTEtest) { + Serial.printf("MQTT: received topic %s with payload %s\r\n", subscribeTopicOMOTEtest.c_str(), strPayload.c_str()); + + } + + thisAnnounceSubscribedTopics_cb(topicReceived, strPayload); +} + +void mqtt_subscribeTopics() { + mqttClient.setCallback(&callback); + + mqttClient.subscribe(subscribeTopicOMOTEtest.c_str()); + Serial.printf(" Successfully subscribed to MQTT topic %s\r\n", subscribeTopicOMOTEtest.c_str()); + +} + bool checkMQTTconnection() { if (WiFi.isConnected()) { @@ -64,10 +92,17 @@ bool checkMQTTconnection() { return true; } else { // try to connect to mqtt server + mqttClient.setBufferSize(512); // default is 256 + //mqttClient.setKeepAlive(15); // default is 15 Client will send MQTTPINGREQ to keep connection alive + //mqttClient.setSocketTimeout(15); // default is 15 This determines how long the client will wait for incoming data when it expects data to arrive - for example, whilst it is in the middle of reading an MQTT packet. mqttClient.setServer(MQTT_SERVER, MQTT_SERVER_PORT); // MQTT initialization - if (mqttClient.connect(MQTT_CLIENTNAME, MQTT_USER, MQTT_PASS)) { + + std::string mqttClientName = std::string(MQTT_CLIENTNAME) + "_esp32_" + std::string(WiFi.macAddress().c_str()); + if (mqttClient.connect(mqttClientName.c_str(), MQTT_USER, MQTT_PASS)) { Serial.printf(" Successfully connected to MQTT broker\r\n"); + mqtt_subscribeTopics(); + } else { Serial.printf(" MQTT connection failed (but WiFi is available). Will try later ...\r\n"); @@ -75,11 +110,29 @@ bool checkMQTTconnection() { return mqttClient.connected(); } } else { - Serial.printf(" No connection to MQTT server, because WiFi ist not connected.\r\n"); + // Serial.printf(" No connection to MQTT server, because WiFi ist not connected.\r\n"); return false; } } +unsigned long reconnectInterval = 100; +// in order to do reconnect immediately ... +unsigned long lastReconnectAttempt = millis() - reconnectInterval - 1; +void mqtt_loop_HAL() { + if (!mqttClient.connected()) { + unsigned long currentMillis = millis(); + if ((currentMillis - lastReconnectAttempt) > reconnectInterval) { + lastReconnectAttempt = currentMillis; + // Attempt to reconnect + checkMQTTconnection(); + } + } + + if (mqttClient.connected()) { + mqttClient.loop(); + } +} + bool publishMQTTMessage_HAL(const char *topic, const char *payload){ if (checkMQTTconnection()) { diff --git a/Platformio/hardware/ESP32/mqtt_hal_esp32.h b/Platformio/hardware/ESP32/mqtt_hal_esp32.h index c16f0b7..116ae60 100644 --- a/Platformio/hardware/ESP32/mqtt_hal_esp32.h +++ b/Platformio/hardware/ESP32/mqtt_hal_esp32.h @@ -4,11 +4,13 @@ void init_mqtt_HAL(void); bool getIsWifiConnected_HAL(); +void mqtt_loop_HAL(); bool publishMQTTMessage_HAL(const char *topic, const char *payload); void wifiStop_HAL(); -typedef void (*showWiFiconnected_cb)(bool connected); -void set_showWiFiconnected_cb_HAL(showWiFiconnected_cb pShowWiFiconnected_cb); +typedef void (*tAnnounceWiFiconnected_cb)(bool connected); +void set_announceWiFiconnected_cb_HAL(tAnnounceWiFiconnected_cb pAnnounceWiFiconnected_cb); +typedef void (*tAnnounceSubscribedTopics_cb)(std::string topic, std::string payload); +void set_announceSubscribedTopics_cb_HAL(tAnnounceSubscribedTopics_cb pAnnounceSubscribedTopics_cb); #endif - diff --git a/Platformio/hardware/windows_linux/infrared_receiver_hal_windows_linux.cpp b/Platformio/hardware/windows_linux/infrared_receiver_hal_windows_linux.cpp index 5755e9a..50cd1bc 100644 --- a/Platformio/hardware/windows_linux/infrared_receiver_hal_windows_linux.cpp +++ b/Platformio/hardware/windows_linux/infrared_receiver_hal_windows_linux.cpp @@ -19,4 +19,4 @@ void set_irReceiverEnabled_HAL(bool aIrReceiverEnabled) { irReceiverEnabled = aIrReceiverEnabled; } -void set_showNewIRmessage_cb_HAL(tShowNewIRmessage_cb pShowNewIRmessage_cb) {} +void set_announceNewIRmessage_cb_HAL(tAnnounceNewIRmessage_cb pAnnounceNewIRmessage_cb) {} diff --git a/Platformio/hardware/windows_linux/infrared_receiver_hal_windows_linux.h b/Platformio/hardware/windows_linux/infrared_receiver_hal_windows_linux.h index 4c4cb6a..5ebde4d 100644 --- a/Platformio/hardware/windows_linux/infrared_receiver_hal_windows_linux.h +++ b/Platformio/hardware/windows_linux/infrared_receiver_hal_windows_linux.h @@ -9,5 +9,5 @@ void infraredReceiver_loop_HAL(void); bool get_irReceiverEnabled_HAL(); void set_irReceiverEnabled_HAL(bool aIrReceiverEnabled); -typedef void (*tShowNewIRmessage_cb)(std::string message); -void set_showNewIRmessage_cb_HAL(tShowNewIRmessage_cb pShowNewIRmessage_cb); +typedef void (*tAnnounceNewIRmessage_cb)(std::string message); +void set_announceNewIRmessage_cb_HAL(tAnnounceNewIRmessage_cb pAnnounceNewIRmessage_cb); diff --git a/Platformio/hardware/windows_linux/lib/MQTT-C/LICENSE b/Platformio/hardware/windows_linux/lib/MQTT-C/LICENSE new file mode 100644 index 0000000..0bbb845 --- /dev/null +++ b/Platformio/hardware/windows_linux/lib/MQTT-C/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/Platformio/hardware/windows_linux/lib/MQTT-C/README.md b/Platformio/hardware/windows_linux/lib/MQTT-C/README.md new file mode 100644 index 0000000..1367d25 --- /dev/null +++ b/Platformio/hardware/windows_linux/lib/MQTT-C/README.md @@ -0,0 +1,106 @@ +

+ + +

+ +

+
+ + + + + +

+ +MQTT-C is an [MQTT v3.1.1](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html) +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. + +#### A note from the author +It's been great to hear about all the places MQTT-C is being used! Please don't hesitate +to get in touch with me or submit issues on GitHub! + +## Getting Started +To use MQTT-C you first instantiate a `struct mqtt_client` and initialize it by calling +@ref mqtt_init. +```c + struct mqtt_client client; /* instantiate the client */ + mqtt_init(&client, ...); /* initialize the client */ +``` +Once your client is initialized you need to connect to an MQTT broker. +```c + mqtt_connect(&client, ...); /* send a connection request to the broker. */ +``` +At this point the client is ready to use! For example, we can subscribe to a topic like so: +```c + /* subscribe to "toaster/temperature" with a max QoS level of 0 */ + mqtt_subscribe(&client, "toaster/temperature", 0); +``` +And we can publish to a topic like so: +```c + /* publish coffee temperature with a QoS level of 1 */ + int temperature = 67; + mqtt_publish(&client, "coffee/temperature", &temperature, sizeof(int), MQTT_PUBLISH_QOS_1); +``` +Those are the basics! From here the [examples](https://github.com/LiamBindle/MQTT-C/tree/master/examples) and [API documentation](https://liambindle.ca/MQTT-C/group__api.html) are good places to get started. + +## Building +There are **only two source files** that need to be built, `mqtt.c` and `mqtt_pal.c`. +These files are ANSI C (C89) compatible, and should compile with any C compiler. + +Then, simply \#include . + +Alternatively, you can build MQTT-C with CMake or the provided Makefile. These are provided for convenience. + +## Documentation +Pre-built documentation can be found here: [https://liambindle.ca/MQTT-C](https://liambindle.ca/MQTT-C). Be sure to check out the [examples](https://github.com/LiamBindle/MQTT-C/tree/master/examples) too. + +The @ref api documentation contains all the documentation application programmers should need. +The @ref pal documentation contains everything you should need to port MQTT-C to a new platform, +and the other modules contain documentation for MQTT-C developers. + +## Testing and Building the Tests +The MQTT-C unit tests use the [cmocka unit testing framework](https://cmocka.org/). +Therefore, [cmocka](https://cmocka.org/) *must* be installed on your machine to build and run +the unit tests. For convenience, a simple `"makefile"` is included to build the unit tests and +examples on UNIX-like machines. The unit tests and examples can be built as follows: +```bash + $ make all +``` +The unit tests and examples will be built in the `"bin/"` directory. The unit tests can be run +like so: +```bash + $ ./bin/tests [address [port]] +``` +Note that the \c address and \c port arguments are both optional to specify the location of the +MQTT broker that is to be used for the tests. If no \c address is given then the +[Mosquitto MQTT Test Server](https://test.mosquitto.org/) will be used. If no \c port is given, +port 1883 will be used. + +## Portability +MQTT-C provides a transparent platform abstraction layer (PAL) in `mqtt_pal.h` and `mqtt_pal.c`. +These files declare and implement the types and calls that MQTT-C requires. Refer to +@ref pal for the complete documentation of the PAL. + +## Contributing +Please feel free to submit issues and pull-requests [here](https://github.com/LiamBindle/MQTT-C). +When submitting a pull-request please ensure you have *fully documented* your changes and +added the appropriate unit tests. + + +## License +This project is licensed under the [MIT License](https://opensource.org/licenses/MIT). See the +`"LICENSE"` file for more details. + +## 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** + diff --git a/Platformio/hardware/windows_linux/lib/MQTT-C/include/mqtt.h b/Platformio/hardware/windows_linux/lib/MQTT-C/include/mqtt.h new file mode 100644 index 0000000..8695ab8 --- /dev/null +++ b/Platformio/hardware/windows_linux/lib/MQTT-C/include/mqtt.h @@ -0,0 +1,1623 @@ +#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 +#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 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/Platformio/hardware/windows_linux/lib/MQTT-C/include/mqtt_pal.h b/Platformio/hardware/windows_linux/lib/MQTT-C/include/mqtt_pal.h new file mode 100644 index 0000000..e73a3d8 --- /dev/null +++ b/Platformio/hardware/windows_linux/lib/MQTT-C/include/mqtt_pal.h @@ -0,0 +1,203 @@ +#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; + #elif defined(MQTT_USE_WOLFSSL) + #include + typedef WOLFSSL* mqtt_pal_socket_handle; + #elif defined(MQTT_USE_BIO) + #include + typedef BIO* mqtt_pal_socket_handle; + #elif defined(MQTT_USE_BEARSSL) + #include + + typedef struct _bearssl_context { + br_ssl_client_context sc; + br_x509_minimal_context xc; + br_sslio_context ioc; + size_t ta_count; + br_x509_trust_anchor *anchOut; + int fd; + int (*low_read)(void *read_context, unsigned char *buf, size_t len); + int (*low_write)(void *write_context, const unsigned char *buf, size_t len); + } bearssl_context; + + typedef bearssl_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 + + // original from MQTT-C + // typedef SSIZE_T ssize_t; + // changed for usage in OMOTE + #if defined(WIN64) + typedef SSIZE_T ssize_t; + #endif + #define MQTT_PAL_HTONS(s) htons(s) + #define MQTT_PAL_NTOHS(s) ntohs(s) + + #define MQTT_PAL_TIME() time(NULL) + + typedef time_t mqtt_pal_time_t; + typedef 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) + #if defined(MQTT_USE_BIO) + #include + typedef BIO* mqtt_pal_socket_handle; + #else + typedef SOCKET mqtt_pal_socket_handle; + #endif + #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/Platformio/hardware/windows_linux/lib/MQTT-C/include/posix_sockets.h b/Platformio/hardware/windows_linux/lib/MQTT-C/include/posix_sockets.h new file mode 100644 index 0000000..3ba9f28 --- /dev/null +++ b/Platformio/hardware/windows_linux/lib/MQTT-C/include/posix_sockets.h @@ -0,0 +1,103 @@ +#if !defined(__POSIX_SOCKET_TEMPLATE_H__) +#define __POSIX_SOCKET_TEMPLATE_H__ + +#include +#include +#if !defined(WIN32) +#include +#include +#include +#include +#else +#include +#endif +#if defined(__VMS) +#include +#endif +#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, char *MACaddress) { + struct addrinfo hints = {0}; + + 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\r\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; + } + + #if !defined(WIN32) + // get MAC address + struct ifreq s; + ioctl(sockfd, SIOCGIFHWADDR, &s); + char buffer[6*3]; + int i; + for (i = 0; i < 6; ++i) { + sprintf(&buffer[i*3], "%02x:", (unsigned char) s.ifr_addr.sa_data[i]); + //printf(" %02x", (unsigned char) s.ifr_addr.sa_data[i]); + } + //printf("\r\n"); + buffer[17] = '\0'; + + std::string strMACaddress = std::string(buffer, 18); + strMACaddress.copy(MACaddress, 18); + // printf(" MAC address from posix_sockets %s\r\n", strMACaddress.c_str()); + #endif + + 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) { + // original from MQTT-C + // int iMode = 1; + // changed for usage in OMOTE + u_long 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; +} + +#endif diff --git a/Platformio/hardware/windows_linux/lib/MQTT-C/src/mqtt.c b/Platformio/hardware/windows_linux/lib/MQTT-C/src/mqtt.c new file mode 100644 index 0000000..d914c1a --- /dev/null +++ b/Platformio/hardware/windows_linux/lib/MQTT-C/src/mqtt.c @@ -0,0 +1,1791 @@ +/* +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 + +/** + * @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/Platformio/hardware/windows_linux/lib/MQTT-C/src/mqtt_pal.c b/Platformio/hardware/windows_linux/lib/MQTT-C/src/mqtt_pal.c new file mode 100644 index 0000000..27e8149 --- /dev/null +++ b/Platformio/hardware/windows_linux/lib/MQTT-C/src/mqtt_pal.c @@ -0,0 +1,440 @@ +/* +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 + +/** + * @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(MQTT_USE_WOLFSSL) +#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) { + int tmp = wolfSSL_write(fd, buf + sent, (int)(len - sent)); + if (tmp <= 0) { + tmp = wolfSSL_get_error(fd, tmp); + if (tmp == WOLFSSL_ERROR_WANT_READ || tmp == WOLFSSL_ERROR_WANT_WRITE) { + break; + } + return MQTT_ERROR_SOCKET_ERROR; + } + sent += (size_t)tmp; + } + 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; + int tmp; + do { + tmp = wolfSSL_read(fd, buf, (int)bufsz); + if (tmp <= 0) { + tmp = wolfSSL_get_error(fd, tmp); + if (tmp == WOLFSSL_ERROR_WANT_READ || tmp == WOLFSSL_ERROR_WANT_WRITE) { + break; + } + return MQTT_ERROR_SOCKET_ERROR; + } + buf = (char*)buf + tmp; + bufsz -= tmp; + } while (tmp > 0 && bufsz > 0); + + return (ssize_t)(buf - start); +} + +#elif defined(MQTT_USE_BEARSSL) +#include +#include + +static int do_rec_data(mqtt_pal_socket_handle fd, unsigned int status) { + ssize_t rc; + uint8_t *buffer; + size_t length; + int err; + + err = br_ssl_engine_last_error(&fd->sc.eng); + + if (err != BR_ERR_OK) { + return MQTT_ERROR_SOCKET_ERROR; + } + + if ((status & BR_SSL_SENDREC) == BR_SSL_SENDREC) { + buffer = br_ssl_engine_sendrec_buf(&fd->sc.eng, &length); + + if (length > 0) { + if ((rc = fd->low_write(&fd->fd, buffer, length)) < 0) { + return MQTT_ERROR_SOCKET_ERROR; + } + + br_ssl_engine_sendrec_ack(&fd->sc.eng, rc); + } + } + else if ((status & BR_SSL_RECVREC) == BR_SSL_RECVREC) { + buffer = br_ssl_engine_recvrec_buf(&fd->sc.eng, &length); + + if (length > 0) { + if ((rc = fd->low_read(&fd->fd, buffer, length)) < 0) { + return MQTT_ERROR_SOCKET_ERROR; + } + + br_ssl_engine_recvrec_ack(&fd->sc.eng, rc); + } + } + else if ((status && BR_SSL_CLOSED) == BR_SSL_CLOSED) { + return MQTT_ERROR_SOCKET_ERROR; + } + + return MQTT_OK; +} + +ssize_t mqtt_pal_sendall(mqtt_pal_socket_handle fd, const void* buf, size_t len, int flags) { + int rc = MQTT_OK; + uint8_t *buffer; + size_t length; + size_t remaining_bytes = len; + const uint8_t *walker = buf; + unsigned int status; + + while (remaining_bytes > 0) { + + if (rc == MQTT_ERROR_SOCKET_ERROR) { + return rc; + } + + status = br_ssl_engine_current_state(&fd->sc.eng); + + if ((status & BR_SSL_CLOSED) != 0) { + return MQTT_ERROR_SOCKET_ERROR; + } + + if ((status & (BR_SSL_RECVREC | BR_SSL_SENDREC)) != 0) { + rc = do_rec_data(fd, status); + + if (rc != MQTT_OK) { + return rc; + } + status = br_ssl_engine_current_state(&fd->sc.eng); + } + + if ((status & BR_SSL_SENDAPP) == BR_SSL_SENDAPP) { + buffer = br_ssl_engine_sendapp_buf(&fd->sc.eng, &length); + + if (length > 0) { + size_t write = length >= remaining_bytes? remaining_bytes : length; + memcpy(buffer, walker, write); + remaining_bytes -= write; + walker += write; + br_ssl_engine_sendapp_ack(&fd->sc.eng, write); + br_ssl_engine_flush(&fd->sc.eng, 0); + } + } + } + + return len; +} + +ssize_t mqtt_pal_recvall(mqtt_pal_socket_handle fd, void* buf, size_t bufsz, int flags) { + int rc = MQTT_OK; + uint8_t *buffer; + size_t length; + size_t remaining_bytes = bufsz; + uint8_t *walker = buf; + unsigned int status; + + if (rc == MQTT_ERROR_SOCKET_ERROR) { + return rc; + } + + status = br_ssl_engine_current_state(&fd->sc.eng); + + if ((status & (BR_SSL_RECVREC | BR_SSL_SENDREC)) != 0) { + rc = do_rec_data(fd, status); + + if (rc != MQTT_OK) { + return rc; + } + status = br_ssl_engine_current_state(&fd->sc.eng); + } + + if ((status & BR_SSL_RECVAPP) == BR_SSL_RECVAPP) { + buffer = br_ssl_engine_recvapp_buf(&fd->sc.eng, &length); + + if (length > 0) { + size_t write = length >= remaining_bytes? remaining_bytes : length; + memcpy(walker, buffer, write); + remaining_bytes -= write; + walker += write; + br_ssl_engine_recvapp_ack(&fd->sc.eng, write); + } + } + + return bufsz - remaining_bytes; +} + +#elif defined(MQTT_USE_BIO) +#include +#include +#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) { + int tmp = BIO_write(fd, (const char*)buf + sent, (int)(len - sent)); + if (tmp > 0) { + sent += (size_t) tmp; + } else if (tmp <= 0 && !BIO_should_retry(fd)) { + return MQTT_ERROR_SOCKET_ERROR; + } + } + + return (ssize_t)sent; +} + +ssize_t mqtt_pal_recvall(mqtt_pal_socket_handle fd, void* buf, size_t bufsz, int flags) { + const char* const start = (const char*)buf; + char* bufptr = (char*)buf; + int rv; + do { + rv = BIO_read(fd, bufptr, (int)bufsz); + if (rv > 0) { + /* successfully read bytes from the socket */ + bufptr += rv; + bufsz -= (unsigned long)rv; + } else if (!BIO_should_retry(fd)) { + /* an error occurred that wasn't "nothing to read". */ + return MQTT_ERROR_SOCKET_ERROR; + } + } while (!BIO_should_read(fd) && bufsz > 0); + + return (ssize_t)(bufptr - 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/Platformio/hardware/windows_linux/mqtt_hal_windows_linux.cpp b/Platformio/hardware/windows_linux/mqtt_hal_windows_linux.cpp index 67e56b9..cfed6be 100644 --- a/Platformio/hardware/windows_linux/mqtt_hal_windows_linux.cpp +++ b/Platformio/hardware/windows_linux/mqtt_hal_windows_linux.cpp @@ -1,18 +1,198 @@ +#include #include "mqtt_hal_windows_linux.h" +#include "secrets.h" + #if (ENABLE_WIFI_AND_MQTT == 1) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// example is mainly taken from .pio/libdeps/windows_linux_64bit/MQTT-C/tests.c, TEST__api__publish_subscribe__single +#if !defined(WIN32) +#include +#include +#include +#include +#include +#include +#else +#include + +/* Some shortcuts to call winapi in a posix-like way */ +#define close(sock) closesocket(sock) +#define usleep(usec) Sleep((usec) / 1000) +#endif + +#include "lib/MQTT-C/include/mqtt.h" +#include "lib/MQTT-C/include/posix_sockets.h" + +int sockfd = -1; +uint8_t sendmem1[1024], sendmem2[1024]; +uint8_t recvmem1[1024], recvmem2[1024]; +struct mqtt_client mqttClient; +std::string uniqueClientSuffix = ""; +int state = 0; + +tAnnounceWiFiconnected_cb thisAnnounceWiFiconnected_cb = NULL; +void set_announceWiFiconnected_cb_HAL(tAnnounceWiFiconnected_cb pAnnounceWiFiconnected_cb) { + thisAnnounceWiFiconnected_cb = pAnnounceWiFiconnected_cb; +} + +tAnnounceSubscribedTopics_cb thisAnnounceSubscribedTopics_cb = NULL; +void set_announceSubscribedTopics_cb_HAL(tAnnounceSubscribedTopics_cb pAnnounceSubscribedTopics_cb) { + thisAnnounceSubscribedTopics_cb = pAnnounceSubscribedTopics_cb; +} bool getIsWifiConnected_HAL() { + return (sockfd != -1); +} + +void publish_callback(void** state, struct mqtt_response_publish *publish) { + **(int**)state += 1; + printf("message nr %d received\r\n", **(int**)state); + + std::string topic((const char*) (publish->topic_name), publish->topic_name_size); + std::string payload((const char*) (publish->application_message), publish->application_message_size); + + printf("Received a PUBLISH(topic=%s, DUP=%d, QOS=%d, RETAIN=%d, pid=%d) from the broker. Data='%s'\r\n", + topic.c_str(), publish->dup_flag, publish->qos_level, publish->retain_flag, publish->packet_id, + payload.c_str() + ); + + thisAnnounceSubscribedTopics_cb(topic, payload); +} + +void mqtt_subscribeTopics() { + mqtt_subscribe(&mqttClient, "OMOTE/test", 2); + +} + +void reconnect_mqtt(struct mqtt_client *mqttClient, void**) { + printf("MQTT: will reconnect ...\r\n"); + + mqtt_reinit(mqttClient, sockfd, sendmem1, sizeof(sendmem1), recvmem1, sizeof(recvmem1)); + + std::string mqttClientName = std::string(MQTT_CLIENTNAME) + uniqueClientSuffix; + // client_id, will_topic, will_message, will_message_size, user_name, password, connect_flags, keep_alive + mqtt_connect(mqttClient, mqttClientName.c_str(), NULL, NULL, 0, MQTT_USER, MQTT_PASS, 0, 30); + if (mqttClient->error != MQTT_OK) { + printf("MQTT: connect error: %s\r\n", mqtt_error_str(mqttClient->error)); + // sockfd = -1; + // return; + } +} + +#if !defined(WIN32) +std::string getMACaddress() { + struct ifreq s; + int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + + strcpy(s.ifr_name, "eth0"); + if (0 == ioctl(fd, SIOCGIFHWADDR, &s)) { + char buffer[6*3]; + int i; + for (i = 0; i < 6; ++i) { + sprintf(&buffer[i*3], "%02x:", (unsigned char) s.ifr_addr.sa_data[i]); + // printf(" %02x", (unsigned char) s.ifr_addr.sa_data[i]); + } + //printf("\r\n"); + + std::string MACaddress = std::string(buffer, 17); + printf(" result in MACaddress(): %s\r\n", MACaddress.c_str()); + return MACaddress; + } + return ""; +} +#endif + +void init_mqtt_HAL(void) { + #if defined(WIN32) + WSADATA wsaData; + int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (iResult != NO_ERROR) { + printf("Failed to init sockets: %i\r\n", iResult); + return; // return iResult; + } + #endif + + char MACaddress[6*3]; + sockfd = open_nb_socket(MQTT_SERVER, std::to_string(MQTT_SERVER_PORT).c_str(), MACaddress); + if (sockfd == -1) { + printf("MQTT: Failed to open socket\r\n"); + return; + } + + #if !defined(WIN32) + // MAC address is not the best. You cannot start more than one instance like that, otherwise the MQTT broker will only keep the last connection. + // printf("MQTT: received MAC address from posix_sockets.h is %s\r\n", MACaddress); + // uniqueClientSuffix = std::string(MACaddress, 18); + // simply use a random number + srand(time(NULL)); // Initialization, should only be called once. + int r = rand(); // Returns a pseudo-random integer between 0 and RAND_MAX. + uniqueClientSuffix = "_linux_" + std::to_string(r); + #else + srand(time(NULL)); // Initialization, should only be called once. + int r = rand(); // Returns a pseudo-random integer between 0 and RAND_MAX. + uniqueClientSuffix = "_windows_" + std::to_string(r); + #endif + + // printf("MQTT: MAC address from getMACaddress() in mqtt_hal_windows_linux.cpp is %s\r\n", getMACaddress().c_str()); + + // printf("MQTT: will init ...\r\n"); + // mqtt_init(&mqttClient, sockfd, sendmem1, sizeof(sendmem1), recvmem1, sizeof(recvmem1), publish_callback); + printf("MQTT: will init with reconnect ...\r\n"); + mqtt_init_reconnect(&mqttClient, reconnect_mqtt, NULL, publish_callback); + reconnect_mqtt(&mqttClient, NULL); + mqttClient.publish_response_callback_state = &state; + + mqtt_subscribeTopics(); + + thisAnnounceWiFiconnected_cb(true); + +} + +void mqtt_loop_HAL() { + if (sockfd != -1) { + mqtt_sync(&mqttClient); + } + +} + +bool publishMQTTMessage_HAL(const char *topic, const char *payload) { + + if (sockfd == -1) { + init_mqtt_HAL(); + } + + mqtt_publish(&mqttClient, topic, payload, strlen(payload), MQTT_PUBLISH_QOS_0); + if (mqttClient.error != MQTT_OK) { + printf("MQTT: publish error %s\r\n", mqtt_error_str(mqttClient.error)); + sockfd = -1; + return false; + } + return true; } -void init_mqtt_HAL(void) {} +void wifiStop_HAL() { + /* disconnect */ + if (sockfd != -1) { + mqtt_disconnect(&mqttClient); + mqtt_sync(&mqttClient); + } + + #if defined(WIN32) + WSACleanup(); + #endif -bool publishMQTTMessage_HAL(const char *topic, const char *payload){ - return false; } -void wifiStop_HAL() {} - -void set_showWiFiconnected_cb_HAL(showWiFiconnected_cb pShowWiFiconnected_cb) {} - #endif diff --git a/Platformio/hardware/windows_linux/mqtt_hal_windows_linux.h b/Platformio/hardware/windows_linux/mqtt_hal_windows_linux.h index 148816f..116ae60 100644 --- a/Platformio/hardware/windows_linux/mqtt_hal_windows_linux.h +++ b/Platformio/hardware/windows_linux/mqtt_hal_windows_linux.h @@ -4,10 +4,13 @@ void init_mqtt_HAL(void); bool getIsWifiConnected_HAL(); +void mqtt_loop_HAL(); bool publishMQTTMessage_HAL(const char *topic, const char *payload); void wifiStop_HAL(); -typedef void (*showWiFiconnected_cb)(bool connected); -void set_showWiFiconnected_cb_HAL(showWiFiconnected_cb pShowWiFiconnected_cb); +typedef void (*tAnnounceWiFiconnected_cb)(bool connected); +void set_announceWiFiconnected_cb_HAL(tAnnounceWiFiconnected_cb pAnnounceWiFiconnected_cb); +typedef void (*tAnnounceSubscribedTopics_cb)(std::string topic, std::string payload); +void set_announceSubscribedTopics_cb_HAL(tAnnounceSubscribedTopics_cb pAnnounceSubscribedTopics_cb); #endif diff --git a/Platformio/platformio.ini b/Platformio/platformio.ini index da73520..ed93a0a 100644 --- a/Platformio/platformio.ini +++ b/Platformio/platformio.ini @@ -143,8 +143,8 @@ build_src_filter = +<../hardware/ESP32/*> ;+<../hardware/ESP32/lib/ESP32-BLE-Keyboard/*> -; use this if you have a 64 bit compiler (Ubuntu, WSL2, Windows with MSYS2 MINGW64) -[env:windows_linux_64bit] +; use this if you are using Ubuntu or WSL2 (64 bit compiler) +[env:linux_64bit] platform = native@^1.2.1 lib_deps = ${env.lib_deps} @@ -169,20 +169,29 @@ build_flags = -D SDL_ZOOM=2 ;-- hardware abstraction, needed to find hardwareLayer.h ------------------ -I hardware + -I hardware/windows_linux/lib/MQTT-C/include build_src_filter = +<*> +<../hardware/windows_linux/*> -; use this if you have a 32 bit compiler (Windows MSYS2 MINGW32) -[env:windows_linux_32bit] -extends = env:windows_linux_64bit +; use this if you are using Windows MSYS2 MINGW64 (64 bit compiler) +[env:windows_64bit] +extends = env:linux_64bit +build_flags = + ${env:linux_64bit.build_flags} + ; winsock + -l ws2_32 + +; use this if you are using Windows MSYS2 MINGW32 (32 bit compiler) +[env:windows_32bit] +extends = env:windows_64bit build_unflags = - ${env:windows_linux_64bit.build_unflags} + ${env:windows_64bit.build_unflags} ;-- lvgl ------------------------------------------------------------------ -D LV_MEM_CUSTOM=0 -D LV_MEM_SIZE="(64U * 1024U)" build_flags = - ${env:windows_linux_64bit.build_flags} + ${env:windows_64bit.build_flags} ;-- lvgl ------------------------------------------------------------------ ; 32 bit needs exact the same lvgl memory as on ESP32 -D LV_MEM_CUSTOM=0 diff --git a/Platformio/src/applicationInternal/commandHandler.cpp b/Platformio/src/applicationInternal/commandHandler.cpp index bdd0e30..c881e75 100644 --- a/Platformio/src/applicationInternal/commandHandler.cpp +++ b/Platformio/src/applicationInternal/commandHandler.cpp @@ -7,6 +7,10 @@ #include "applicationInternal/scenes/sceneHandler.h" #include "applicationInternal/hardware/hardwarePresenter.h" #include "devices/misc/device_specialCommands.h" +// show WiFi status +#include "applicationInternal/gui/guiBase.h" +// show received IR and MQTT messages +#include "guis/gui_irReceiver.h" uint16_t KEYBOARD_DUMMY_UP ; //"Keyboard_dummy_up" uint16_t KEYBOARD_DUMMY_DOWN ; //"Keyboard_dummy_down" @@ -250,3 +254,29 @@ void executeCommand(uint16_t command, std::string additionalPayload) { Serial.printf("executeCommand: internal error, command not registered\r\n"); } } + +void receiveNewIRmessage_cb(std::string message) { + showNewIRmessage(message); +} +#if (ENABLE_WIFI_AND_MQTT == 1) +void receiveWiFiConnected_cb(bool connected) { + // show status in header + showWiFiConnected(connected); + + if (connected) { + // Here you could add sending a MQTT message. This message could be recognized by your home automation software. + // When receiving this message, your home automation software could send the states of the smart home devices known to OMOTE. + // With that, OMOTE could show on startup the correct status of the smart home devices. + // + // Remark: in your home automation software, maybe add a short delay (e.g. 100-200 ms) between receiving this message and sending out the status of the smart home devices. + // WiFi connection could be already available, but MQTT connection could be not completely ready. Just try what works for you. + + // executeCommand(TRIGGER_UPDATE_OF_OMOTE_SMART_HOME_DEVICES); + + } +} +void receiveMQTTmessage_cb(std::string topic, std::string payload) { + showMQTTmessage(topic, payload); +} + +#endif diff --git a/Platformio/src/applicationInternal/commandHandler.h b/Platformio/src/applicationInternal/commandHandler.h index bc8ac25..1bf73a1 100644 --- a/Platformio/src/applicationInternal/commandHandler.h +++ b/Platformio/src/applicationInternal/commandHandler.h @@ -116,3 +116,10 @@ void get_uniqueCommandID(uint16_t *command); void register_keyboardCommands(); commandData makeCommandData(commandHandlers a, std::list b); void executeCommand(uint16_t command, std::string additionalPayload = ""); + +void receiveNewIRmessage_cb(std::string message); +#if (ENABLE_WIFI_AND_MQTT == 1) +// used as callbacks from hardware +void receiveWiFiConnected_cb(bool connected); +void receiveMQTTmessage_cb(std::string topic, std::string payload); +#endif diff --git a/Platformio/src/applicationInternal/gui/guiBase.cpp b/Platformio/src/applicationInternal/gui/guiBase.cpp index f994069..9c310b2 100644 --- a/Platformio/src/applicationInternal/gui/guiBase.cpp +++ b/Platformio/src/applicationInternal/gui/guiBase.cpp @@ -291,7 +291,7 @@ void setActiveTab(uint32_t index, lv_anim_enable_t anim_en) { } } -void showWiFiConnected_cb(bool connected) { +void showWiFiConnected(bool connected) { if (connected) { if (WifiLabel != NULL) {lv_label_set_text(WifiLabel, LV_SYMBOL_WIFI);} } else { diff --git a/Platformio/src/applicationInternal/gui/guiBase.h b/Platformio/src/applicationInternal/gui/guiBase.h index c02ae73..9540b94 100644 --- a/Platformio/src/applicationInternal/gui/guiBase.h +++ b/Platformio/src/applicationInternal/gui/guiBase.h @@ -33,5 +33,5 @@ void tabview_tab_changed_event_cb(lv_event_t* e); void setActiveTab(uint32_t index, lv_anim_enable_t anim_en); // used by memoryUsage.cpp void showMemoryUsageBar(bool showBar); -// used as callback from hardware -void showWiFiConnected_cb(bool connected); +// used by commandHandler to show WiFi status +void showWiFiConnected(bool connected); diff --git a/Platformio/src/applicationInternal/hardware/hardwarePresenter.cpp b/Platformio/src/applicationInternal/hardware/hardwarePresenter.cpp index fe90fb2..eeb20b3 100644 --- a/Platformio/src/applicationInternal/hardware/hardwarePresenter.cpp +++ b/Platformio/src/applicationInternal/hardware/hardwarePresenter.cpp @@ -3,6 +3,8 @@ #include "applicationInternal/hardware/hardwarePresenter.h" // for registering the callback to show received IR messages #include "guis/gui_irReceiver.h" +// for registering the callback to receive MQTT messages +#include "../commandHandler.h" // for registering the callback to show WiFi status #include "applicationInternal/gui/guiBase.h" @@ -111,9 +113,9 @@ bool get_irReceiverEnabled() { } void set_irReceiverEnabled(bool aIrReceiverEnabled) { if (aIrReceiverEnabled) { - set_showNewIRmessage_cb_HAL(&showNewIRmessage_cb); + set_announceNewIRmessage_cb_HAL(&receiveNewIRmessage_cb); } else { - set_showNewIRmessage_cb_HAL(NULL); + set_announceNewIRmessage_cb_HAL(NULL); } set_irReceiverEnabled_HAL(aIrReceiverEnabled); } @@ -169,13 +171,17 @@ void init_lvgl_hardware() { // --- WiFi / MQTT ------------------------------------------------------------ #if (ENABLE_WIFI_AND_MQTT == 1) void init_mqtt(void) { - set_showWiFiconnected_cb_HAL(&showWiFiConnected_cb); + set_announceWiFiconnected_cb_HAL(&receiveWiFiConnected_cb); + set_announceSubscribedTopics_cb_HAL(receiveMQTTmessage_cb); init_mqtt_HAL(); } // used by "commandHandler.cpp", "sleep.cpp" bool getIsWifiConnected() { return getIsWifiConnected_HAL(); } +void mqtt_loop() { + mqtt_loop_HAL(); +} bool publishMQTTMessage(const char *topic, const char *payload) { return publishMQTTMessage_HAL(topic, payload); } diff --git a/Platformio/src/applicationInternal/hardware/hardwarePresenter.h b/Platformio/src/applicationInternal/hardware/hardwarePresenter.h index cd298dd..ac69907 100644 --- a/Platformio/src/applicationInternal/hardware/hardwarePresenter.h +++ b/Platformio/src/applicationInternal/hardware/hardwarePresenter.h @@ -108,6 +108,7 @@ void init_lvgl_hardware(); void init_mqtt(void); // used by "commandHandler.cpp", "sleep.cpp" bool getIsWifiConnected(); +void mqtt_loop(); bool publishMQTTMessage(const char *topic, const char *payload); void wifiStop(); #endif diff --git a/Platformio/src/devices/misc/device_specialCommands.cpp b/Platformio/src/devices/misc/device_specialCommands.cpp index a5efeeb..7bf976b 100644 --- a/Platformio/src/devices/misc/device_specialCommands.cpp +++ b/Platformio/src/devices/misc/device_specialCommands.cpp @@ -3,11 +3,15 @@ uint16_t COMMAND_UNKNOWN ; uint16_t MY_SPECIAL_COMMAND; //"My_special_command"; +// uint16_t TRIGGER_UPDATE_OF_OMOTE_SMART_HOME_DEVICES; void register_specialCommands() { get_uniqueCommandID(&COMMAND_UNKNOWN); + // command to trigger your home automation software to send the states of the smart home devices known to OMOTE + // register_command(&TRIGGER_UPDATE_OF_OMOTE_SMART_HOME_DEVICES, makeCommandData(MQTT, {"put_here_your_topic_to_trigger_update_of_omote_smart_home_devices", "PRESS"})); + // put SPECIAL commands here if you want - register_command(&MY_SPECIAL_COMMAND , makeCommandData(SPECIAL, {""})); + register_command(&MY_SPECIAL_COMMAND, makeCommandData(SPECIAL, {""})); } diff --git a/Platformio/src/devices/misc/device_specialCommands.h b/Platformio/src/devices/misc/device_specialCommands.h index 86afc33..d9df102 100644 --- a/Platformio/src/devices/misc/device_specialCommands.h +++ b/Platformio/src/devices/misc/device_specialCommands.h @@ -4,5 +4,6 @@ extern uint16_t COMMAND_UNKNOWN; extern uint16_t MY_SPECIAL_COMMAND; +// extern uint16_t TRIGGER_UPDATE_OF_OMOTE_SMART_HOME_DEVICES; void register_specialCommands(); diff --git a/Platformio/src/guis/gui_irReceiver.cpp b/Platformio/src/guis/gui_irReceiver.cpp index 89004c2..a1a61e7 100644 --- a/Platformio/src/guis/gui_irReceiver.cpp +++ b/Platformio/src/guis/gui_irReceiver.cpp @@ -16,6 +16,20 @@ int messagePos = 0; int messageCount = 0; bool tabIsInMemory = false; +lv_obj_t* objMQTTmessageReceivedTopic; +lv_obj_t* objMQTTmessageReceivedPayload; + +void showMQTTmessage(std::string topic, std::string payload) { + if (!tabIsInMemory) {return;} + + if (objMQTTmessageReceivedTopic != NULL) { + lv_label_set_text(objMQTTmessageReceivedTopic, topic.c_str()); + } + if (objMQTTmessageReceivedPayload != NULL) { + lv_label_set_text(objMQTTmessageReceivedPayload, payload.c_str()); + } +} + void printReceivedMessages(bool clearMessages = false) { if (!tabIsInMemory) {return;} @@ -44,7 +58,7 @@ void printReceivedMessages(bool clearMessages = false) { } } -void showNewIRmessage_cb(std::string message) { +void showNewIRmessage(std::string message) { setLastActivityTimestamp(); // Reset the sleep timer when a IR message is received // Serial.printf(" new IR message received: %s\r\n", message.c_str()); @@ -130,6 +144,23 @@ void create_tab_content_irReceiver(lv_obj_t* tab) { printReceivedMessages(true); } + // Show MQTT messages we subscribed to ------------------------------------------------------ + menuLabel = lv_label_create(tab); + lv_label_set_text(menuLabel, "MQTT messages arrived"); + lv_obj_t* menuBox = lv_obj_create(tab); + lv_obj_set_size(menuBox, lv_pct(100), 46); + lv_obj_set_style_bg_color(menuBox, color_primary, LV_PART_MAIN); + lv_obj_set_style_border_width(menuBox, 0, LV_PART_MAIN); + + objMQTTmessageReceivedTopic = lv_label_create(menuBox); + lv_label_set_text(objMQTTmessageReceivedTopic, ""); + lv_obj_set_style_text_font(objMQTTmessageReceivedTopic, &lv_font_montserrat_10, LV_PART_MAIN); + lv_obj_align(objMQTTmessageReceivedTopic, LV_ALIGN_TOP_LEFT, 0, -4); + objMQTTmessageReceivedPayload = lv_label_create(menuBox); + lv_label_set_text(objMQTTmessageReceivedPayload, ""); + lv_obj_set_style_text_font(objMQTTmessageReceivedPayload, &lv_font_montserrat_10, LV_PART_MAIN); + lv_obj_align(objMQTTmessageReceivedPayload, LV_ALIGN_TOP_LEFT, 0, 8); + } void notify_tab_before_delete_irReceiver(void) { diff --git a/Platformio/src/guis/gui_irReceiver.h b/Platformio/src/guis/gui_irReceiver.h index 23fb2ed..13ce124 100644 --- a/Platformio/src/guis/gui_irReceiver.h +++ b/Platformio/src/guis/gui_irReceiver.h @@ -6,4 +6,6 @@ const char * const tabName_irReceiver = "IR Receiver"; void register_gui_irReceiver(void); -void showNewIRmessage_cb(std::string); +// used by commandHandler to show WiFi status +void showNewIRmessage(std::string); +void showMQTTmessage(std::string topic, std::string payload); \ No newline at end of file diff --git a/Platformio/src/main.cpp b/Platformio/src/main.cpp index 288c066..28bf7ac 100644 --- a/Platformio/src/main.cpp +++ b/Platformio/src/main.cpp @@ -138,6 +138,10 @@ void loop(unsigned long *pIMUTaskTimer, unsigned long *pUpdateStatusTimer) { } // update LVGL UI gui_loop(); + // call mqtt loop to receive mqtt messages, if you are subscribed to some topics + #if (ENABLE_WIFI_AND_MQTT == 1) + mqtt_loop(); + #endif // --- every 100 ms ------------------------------------------------------------------- // Refresh IMU data (motion detection) every 100 ms @@ -146,6 +150,7 @@ void loop(unsigned long *pIMUTaskTimer, unsigned long *pUpdateStatusTimer) { *pIMUTaskTimer = millis(); check_activity(); + } // --- every 1000 ms ------------------------------------------------------------------