osd-contiki/apps/mqtt/mqtt.h

509 lines
17 KiB
C
Raw Permalink Normal View History

2015-02-17 13:44:29 +01:00
/*
* Copyright (c) 2015, Texas Instruments Incorporated - http://www.ti.com/
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*---------------------------------------------------------------------------*/
/**
* \addtogroup apps
* @{
*
* \defgroup mqtt-engine An implementation of MQTT v3.1
* @{
*
* This application is an engine for MQTT v3.1. It supports QoS Levels 0 and 1.
*
* MQTT is a Client Server publish/subscribe messaging transport protocol.
* It is light weight, open, simple, and designed so as to be easy to implement.
* These characteristics make it ideal for use in many situations, including
* constrained environments such as for communication in Machine to Machine
* (M2M) and Internet of Things (IoT) contexts where a small code footprint is
* required and/or network bandwidth is at a premium.
*
* The protocol runs over TCP/IP, more specifically tcp_socket.
* Its features include:
*
* - Use of the publish/subscribe message pattern which provides
* one-to-many message distribution and decoupling of applications.
* - A messaging transport that is agnostic to the content of the payload.
* Three qualities of service for message delivery:
* -- "At most once" (0), where messages are delivered according to the best
* efforts of the operating environment. Message loss can occur.
* This level could be used, for example, with ambient sensor data where it
* does not matter if an individual reading is lost as the next one will be
* published soon after.
* --"At least once" (1), where messages are assured to arrive but duplicates
* can occur.
* -- "Exactly once" (2), where message are assured to arrive exactly once.
* This level could be used, for example, with billing systems where duplicate
* or lost messages could lead to incorrect charges being applied. This QoS
* level is currently not supported in this implementation.
*
* - A small transport overhead and protocol exchanges minimized to reduce
* network traffic.
* - A mechanism, Last Will, to notify interested parties when an abnormal
* disconnection occurs.
*
* The protocol specification and other useful information can be found
* here: http://mqtt.org
*
*/
/**
* \file
* Header file for the Contiki MQTT engine
*
* \author
* Texas Instruments
*/
/*---------------------------------------------------------------------------*/
#ifndef MQTT_H_
#define MQTT_H_
/*---------------------------------------------------------------------------*/
#include "contiki.h"
#include "contiki-net.h"
#include "contiki-lib.h"
#include "lib/random.h"
#include "sys/ctimer.h"
#include "sys/etimer.h"
#include "net/rpl/rpl.h"
#include "net/ip/uip.h"
#include "net/ipv6/uip-ds6.h"
#include "dev/leds.h"
#include "tcp-socket.h"
#include "udp-socket.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/*---------------------------------------------------------------------------*/
/* Protocol constants */
#define MQTT_CLIENT_ID_MAX_LEN 23
/* Size of the underlying TCP buffers */
#define MQTT_TCP_INPUT_BUFF_SIZE 512
#define MQTT_TCP_OUTPUT_BUFF_SIZE 512
#define MQTT_INPUT_BUFF_SIZE 512
#define MQTT_MAX_TOPIC_LENGTH 64
#define MQTT_MAX_TOPICS_PER_SUBSCRIBE 1
#define MQTT_FHDR_SIZE 1
#define MQTT_MAX_REMAINING_LENGTH_BYTES 4
#define MQTT_PROTOCOL_VERSION 3
#define MQTT_PROTOCOL_NAME "MQIsdp"
#define MQTT_TOPIC_MAX_LENGTH 128
/*---------------------------------------------------------------------------*/
/*
* Debug configuration, this is similar but not exactly like the Debugging
* System discussion at https://github.com/contiki-os/contiki/wiki.
*/
#define DEBUG_MQTT 0
#if DEBUG_MQTT == 1
#define DBG(...) printf(__VA_ARGS__)
#else
#define DBG(...)
#endif /* DEBUG */
/*---------------------------------------------------------------------------*/
extern process_event_t mqtt_update_event;
/* Forward declaration */
struct mqtt_connection;
typedef enum {
MQTT_RETAIN_OFF,
MQTT_RETAIN_ON,
} mqtt_retain_t;
/**
* \brief MQTT engine events
*/
typedef enum {
MQTT_EVENT_CONNECTED,
MQTT_EVENT_DISCONNECTED,
MQTT_EVENT_SUBACK,
MQTT_EVENT_UNSUBACK,
MQTT_EVENT_PUBLISH,
MQTT_EVENT_PUBACK,
/* Errors */
MQTT_EVENT_ERROR = 0x80,
MQTT_EVENT_PROTOCOL_ERROR,
MQTT_EVENT_CONNECTION_REFUSED_ERROR,
MQTT_EVENT_DNS_ERROR,
MQTT_EVENT_NOT_IMPLEMENTED_ERROR,
/* Add more */
} mqtt_event_t;
typedef enum {
MQTT_STATUS_OK,
MQTT_STATUS_OUT_QUEUE_FULL,
/* Errors */
MQTT_STATUS_ERROR = 0x80,
MQTT_STATUS_NOT_CONNECTED_ERROR,
MQTT_STATUS_INVALID_ARGS_ERROR,
MQTT_STATUS_DNS_ERROR,
} mqtt_status_t;
typedef enum {
MQTT_QOS_LEVEL_0,
MQTT_QOS_LEVEL_1,
MQTT_QOS_LEVEL_2,
} mqtt_qos_level_t;
typedef enum {
MQTT_QOS_STATE_NO_ACK,
MQTT_QOS_STATE_GOT_ACK,
/* Expand for QoS 2 */
} mqtt_qos_state_t;
/*---------------------------------------------------------------------------*/
/*
* This is the state of the connection itself.
*
* N.B. The order is important because of runtime checks on how far the
* connection has proceeded.
*/
typedef enum {
MQTT_CONN_STATE_ERROR,
MQTT_CONN_STATE_DNS_ERROR,
MQTT_CONN_STATE_DISCONNECTING,
Several changes to fix bugs and harden mqtt code. 1. The PT_MQTT_WAIT_SEND() macro has several issues: - It does not check the return value from process_post(), which sometimes returns an error code. See next issue. - Each time the macro is called, is posts an event to itself. The idea seems to be that the event should be absorbed by the macro itself, so when the macro terminates there is NOT a net growth of the event queue. This does not work. The reason is that the PROCESS_WAIT_EVENT() sometimes absorbs a broadcast event instead of its own event, and then the number of events in the event queue increases. This leads to event explosions and overflow in the event queue. - The macro cannot call PT_EXIT(). This will expand to a return statement, causing a return from the function calling the macro (mqtt_process), rather then exiting the protothread (which was probably the intention). Protothreads have lexical scope... Fixes: 1) Check return value from process_post() 2) Loop until the event posted to itself is absorbed (rather than just absorbing the next event) 3) Replace PT_EXIT() with PT_INIT() (doesn't really make a difference, could probably just be removed). 2. Change order of the checks in the protothread-calling loops in mqtt_process(). Reason: When a protothread has been cleared by PT_MQTT_WAIT_SEND(), it will not return a value, so checking against PT_EXITED does not make sense. 3. PT_MQTT_WRITE_BYTES() should initialize conn->out_write_pos to 0. When PT_MQTT_WRITE_BYTES() does not finish (due to TCP disconnect for instance), it may leave conn->out_write_pos with a non-zero value. Next time PT_MQTT_WRITE_BYTES() is called, it will take data from the wrong place. 4. Put MQTT_CONN_STATE_ABORT_IMMEDIATE before MQTT_CONN_STATE_NOT_CONNECTED in the enum list, so that the check if(conn->state > MQTT_CONN_STATE_NOT_CONNECTED) in mqtt_connect() fails when in state MQTT_CONN_STATE_ABORT_IMMEDIATE. Otherwise, it will deadlock and not reattempt connections while in this state.
2017-02-23 22:03:28 +01:00
MQTT_CONN_STATE_ABORT_IMMEDIATE,
2015-02-17 13:44:29 +01:00
MQTT_CONN_STATE_NOT_CONNECTED,
MQTT_CONN_STATE_DNS_LOOKUP,
MQTT_CONN_STATE_TCP_CONNECTING,
MQTT_CONN_STATE_TCP_CONNECTED,
MQTT_CONN_STATE_CONNECTING_TO_BROKER,
MQTT_CONN_STATE_CONNECTED_TO_BROKER,
MQTT_CONN_STATE_SENDING_MQTT_DISCONNECT,
} mqtt_conn_state_t;
/*---------------------------------------------------------------------------*/
struct mqtt_string {
char *string;
uint16_t length;
};
/*
* Note that the pairing mid <-> QoS level only applies one-to-one if we only
* allow the subscription of one topic at a time. Otherwise we will have an
* ordered list of QoS levels corresponding to the order of topics.
*
* This could be part of a union of event data structures.
*/
struct mqtt_suback_event {
uint16_t mid;
mqtt_qos_level_t qos_level;
};
/* This is the MQTT message that is exposed to the end user. */
struct mqtt_message {
uint32_t mid;
char topic[MQTT_MAX_TOPIC_LENGTH + 1]; /* +1 for string termination */
uint8_t *payload_chunk;
uint16_t payload_chunk_length;
uint8_t first_chunk;
uint16_t payload_length;
uint16_t payload_left;
};
/* This struct represents a packet received from the MQTT server. */
struct mqtt_in_packet {
/* Used by the list interface, must be first in the struct. */
struct mqtt_connection *next;
/* Total bytes read so far. Compared to the remaining length to to decide when
* we've read the payload. */
uint32_t byte_counter;
uint8_t packet_received;
uint8_t fhdr;
uint16_t remaining_length;
uint16_t mid;
/* Helper variables needed to decode the remaining_length */
uint8_t remaining_multiplier;
uint8_t has_remaining_length;
uint8_t remaining_length_bytes;
/* Not the same as payload in the MQTT sense, it also contains the variable
* header.
*/
uint8_t payload_pos;
uint8_t payload[MQTT_INPUT_BUFF_SIZE];
/* Message specific data */
uint16_t topic_len;
uint16_t topic_pos;
uint8_t topic_len_received;
uint8_t topic_received;
};
/* This struct represents a packet sent to the MQTT server. */
struct mqtt_out_packet {
uint8_t fhdr;
uint32_t remaining_length;
uint8_t remaining_length_enc[MQTT_MAX_REMAINING_LENGTH_BYTES];
uint8_t remaining_length_enc_bytes;
uint16_t mid;
char *topic;
uint16_t topic_length;
uint8_t *payload;
uint32_t payload_size;
mqtt_qos_level_t qos;
mqtt_qos_state_t qos_state;
mqtt_retain_t retain;
};
/*---------------------------------------------------------------------------*/
/**
* \brief MQTT event callback function
* \param m A pointer to a MQTT connection
* \param event The event number
* \param data A user-defined pointer
*
* The MQTT socket event callback function gets called whenever there is an
* event on a MQTT connection, such as the connection getting connected
* or closed.
*/
typedef void (*mqtt_event_callback_t)(struct mqtt_connection *m,
mqtt_event_t event,
void *data);
typedef void (*mqtt_topic_callback_t)(struct mqtt_connection *m,
struct mqtt_message *msg);
/*---------------------------------------------------------------------------*/
struct mqtt_will {
struct mqtt_string topic;
struct mqtt_string message;
mqtt_qos_level_t qos;
};
struct mqtt_credentials {
struct mqtt_string username;
struct mqtt_string password;
};
struct mqtt_connection {
/* Used by the list interface, must be first in the struct */
struct mqtt_connection *next;
struct timer t;
struct mqtt_string client_id;
uint8_t connect_vhdr_flags;
uint8_t auto_reconnect;
uint16_t keep_alive;
struct ctimer keep_alive_timer;
uint8_t waiting_for_pingresp;
struct mqtt_will will;
struct mqtt_credentials credentials;
mqtt_conn_state_t state;
mqtt_event_callback_t event_callback;
/* Internal data */
uint16_t mid_counter;
/* Used for communication between MQTT API and APP */
uint8_t out_queue_full;
struct process *app_process;
/* Outgoing data related */
uint8_t *out_buffer_ptr;
uint8_t out_buffer[MQTT_TCP_OUTPUT_BUFF_SIZE];
uint8_t out_buffer_sent;
struct mqtt_out_packet out_packet;
struct pt out_proto_thread;
uint32_t out_write_pos;
uint16_t max_segment_size;
/* Incoming data related */
uint8_t in_buffer[MQTT_TCP_INPUT_BUFF_SIZE];
struct mqtt_in_packet in_packet;
struct mqtt_message in_publish_msg;
/* TCP related information */
char *server_host;
uip_ipaddr_t server_ip;
uint16_t server_port;
struct tcp_socket socket;
};
/* This is the API exposed to the user. */
/*---------------------------------------------------------------------------*/
/**
* \brief Initializes the MQTT engine.
* \param conn A pointer to the MQTT connection.
* \param app_process A pointer to the application process handling the MQTT
* connection.
* \param client_id A pointer to the MQTT client ID.
* \param event_callback Callback function responsible for handling the
* callback from MQTT engine.
* \param max_segment_size The TCP segment size to use for this MQTT/TCP
* connection.
* \return MQTT_STATUS_OK or MQTT_STATUS_INVALID_ARGS_ERROR
*
* This function initializes the MQTT engine and shall be called before any
* other MQTT function.
*/
mqtt_status_t mqtt_register(struct mqtt_connection *conn,
struct process *app_process,
char *client_id,
mqtt_event_callback_t event_callback,
uint16_t max_segment_size);
/*---------------------------------------------------------------------------*/
/**
* \brief Connects to a MQTT broker.
* \param conn A pointer to the MQTT connection.
* \param host IP address of the broker to connect to.
* \param port Port of the broker to connect to, default is MQTT port is 1883.
* \param keep_alive Keep alive timer in seconds. Used by broker to handle
* client disc. Defines the maximum time interval between two messages
* from the client. Shall be min 1.5 x report interval.
* \return MQTT_STATUS_OK or an error status
*
* This function connects to a MQTT broker.
*/
mqtt_status_t mqtt_connect(struct mqtt_connection *conn,
char *host,
uint16_t port,
uint16_t keep_alive);
/*---------------------------------------------------------------------------*/
/**
* \brief Disconnects from a MQTT broker.
* \param conn A pointer to the MQTT connection.
*
* This function disconnects from a MQTT broker.
*/
void mqtt_disconnect(struct mqtt_connection *conn);
/*---------------------------------------------------------------------------*/
/**
* \brief Subscribes to a MQTT topic.
* \param conn A pointer to the MQTT connection.
* \param mid A pointer to message ID.
* \param topic A pointer to the topic to subscribe to.
* \param qos_level Quality Of Service level to use. Currently supports 0, 1.
* \return MQTT_STATUS_OK or some error status
*
* This function subscribes to a topic on a MQTT broker.
*/
mqtt_status_t mqtt_subscribe(struct mqtt_connection *conn,
uint16_t *mid,
char *topic,
mqtt_qos_level_t qos_level);
/*---------------------------------------------------------------------------*/
/**
* \brief Unsubscribes from a MQTT topic.
* \param conn A pointer to the MQTT connection.
* \param mid A pointer to message ID.
* \param topic A pointer to the topic to unsubscribe from.
* \return MQTT_STATUS_OK or some error status
*
* This function unsubscribes from a topic on a MQTT broker.
*/
mqtt_status_t mqtt_unsubscribe(struct mqtt_connection *conn,
uint16_t *mid,
char *topic);
/*---------------------------------------------------------------------------*/
/**
* \brief Publish to a MQTT topic.
* \param conn A pointer to the MQTT connection.
* \param mid A pointer to message ID.
* \param topic A pointer to the topic to subscribe to.
* \param payload A pointer to the topic payload.
* \param payload_size Payload size.
* \param qos_level Quality Of Service level to use. Currently supports 0, 1.
* \param retain If the RETAIN flag is set to 1, in a PUBLISH Packet sent by a
* Client to a Server, the Server MUST store the Application Message
* and its QoS, so that it can be delivered to future subscribers whose
* subscriptions match its topic name
* \return MQTT_STATUS_OK or some error status
*
* This function publishes to a topic on a MQTT broker.
*/
mqtt_status_t mqtt_publish(struct mqtt_connection *conn,
uint16_t *mid,
char *topic,
uint8_t *payload,
uint32_t payload_size,
mqtt_qos_level_t qos_level,
mqtt_retain_t retain);
/*---------------------------------------------------------------------------*/
/**
* \brief Set the user name and password for a MQTT client.
* \param conn A pointer to the MQTT connection.
* \param username A pointer to the user name.
* \param password A pointer to the password.
*
* This function sets clients user name and password to use when connecting to
* a MQTT broker.
*/
void mqtt_set_username_password(struct mqtt_connection *conn,
char *username,
char *password);
/*---------------------------------------------------------------------------*/
/**
* \brief Set the last will topic and message for a MQTT client.
* \param conn A pointer to the MQTT connection.
* \param topic A pointer to the Last Will topic.
* \param message A pointer to the Last Will message (payload).
* \param qos The desired QoS level.
*
* This function sets clients Last Will topic and message (payload).
* If the Will Flag is set to 1 (using the function) this indicates that,
* if the Connect request is accepted, a Will Message MUST be stored on the
* Server and associated with the Network Connection. The Will Message MUST
* be published when the Network Connection is subsequently closed.
*
* This functionality can be used to get notified that a device has
* disconnected from the broker.
*
*/
void mqtt_set_last_will(struct mqtt_connection *conn,
char *topic,
char *message,
mqtt_qos_level_t qos);
#define mqtt_connected(conn) \
((conn)->state == MQTT_CONN_STATE_CONNECTED_TO_BROKER ? 1 : 0)
#define mqtt_ready(conn) \
(!(conn)->out_queue_full && mqtt_connected((conn)))
/*---------------------------------------------------------------------------*/
#endif /* MQTT_H_ */
/*---------------------------------------------------------------------------*/
/**
* @}
* @}
*/