/* * Copyright (C) 2017 Freie Universität Berlin * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level * directory for more details. */ /** * @defgroup net_emcute MQTT-SN Client (emCute) * @ingroup net * @brief emCute, the MQTT-SN implementation for RIOT * * # About * emCute is the implementation of the OASIS MQTT-SN protocol for RIOT. It is * designed with a focus on small memory footprint and usability. * * * # Design Decisions and Restrictions * * emCute is designed to run on top of UDP only, making use of * @ref net_sock_udp. The design is not intended to be used with any other * transport. * * The implementation is based on a 2-thread model: emCute needs one thread of * its own, in which receiving of packets and sending of ping messages are * handled. All 'user space functions' have to run from (a) different (i.e. * user) thread(s). emCute uses thread flags to synchronize between threads. * * Further know restrictions are: * - ASCII topic names only (no support for UTF8 names, yet) * - topic length is restricted to fit in a single length byte (248 byte max) * - no support for wildcards in topic names. This feature requires more * elaborate internal memory management, supposedly at the cost of quite * increased ROM and RAM usage * - no retransmit when receiving a REJ_CONG (reject, reason congestion). when * getting a REJ_CONG (reject, reason congestion), the spec tells us to resend * the original message after T_WAIT (default: >5min). This is not supported, * as this would require to block to calling thread (or keep state) for long * periods of time and is (in Hauke's opinion) not feasible for constrained * nodes. * * * # Error Handling * This implementation tries minimize parameter checks to a minimum, checking as * many parameters as feasible using assertions. For the sake of run-time * stability and usability, typical overflow checks are always done during run- * time and explicit error values returned in case of errors. * * * # Implementation state * In the current state, emCute supports: * - connecting to a gateway * - disconnecting from gateway * - registering a last will topic and message during connection setup * - registering topic names with the gateway (obtaining topic IDs) * - subscribing to topics * - unsubscribing from topics * - updating will topic * - updating will message * - sending out periodic PINGREQ messages * - handling re-transmits * * The following features are however still missing (but planned): * @todo Gateway discovery (so far there is no support for handling * ADVERTISE, GWINFO, and SEARCHGW). Open question to answer here: * how to put / how to encode the IPv(4/6) address AND the port of * a gateway in the GwAdd field of the GWINFO message * @todo QOS level 2 * @todo put the node to sleep (send DISCONNECT with duration field set) * @todo handle DISCONNECT messages initiated by the broker/gateway * @todo support for pre-defined and short topic IDs * @todo handle (previously) active subscriptions on reconnect/disconnect * @todo handle re-connect/disconnect from unresponsive gateway (in case * a number of ping requests are unanswered) * @todo react only to incoming ping requests that are actually send by * the gateway we are connected to * * @{ * @file * @brief emCute MQTT-SN interface definition * * @author Hauke Petersen */ #ifndef NET_EMCUTE_H #define NET_EMCUTE_H #include #include #include #include "net/sock/udp.h" #ifdef __cplusplus extern "C" { #endif #ifndef EMCUTE_DEFAULT_PORT /** * @brief Default UDP port to listen on (also used as SRC port) */ #define EMCUTE_DEFAULT_PORT (1883U) #endif #ifndef EMCUTE_BUFSIZE /** * @brief Buffer size used for emCute's transmit and receive buffers * * @note The buffer size MUST be less than 32768 on 16-bit and 8-bit * platforms to prevent buffer overflows. * * The overall buffer size used by emCute is this value time two (Rx + Tx). */ #define EMCUTE_BUFSIZE (512U) #endif #ifndef EMCUTE_ID_MAXLEN /** * @brief Maximum client ID length * * @note **Must** be less than (256 - 6) AND less than * (@ref EMCUTE_BUFSIZE - 6). */ #define EMCUTE_ID_MAXLEN (196U) #endif #ifndef EMCUTE_TOPIC_MAXLEN /** * @brief Maximum topic length * * @note **Must** be less than (256 - 6) AND less than * (@ref EMCUTE_BUFSIZE - 6). */ #define EMCUTE_TOPIC_MAXLEN (196U) #endif #ifndef EMCUTE_KEEPALIVE /** * @brief Keep-alive interval [in s] * * The node will communicate this interval to the gateway send a ping message * every time when this amount of time has passed. * * For the default value, see spec v1.2, section 7.2 -> T_WAIT: > 5 min */ #define EMCUTE_KEEPALIVE (360) /* -> 6 min*/ #endif #ifndef EMCUTE_T_RETRY /** * @brief Re-send interval [in seconds] * * For the default value, see spec v1.2, section 7.2 -> T_RETRY: 10 to 15 sec */ #define EMCUTE_T_RETRY (15U) /* -> 15 sec */ #endif #ifndef EMCUTE_N_RETRY /** * @brief Number of retries when sending packets * * For the default value, see spec v1.2, section 7.2 -> N_RETRY: 3-5 */ #define EMCUTE_N_RETRY (3U) #endif /** * @brief MQTT-SN flags * * All MQTT-SN functions only support a sub-set of the available flags. It is up * to the user to only supply valid/supported flags to a function. emCute will * trigger assertion fails on the use of unsupported flags (if compiled with * DEVELHELP). * * Refer to the MQTT-SN spec section 5.3.4 for further information. */ enum { EMCUTE_DUP = 0x80, /**< duplicate flag */ EMCUTE_QOS_MASK = 0x60, /**< QoS level mask */ EMCUTE_QOS_2 = 0x40, /**< QoS level 2 */ EMCUTE_QOS_1 = 0x20, /**< QoS level 1 */ EMCUTE_QOS_0 = 0x00, /**< QoS level 0 */ EMCUTE_RETAIN = 0x10, /**< retain flag */ EMCUTE_WILL = 0x08, /**< will flag, used during CONNECT */ EMCUTE_CS = 0x04, /**< clean session flag */ EMCUTE_TIT_MASK = 0x03, /**< topic ID type mask */ EMCUTE_TIT_SHORT = 0x02, /**< topic ID: short */ EMCUTE_TIT_PREDEF = 0x01, /**< topic ID: pre-defined */ EMCUTE_TIT_NORMAL = 0x00 /**< topic ID: normal */ }; /** * @brief Possible emCute return values */ enum { EMCUTE_OK = 0, /**< everything went as expect */ EMCUTE_NOGW = -1, /**< error: not connected to a gateway */ EMCUTE_REJECT = -2, /**< error: operation was rejected by broker */ EMCUTE_OVERFLOW = -3, /**< error: ran out of buffer space */ EMCUTE_TIMEOUT = -4, /**< error: timeout */ EMCUTE_NOTSUP = -5 /**< error: feature not supported */ }; /** * @brief MQTT-SN topic */ typedef struct { const char *name; /**< topic string (currently ACSII only) */ uint16_t id; /**< topic id, as assigned by the gateway */ } emcute_topic_t; /** * @brief Signature for callbacks fired when publish messages are received * * @param[in] topic topic the received data was published on * @param[in] data published data, can be NULL * @param[in] len length of @p data in bytes */ typedef void(*emcute_cb_t)(const emcute_topic_t *topic, void *data, size_t len); /** * @brief Data-structure for keeping track of topics we register to */ typedef struct emcute_sub { struct emcute_sub *next; /**< next subscription (saved in a list) */ emcute_topic_t topic; /**< topic we subscribe to */ emcute_cb_t cb; /**< function called when receiving messages */ void *arg; /**< optional custom argument */ } emcute_sub_t; /** * @brief Connect to a given MQTT-SN gateway (CONNECT) * * When called while already connected to a gateway, call emcute_discon() first * to disconnect from the current gateway. * * @param[in] remote address and port of the target MQTT-SN gateway * @param[in] clean set to true to start a clean session * @param[in] will_topic last will topic name, no last will will be * configured if set to NULL * @param[in] will_msg last will message content, will be ignored if * @p will_topic is set to NULL * @param[in] will_msg_len length of @p will_msg in byte * @param[in] flags flags used for the last will, allowed are retain and * QoS * * @return EMCUTE_OK on success * @return EMCUTE_NOGW if already connected to a gateway * @return EMCUTE_REJECT on connection refused by gateway * @return EMCUTE_TIMEOUT on connection timeout */ int emcute_con(sock_udp_ep_t *remote, bool clean, const char *will_topic, const void *will_msg, size_t will_msg_len, unsigned flags); /** * @brief Disconnect from the gateway we are currently connected to * * @return EMCUTE_OK on success * @return EMCUTE_GW if not connected to a gateway * @return EMCUTE_TIMEOUT on response timeout */ int emcute_discon(void); /** * @brief Get a topic ID for the given topic name from the gateway * * @param[in,out] topic topic to register, topic.name **must not** be NULL * * @return EMCUTE_OK on success * @return EMCUTE_NOGW if not connected to a gateway * @return EMCUTE_OVERFLOW if length of topic name exceeds * @ref EMCUTE_TOPIC_MAXLEN * @return EMCUTE_TIMEOUT on connection timeout */ int emcute_reg(emcute_topic_t *topic); /** * @brief Publish data on the given topic * * @param[in] topic topic to send data to, topic **must** be registered * (topic.id **must** populated). * @param[in] buf data to publish * @param[in] len length of @p data in bytes * @param[in] flags flags used for publication, allowed are QoS and retain * * @return EMCUTE_OK on success * @return EMCUTE_NOGW if not connected to a gateway * @return EMCUTE_REJECT if publish message was rejected (QoS > 0 only) * @return EMCUTE_OVERFLOW if length of data exceeds @ref EMCUTE_BUFSIZE * @return EMCUTE_TIMEOUT on connection timeout (QoS > 0 only) * @return EMCUTE_NOTSUP on unsupported flag values */ int emcute_pub(emcute_topic_t *topic, const void *buf, size_t len, unsigned flags); /** * @brief Subscribe to the given topic * * When calling this function, @p sub->topic.name and @p sub->cb **must** be * set. * * @param[in,out] sub subscription context, @p sub->topic.name and @p sub->cb * **must** not be NULL. * @param[in] flags flags used when subscribing, allowed are QoS, DUP, and * topic ID type * * @return EMCUTE_OK on success * @return EMCUTE_NOGW if not connected to a gateway * @return EMCUTE_OVERFLOW if length of topic name exceeds * @ref EMCUTE_TOPIC_MAXLEN * @return EMCUTE_TIMEOUT on connection timeout */ int emcute_sub(emcute_sub_t *sub, unsigned flags); /** * @brief Unsubscripbe the given topic * * @param[in] sub subscription context * * @return EMCUTE_OK on success * @return EMCUTE_NOGW if not connected to a gateway * @return EMCUTE_TIMEOUT on connection timeout */ int emcute_unsub(emcute_sub_t *sub); /** * @brief Update the last will topic * * @param[in] topic new last will topic * @param[in] flags flags used for the topic, allowed are QoS and retain * * @return EMCUTE_OK on success * @return EMCUTE_NOGW if not connected to a gateway * @return EMCUTE_OVERFLOW if length of topic name exceeds * @ref EMCUTE_TOPIC_MAXLEN * @return EMCUTE_REJECT on rejection by the gateway * @return EMCUTE_TIMEOUT on response timeout */ int emcute_willupd_topic(const char *topic, unsigned flags); /** * @brief Update the last will message * * @param[in] data new message to send on last will * @param[in] len length of @p data in bytes * * @return EMCUTE_OK on success * @return EMCUTE_NOGW if not connected to a gateway * @return EMCUTE_OVERFLOW if length of the given message exceeds * @ref EMCUTE_BUFSIZE * @return EMCUTE_REJECT on rejection by the gateway * @return EMCUTE_TIMEOUT on response timeout */ int emcute_willupd_msg(const void *data, size_t len); /** * @brief Run emCute, will 'occupy' the calling thread * * This function will run the emCute message receiver. It will block the thread * it is running in. * * @param[in] port UDP port used for listening (default: 1883) * @param[in] id client ID (should be unique) */ void emcute_run(uint16_t port, const char *id); /** * @brief Return the string representation of the given type value * * This function is for debugging purposes. * * @param[in] type MQTT-SN message type * * @return string representation of the given type * @return 'UNKNOWN' on invalid type value */ const char *emcute_type_str(uint8_t type); #ifdef __cplusplus } #endif #endif /* NET_EMCUTE_H */ /** @} */