1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/sys/include/net/asymcute.h

579 lines
20 KiB
C
Raw Normal View History

/*
* Copyright (C) 2018 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_asymcute MQTT-SN Client (Asymcute)
* @ingroup net
* @brief Asymcute is an asynchronous MQTT-SN implementation
*
* # About
* `Asymcute` is a asynchronous MQTT-SN client implementation, aiming at
* providing the user a high degree of flexibility. It provides a flexible
* interface that allows users to issue any number of concurrent requests to
* one or more different gateways simultaneously.
*
* # Implementation state
*
* Implemented features:
* - Connecting to multiple gateways simultaneously
* - Registration of topic names
* - Publishing of data (QoS 0 and QoS 1)
* - Subscription to topics
* - Pre-defined topic IDs as well as short and normal topic names
*
* Missing features:
* - Gateway discovery process not implemented
* - Last will feature not implemented
* - No support for QoS level 2
* - No support for QoS level -1
* - No support for wildcard characters in topic names when subscribing
* - Actual granted QoS level on subscription is ignored
*
* @{
* @file
* @brief Asymcute MQTT-SN interface definition
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
*/
#ifndef NET_ASYMCUTE_H
#define NET_ASYMCUTE_H
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include "assert.h"
#include "event/timeout.h"
#include "event/callback.h"
#include "net/mqttsn.h"
#include "net/sock/udp.h"
#include "net/sock/util.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
2020-07-06 13:10:40 +02:00
* @defgroup net_asymcute_conf Asymcute (MQTT-SN Client) compile configurations
* @ingroup net_mqtt_conf
* @brief Compile-time configuration options for Asymcute, an asynchronous
* MQTT-SN implementation based on the OASIS MQTT-SN protocol. It
* provides a flexible interface that allows users to issue any number
* of concurrent requests to one or more different gateways
* simultaneously.
2020-07-06 13:10:40 +02:00
* @{
*/
/**
* @brief Default UDP port to listen on. Usage can be found in
* examples/asymcute_mqttsn. Application code is expected to use this
* macro to assign the default port.
*/
#ifndef CONFIG_ASYMCUTE_DEFAULT_PORT
#define CONFIG_ASYMCUTE_DEFAULT_PORT (1883U)
#endif
/**
* @brief Default buffer size used for receive and request buffers
*/
#ifndef CONFIG_ASYMCUTE_BUFSIZE
#define CONFIG_ASYMCUTE_BUFSIZE (128U)
#endif
/**
2020-07-06 13:10:40 +02:00
* @brief Maximum topic length
*
* @note Must be less than (256 - 8) AND less than ( @ref CONFIG_ASYMCUTE_BUFSIZE - 8).
*/
2020-07-06 13:10:40 +02:00
#ifndef CONFIG_ASYMCUTE_TOPIC_MAXLEN
#define CONFIG_ASYMCUTE_TOPIC_MAXLEN (32U)
#endif
/**
2020-07-06 13:10:40 +02:00
* @brief Keep alive interval [in s] communicated to the gateway
*
* Keep alive interval in seconds which is communicated to the gateway in the
2020-07-06 13:10:40 +02:00
* CONNECT message. For more information, see MQTT-SN Spec v1.2, section 5.4.4.
* For default values,see section 7.2 -> TWAIT: > 5 min.
*/
2020-07-06 13:10:40 +02:00
#ifndef CONFIG_ASYMCUTE_KEEPALIVE
#define CONFIG_ASYMCUTE_KEEPALIVE (360)
#endif
/**
2020-07-06 13:10:40 +02:00
* @brief Interval to use for sending periodic ping messages
*
* The default behavior of this implementation is to send ping messages as soon
* as three quarters of the keep alive interval have passed.
*
* @note Must be less than @ref CONFIG_ASYMCUTE_KEEPALIVE
*/
2020-07-06 13:10:40 +02:00
#ifndef CONFIG_ASYMCUTE_KEEPALIVE_PING
#define CONFIG_ASYMCUTE_KEEPALIVE_PING ((CONFIG_ASYMCUTE_KEEPALIVE / 4) * 3)
#endif
/**
2020-07-06 13:10:40 +02:00
* @brief Resend interval [in seconds]
*
2020-07-06 13:10:40 +02:00
* Interval used for timing the retry messages which are sent when the expected
* reply from GW is not received. The retry timer is started by the client when
* the message is sent and stopped when the expected reply from GW is received.
* If the timer times out and the expected GWs reply is not received, the
* client retransmits the message. For more information, see MQTT-SN Spec v1.2,
* section 6.13. For default values, see section 7.2 -> Tretry: 10 to 15 sec.
*/
2020-07-06 13:10:40 +02:00
#ifndef CONFIG_ASYMCUTE_T_RETRY
#define CONFIG_ASYMCUTE_T_RETRY (10U)
#endif
/**
2020-07-06 13:10:40 +02:00
* @brief Number of retransmissions until requests time out
*
* Maximum number of retransmissions in the event that the retry timer times
* out. After 'CONFIG_ASYMCUTE_N_RETRY' number of retransmissions, the client
* aborts the procedure and assumes that its MQTT-SN connection to the gateway
* is disconnected. For more information, see MQTT-SN Spec v1.2, section 6.13.
* For default values, see section 7.2 -> Nretry: 3-5.
*/
2020-07-06 13:10:40 +02:00
#ifndef CONFIG_ASYMCUTE_N_RETRY
#define CONFIG_ASYMCUTE_N_RETRY (3U)
#endif
2020-07-06 13:10:40 +02:00
/** @} */
2020-07-06 13:10:40 +02:00
#ifndef ASYMCUTE_HANDLER_PRIO
/**
2020-07-06 13:10:40 +02:00
* @brief Default priority for Asymcute's handler thread
*/
#define ASYMCUTE_HANDLER_PRIO (THREAD_PRIORITY_MAIN - 2)
#endif
2020-07-06 13:10:40 +02:00
#ifndef ASYMCUTE_HANDLER_STACKSIZE
/**
2020-07-06 13:10:40 +02:00
* @brief Default stack size for Asymcute's handler thread
*/
#define ASYMCUTE_HANDLER_STACKSIZE (THREAD_STACKSIZE_DEFAULT)
#endif
/**
* @brief Return values used by public Asymcute functions
*/
enum {
ASYMCUTE_OK = 0, /**< all is good */
ASYMCUTE_OVERFLOW = -1, /**< error: insufficient buffer space */
ASYMCUTE_GWERR = -2, /**< error: bad gateway connection state */
ASYMCUTE_NOTSUP = -3, /**< error: feature not supported */
ASYMCUTE_BUSY = -4, /**< error: context already in use */
ASYMCUTE_REGERR = -5, /**< error: registration invalid */
ASYMCUTE_SUBERR = -6, /**< error: subscription invalid */
ASYMCUTE_SENDERR = -7, /**< error: unable to sent packet */
};
/**
* @brief Possible event types passed to the event callback
*/
enum {
ASYMCUTE_TIMEOUT, /**< request timed out */
ASYMCUTE_CANCELED, /**< request was canceled */
ASYMCUTE_REJECTED, /**< request was rejected */
ASYMCUTE_CONNECTED, /**< connected to gateway */
ASYMCUTE_DISCONNECTED, /**< connection got disconnected */
ASYMCUTE_REGISTERED, /**< topic was registered */
ASYMCUTE_PUBLISHED, /**< data was published */
ASYMCUTE_SUBSCRIBED, /**< client was subscribed to topic */
ASYMCUTE_UNSUBSCRIBED, /**< client was unsubscribed from topic */
};
/**
* @brief Forward type declaration for connections contexts
*/
typedef struct asymcute_con asymcute_con_t;
/**
* @brief Forward type declaration for request contexts
*/
typedef struct asymcute_req asymcute_req_t;
/**
* @brief Forward type declaration for subscription contexts
*/
typedef struct asymcute_sub asymcute_sub_t;
/**
* @brief Forward type declaration for topic definitions
*/
typedef struct asymcute_topic asymcute_topic_t;
/**
* @brief Forward type declaration for last will definitions
*/
typedef struct asymcute_will asymcute_will_t;
/**
* @brief Event callback used for communicating connection and request related
* events to the user
*
* @param[in] req pointer to the request context that triggered the event,
* may be NULL of unsolicited events
* @param[in] evt_type type of the event
*/
typedef void(*asymcute_evt_cb_t)(asymcute_req_t *req, unsigned evt_type);
/**
* @brief Callback triggered on events for active subscriptions
*
* @param[in] sub pointer to subscription context triggering this event
* @param[in] evt_type type of the event
* @param[in] data incoming data for PUBLISHED events, may be NULL
* @param[in] len length of @p data in bytes
* @param[in] arg user supplied argument
*/
typedef void(*asymcute_sub_cb_t)(const asymcute_sub_t *sub, unsigned evt_type,
const void *data, size_t len, void *arg);
/**
* @brief Context specific timeout callback, only used internally
*
* @internal
*
* @param[in] con connection context for this timeout
* @param[in] req request that timed out
*
* @return Event type to communicate to the user
*/
typedef unsigned(*asymcute_to_cb_t)(asymcute_con_t *con, asymcute_req_t *req);
/**
* @brief Asymcute request context
*/
struct asymcute_req {
mutex_t lock; /**< synchronization lock */
struct asymcute_req *next; /**< the requests list entry */
asymcute_con_t *con; /**< connection the request is using */
asymcute_to_cb_t cb; /**< internally used callback */
void *arg; /**< internally used additional state */
event_callback_t to_evt; /**< timeout event */
event_timeout_t to_timer; /**< timeout timer */
uint8_t data[CONFIG_ASYMCUTE_BUFSIZE]; /**< buffer holding the request's data */
size_t data_len; /**< length of the request packet in byte */
uint16_t msg_id; /**< used message id for this request */
uint8_t retry_cnt; /**< retransmission counter */
};
/**
* @brief Asymcute connection context
*/
struct asymcute_con {
mutex_t lock; /**< synchronization lock */
sock_udp_t sock; /**< socket used by a connections */
asymcute_req_t *pending; /**< list holding pending requests */
asymcute_sub_t *subscriptions; /**< list holding active subscriptions */
asymcute_evt_cb_t user_cb; /**< event callback provided by user */
event_callback_t keepalive_evt; /**< keep alive event */
event_timeout_t keepalive_timer; /**< keep alive timer */
uint16_t last_id; /**< last used message ID for this
* connection */
uint8_t keepalive_retry_cnt; /**< keep alive transmission counter */
uint8_t state; /**< connection state */
uint8_t rxbuf[CONFIG_ASYMCUTE_BUFSIZE]; /**< connection specific receive buf */
char cli_id[MQTTSN_CLI_ID_MAXLEN + 1]; /**< buffer to store client ID */
};
/**
* @brief Data-structure for holding topics and their registration status
*/
struct asymcute_topic {
asymcute_con_t *con; /**< connection used for registration */
char name[CONFIG_ASYMCUTE_TOPIC_MAXLEN + 1]; /**< topic string (ACSII only) */
uint8_t flags; /**< normal, short, or pre-defined */
uint16_t id; /**< topic id */
};
/**
* @brief Data-structure holding the state of subscriptions
*/
struct asymcute_sub {
asymcute_sub_t *next; /**< the subscriptions list entry */
asymcute_topic_t *topic; /**< topic we subscribe to */
asymcute_sub_cb_t cb; /**< called on incoming data */
void *arg; /**< user supplied callback argument */
};
/**
* @brief Data structure for defining a last will
*/
struct asymcute_will {
const char *topic; /**< last will topic */
void *msg; /**< last will message content */
size_t msg_len; /**< length of last will message content */
};
/**
* @brief Check if a given request context is currently used
*
* @param[in] req request context to check
*
* @return true if context is currently used
* @return false if context is not used
*/
static inline bool asymcute_req_in_use(const asymcute_req_t *req)
{
assert(req);
return (req->con != NULL);
}
/**
* @brief Check if a given subscription is currently active
*
* @param[in] sub subscription context to check
*
* @return true if subscription is active
* @return false if subscription is not active
*/
static inline bool asymcute_sub_active(const asymcute_sub_t *sub)
{
assert(sub);
return (sub->topic != NULL);
}
/**
* @brief Reset the given topic
*
* @warning Make sure that the given topic is not used by any subscription or
* last will when calling this function
*
* @param[out] topic topic to reset
*/
static inline void asymcute_topic_reset(asymcute_topic_t *topic)
{
assert(topic);
memset(topic, 0, sizeof(asymcute_topic_t));
}
/**
* @brief Check if a given topic is currently registered with a gateway
*
* @param[in] topic topic to check
*
* @return true if topic is registered
* @return false if topic is not registered
*/
static inline bool asymcute_topic_is_reg(const asymcute_topic_t *topic)
{
assert(topic);
return (topic->con != NULL);
}
/**
* @brief Check if a given topic is a short topic
*
* @param[in] topic topic to check
*
* @return true if topic is a short topic
* @return false if topic is not short topic
*/
static inline bool asymcute_topic_is_short(const asymcute_topic_t *topic)
{
assert(topic);
return ((topic->flags & MQTTSN_TIT_SHORT) != 0);
}
/**
* @brief Check if a given topic is a pre-defined topic
*
* @param[in] topic topic to check
*
* @return true if topic is pre-defined
* @return false if topic is not pre-defined
*/
static inline bool asymcute_topic_is_predef(const asymcute_topic_t *topic)
{
assert(topic);
return ((topic->flags & MQTTSN_TIT_PREDEF) != 0);
}
/**
* @brief Check if a given topic is initialized
*
* @param[in] topic topic to check
*
* @return true if topic is initialized
* @return false if topic is not initialized
*/
static inline bool asymcute_topic_is_init(const asymcute_topic_t *topic)
{
assert(topic);
return (topic->name[0] != '\0');
}
/**
* @brief Compare two given topics and check if they are equal
*
* @param[in] a topic A
* @param[in] b topic B
*
* @return true if both topics are equal
* @return false if topics differ
*/
static inline bool asymcute_topic_equal(const asymcute_topic_t *a,
const asymcute_topic_t *b)
{
assert(a);
assert(b);
return ((a->flags == b->flags) && (a->id == b->id));
}
/**
* @brief Initialize the given topic
*
* @param[out] topic topic to initialize
* @param[in] topic_name topic name (ASCII), may be NULL if topic should use
* a pre-defined topic ID
* @param[in] topic_id pre-defined topic ID, or don't care if @p topic_name
* is given
*
* @return ASYMCUTE_OK on success
* @return ASYMCUTE_REGERR if topic is already registered
* @return ASYMCUTE_OVERFLOW if topic name does not fit into buffer or if pre-
* defined topic ID is invalid
*/
int asymcute_topic_init(asymcute_topic_t *topic, const char *topic_name,
uint16_t topic_id);
/**
* @brief Start the global Asymcute handler thread for processing timeouts and
* keep alive events
*
* This function is typically called during system initialization.
*/
void asymcute_handler_run(void);
/**
* @brief Check if the given connection context is connected to a gateway
*
* @param[in] con connection to check
*
* @return true if context is connected
* @return false if not connected
*/
bool asymcute_is_connected(const asymcute_con_t *con);
/**
* @brief Connect to the given MQTT-SN gateway
*
* @param[in,out] con connection to use
* @param[in,out] req request context to use for CONNECT procedure
* @param[in] server UDP endpoint of the target gateway
* @param[in] cli_id client ID to register with the gateway
* @param[in] clean set `true` to start a clean session
* @param[in] will last will (currently not implemented)
* @param[in] callback user callback triggered on defined events
*
* @return ASYMCUTE_OK if CONNECT message has been sent
* @return ASYMCUTE_NOTSUP if last will was given (temporary until implemented)
* @return ASYMCUTE_OVERFLOW if @p cli_id is larger than ASYMCUTE_ID_MAXLEN
* @return ASYMCUTE_GWERR if initializing the socket for the connection failed
* @return ASYMCUTE_BUSY if the connection or the request context are in use
*/
int asymcute_connect(asymcute_con_t *con, asymcute_req_t *req,
sock_udp_ep_t *server, const char *cli_id, bool clean,
asymcute_will_t *will, asymcute_evt_cb_t callback);
/**
* @brief Close the given connection
*
* @param[in,out] con connection to close
* @param[in,out] req request context to use for DISCONNECT procedure
*
* @return ASYMCUTE_OK if DISCONNECT message has been sent
* @return ASYMCUTE_GWERR if connection context is not connected
* @return ASYMCUTE_BUSY if the given request context is already in use
*/
int asymcute_disconnect(asymcute_con_t *con, asymcute_req_t *req);
/**
* @brief Register a given topic with the connected gateway
*
* @param[in] con connection to use
* @param[in,out] req request context to use for REGISTER procedure
* @param[in,out] topic topic to register
*
* @return ASYMCUTE_OK if REGISTER message has been sent
* @return ASYMCUTE_REGERR if topic is already registered
* @return ASYMCUTE_GWERR if not connected to a gateway
* @return ASYMCUTE_BUSY if the given request context is already in use
*/
int asymcute_register(asymcute_con_t *con, asymcute_req_t *req,
asymcute_topic_t *topic);
/**
* @brief Publish the given data to the given topic
*
* @param[in] con connection to use
* @param[in,out] req request context used for PUBLISH procedure
* @param[in] topic publish data to this topic
* @param[in] data actual payload to send
* @param[in] data_len size of @p data in bytes
* @param[in] flags additional flags (QoS level, DUP, and RETAIN)
*
* @return ASYMCUTE_OK if PUBLISH message has been sent
* @return ASYMCUTE_NOTSUP if unsupported flags have been set
* @return ASYMCUTE_OVERFLOW if data does not fit into transmit buffer
* @return ASYMCUTE_REGERR if given topic is not registered
* @return ASYMCUTE_GWERR if not connected to a gateway
* @return ASYMCUTE_BUSY if the given request context is already in use
*/
int asymcute_publish(asymcute_con_t *con, asymcute_req_t *req,
const asymcute_topic_t *topic,
const void *data, size_t data_len, uint8_t flags);
/**
* @brief Subscribe to a given topic
*
* @param[in] con connection to use
* @param[in,out] req request context used for SUBSCRIBE procedure
* @param[out] sub subscription context to store subscription state
* @param[in,out] topic topic to subscribe to, must be initialized (see
* asymcute_topic_init())
* @param[in] callback user callback triggered on events for this subscription
* @param[in] arg user supplied argument passed to the event callback
* @param[in] flags additional flags (QoS level and DUP)
*
* @return ASYMCUTE_OK if SUBSCRIBE message has been sent
* @return ASYMCUTE_NOTSUP if invalid or unsupported flags have been set
* @return ASYMCUTE_REGERR if topic is not initialized
* @return ASYMCUTE_GWERR if not connected to a gateway
* @return ASYMCUTE_SUBERR if already subscribed to the given topic
* @return ASYMCUTE_BUSY if the given request context is already in use
*/
int asymcute_subscribe(asymcute_con_t *con, asymcute_req_t *req,
asymcute_sub_t *sub, asymcute_topic_t *topic,
asymcute_sub_cb_t callback, void *arg, uint8_t flags);
/**
* @brief Cancel an active subscription
*
* @param[in] con connection to use
* @param[in,out] req request context used for UNSUBSCRIBE procedure
* @param[in,out] sub subscription to cancel
*
* @return ASYMCUTE_OK if UNSUBSCRIBE message has been sent
* @return ASYMCUTE_SUBERR if subscription is not currently active
* @return ASYMCUTE_GWERR if not connected to a gateway
* @return ASYMCUTE_BUSY if the given request context is already in use
*/
int asymcute_unsubscribe(asymcute_con_t *con, asymcute_req_t *req,
asymcute_sub_t *sub);
#ifdef __cplusplus
}
#endif
#endif /* NET_ASYMCUTE_H */
/** @} */