From 373fdca6a22f846a1cbcad136ca7ad1906687d21 Mon Sep 17 00:00:00 2001 From: Martin Ger Date: Tue, 14 Nov 2017 18:54:49 +0100 Subject: [PATCH] moved uMQTTBroker lib to a separate repository --- .gitmodules | 3 + Arduino/mqtt_server.h | 40 -- Arduino/uMQTTBrokerSample.ino | 91 --- Makefile | 5 +- Makefile.orig | 4 +- README.md | 19 +- firmware/0x00000.bin | Bin 44608 -> 44608 bytes firmware/0x10000.bin | Bin 300236 -> 300284 bytes firmware/libmqtt.a | Bin 292160 -> 0 bytes firmware/sha1sums | 4 +- mqtt/Makefile | 45 -- mqtt/include/debug.h | 18 - mqtt/include/mqtt.h | 149 ----- mqtt/include/mqtt_msg.h | 194 ------- mqtt/include/mqtt_retainedlist.h | 28 - mqtt/include/mqtt_server.h | 57 -- mqtt/include/mqtt_topiclist.h | 21 - mqtt/include/mqtt_topics.h | 32 -- mqtt/include/proto.h | 32 -- mqtt/include/queue.h | 44 -- mqtt/include/ringbuf_mqtt.h | 19 - mqtt/include/typedef.h | 17 - mqtt/include/utils.h | 9 - mqtt/mqtt.c | 943 ------------------------------ mqtt/mqtt_msg.c | 475 ---------------- mqtt/mqtt_retainedlist.c | 191 ------- mqtt/mqtt_server.c | 946 ------------------------------- mqtt/mqtt_topiclist.c | 92 --- mqtt/mqtt_topics.c | 280 --------- mqtt/proto.c | 132 ----- mqtt/queue.c | 53 -- mqtt/ringbuf_mqtt.c | 64 --- mqtt/utils.c | 144 ----- uMQTTBroker | 1 + user/lang.c | 4 +- user/lang.h | 2 +- user/user_main.c | 6 +- user_basic/user_main.c | 2 +- 38 files changed, 19 insertions(+), 4147 deletions(-) create mode 100644 .gitmodules delete mode 100644 Arduino/mqtt_server.h delete mode 100644 Arduino/uMQTTBrokerSample.ino delete mode 100644 firmware/libmqtt.a delete mode 100644 mqtt/Makefile delete mode 100644 mqtt/include/debug.h delete mode 100644 mqtt/include/mqtt.h delete mode 100644 mqtt/include/mqtt_msg.h delete mode 100644 mqtt/include/mqtt_retainedlist.h delete mode 100644 mqtt/include/mqtt_server.h delete mode 100644 mqtt/include/mqtt_topiclist.h delete mode 100644 mqtt/include/mqtt_topics.h delete mode 100644 mqtt/include/proto.h delete mode 100644 mqtt/include/queue.h delete mode 100644 mqtt/include/ringbuf_mqtt.h delete mode 100644 mqtt/include/typedef.h delete mode 100644 mqtt/include/utils.h delete mode 100644 mqtt/mqtt.c delete mode 100644 mqtt/mqtt_msg.c delete mode 100644 mqtt/mqtt_retainedlist.c delete mode 100644 mqtt/mqtt_server.c delete mode 100644 mqtt/mqtt_topiclist.c delete mode 100644 mqtt/mqtt_topics.c delete mode 100644 mqtt/proto.c delete mode 100644 mqtt/queue.c delete mode 100644 mqtt/ringbuf_mqtt.c delete mode 100644 mqtt/utils.c create mode 160000 uMQTTBroker diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..75f8d2b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "uMQTTBroker"] + path = uMQTTBroker + url = https://github.com/martin-ger/uMQTTBroker diff --git a/Arduino/mqtt_server.h b/Arduino/mqtt_server.h deleted file mode 100644 index e77a5fb..0000000 --- a/Arduino/mqtt_server.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef _MQTT_SERVER_H_ -#define _MQTT_SERVER_H_ - -#include "user_interface.h" -extern "C" { - -// Interface for starting the broker - -bool MQTT_server_start(uint16_t portno, uint16_t max_subscriptions, uint16_t max_retained_topics); - -// Callbacks for message reception, username/password authentication, and client connection - -typedef void (*MqttDataCallback)(uint32_t *args, const char* topic, uint32_t topic_len, const char *data, uint32_t lengh); -typedef bool (*MqttAuthCallback)(const char* username, const char *password, struct espconn *pesp_conn); -typedef bool (*MqttConnectCallback)(struct espconn *pesp_conn, uint16_t client_count); - -void MQTT_server_onData(MqttDataCallback dataCb); -void MQTT_server_onAuth(MqttAuthCallback authCb); -void MQTT_server_onConnect(MqttConnectCallback connectCb); - -// Interface for local pub/sub interaction with the broker - -bool MQTT_local_publish(uint8_t* topic, uint8_t* data, uint16_t data_length, uint8_t qos, uint8_t retain); -bool MQTT_local_subscribe(uint8_t* topic, uint8_t qos); -bool MQTT_local_unsubscribe(uint8_t* topic); - -// Interface to cleanup after STA disconnect - -void MQTT_server_cleanupClientCons(); - -// Interface for persistence of retained topics -// Topics can be serialized to a buffer and reinitialized later after reboot -// Application is responsible for saving and restoring that buffer (i.e. to/from flash) - -void clear_retainedtopics(); -int serialize_retainedtopics(char *buf, int len); -bool deserialize_retainedtopics(char *buf, int len); -} - -#endif /* _MQTT_SERVER_H_ */ diff --git a/Arduino/uMQTTBrokerSample.ino b/Arduino/uMQTTBrokerSample.ino deleted file mode 100644 index ce30c7b..0000000 --- a/Arduino/uMQTTBrokerSample.ino +++ /dev/null @@ -1,91 +0,0 @@ -/* - * esp_uMQTT_broker demo for Arduino - * - * The program starts a broker, subscribes to anything and publishs a topic every second. - * Try to connect from a remote client and publish something - the console will show this as well. - */ - -#include - -#include "mqtt_server.h" - -/* - * Your WiFi config here - */ -char ssid[] = "MySSID"; // your network SSID (name) -char pass[] = "MyPassword"; // your network password - - -unsigned int mqttPort = 1883; // the standard MQTT broker port -unsigned int max_subscriptions = 30; -unsigned int max_retained_topics = 30; - -void data_callback(uint32_t *client /* we can ignore this */, const char* topic, uint32_t topic_len, const char *data, uint32_t lengh) { - char topic_str[topic_len+1]; - os_memcpy(topic_str, topic, topic_len); - topic_str[topic_len] = '\0'; - - char data_str[lengh+1]; - os_memcpy(data_str, data, lengh); - data_str[lengh] = '\0'; - - Serial.print("received topic '"); - Serial.print(topic_str); - Serial.print("' with data '"); - Serial.print(data_str); - Serial.println("'"); -} - -void setup() -{ - Serial.begin(115200); - Serial.println(); - Serial.println(); - - // We start by connecting to a WiFi network - Serial.print("Connecting to "); - Serial.println(ssid); - WiFi.begin(ssid, pass); - - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - Serial.println(""); - - Serial.println("WiFi connected"); - Serial.println("IP address: "); - Serial.println(WiFi.localIP()); - -/* - * Register the callback - */ - MQTT_server_onData(data_callback); - -/* - * Start the broker - */ - Serial.println("Starting MQTT broker"); - MQTT_server_start(mqttPort, max_subscriptions, max_retained_topics); - -/* - * Subscribe to anything - */ - MQTT_local_subscribe((unsigned char *)"#", 0); -} - -int counter = 0; - -void loop() -{ - String myData(counter++); - -/* - * Publish the counter value as String - */ - MQTT_local_publish((unsigned char *)"/MyBroker/count", (unsigned char *)myData.c_str(), myData.length(), 0, 0); - - // wait a second - delay(1000); -} - diff --git a/Makefile b/Makefile index 62bbde2..bd9968c 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ # - 2014-11-23: Updated for SDK 0.9.3 # - 2014-12-25: Replaced esptool by esptool.py -BUILD_AREA = /home/martin/github +BUILD_AREA = /home/mfg/github # Output directors to store intermediate compiled files # relative to the project directory @@ -33,7 +33,7 @@ ESPPORT ?= /dev/ttyUSB0 TARGET = app # which modules (subdirectories) of the project to include in compiling -MODULES = driver user mqtt ntp easygpio pwm httpclient adc +MODULES = driver user uMQTTBroker/src ntp easygpio pwm httpclient adc #EXTRA_INCDIR = $(BUILD_AREA)/esp-open-sdk/esp-open-lwip/include include EXTRA_INCDIR = include @@ -141,4 +141,3 @@ clean: $(Q) rm -rf $(FW_BASE) $(BUILD_BASE) $(foreach bdir,$(BUILD_DIR),$(eval $(call compile-objects,$(bdir)))) - diff --git a/Makefile.orig b/Makefile.orig index c3e87a7..165f489 100644 --- a/Makefile.orig +++ b/Makefile.orig @@ -36,12 +36,12 @@ TARGET ?= esp_mqtt TARGET_LIB ?= libmqtt.a # which modules (subdirectories) of the project to include in compiling -USER_MODULES = user driver mqtt modules +USER_MODULES = user driver uMQTTBroker/src modules USER_INC = include USER_LIB = # which modules (subdirectories) of the project to include when compiling as library -LIB_MODULES = mqtt +LIB_MODULES = uMQTTBroker/src SDK_LIBDIR = lib SDK_LIBS = c gcc phy pp net80211 wpa main lwip diff --git a/README.md b/README.md index d9804b1..5d6912a 100644 --- a/README.md +++ b/README.md @@ -209,23 +209,8 @@ The broker does not yet support: - TLS " # Using the esp_uMQTT_broker in an Arduino project -There is a quick-and-dirty hack to add the pure broker functionality (not the CLI and the scripting) to any ESP Arduino project: +You can use the pure broker functionality (not the CLI and the scripting) in any ESP Arduino project by using https://github.com/martin-ger/uMQTTBroker . Just clone (or download the zip-file and extract it) into the libraries directory of your Arduino ESP8266 installation. -- Go to the install directory of the ESP8266 support package (something like: "[yourArduinoDir]/hardware/esp8266com/esp8266") -- Look for the file "platform.txt" -- Search for the line with "compiler.c.elf.libs" -- Add "-lmqtt" to the libs. Now it should look like: -``` -compiler.c.elf.libs=-lm -lgcc -lhal -lphy -lpp -lnet80211 -lwpa -lcrypto -lmain -lwps -laxtls -lsmartconfig -lmesh -lwpa2 -lmqtt {build.lwip_lib} -lstdc++ -``` -- From this directory go to "cd tools/sdk/lib". -- Copy "libmqtt.a" from the "firmware" directory of this repository into that location (where the other C-libs of the SDK are). -- From this directory go to "cd ../include". -- Copy "mqtt_server.h" from the "Arduino" directory of this repository into that location (where the other include files of the SDK are). -- Now you can use it in your sketch. Just include -```c -#include "mqtt_server.h" -``` Now you can use the API as described in the next subsection. Sample: in the Arduino setup() initialize the WiFi connection (client or SoftAP, whatever you need) and somewhere at the end add these line: @@ -235,7 +220,7 @@ MQTT_server_start(1883, 30, 30); The MQTT server will now run in the background and you can connect with any MQTT client. Your Arduino project might do other application logic in its loop. -You can find a sample sketch in the "Arduino" directory. +You can find a sample sketch in the examples. # Using the Source Code The complete broker functionality is included in the mqtt directory and can be integrated into any NONOS SDK (or ESP Arduino) program ("make -f Makefile.orig lib" will build the mqtt code as a C library). You can find a minimal demo in the directory "user_basic". Rename it to "user", adapt "user_config.h", and do the "make" to build a small demo that just starts an MQTT broker without any additional logic. diff --git a/firmware/0x00000.bin b/firmware/0x00000.bin index 396891eb8d613b091ba7a973263c42d37df137cf..9ccf75fb628367bb1d28490362567beac3521ab1 100644 GIT binary patch delta 1188 zcmZ9}ZA@EL90u@nF1=fetx)L35E=^iUIQd#Y)XU==Kd9?5ZTr^qMKt|BkDHTpad2T zbJ8)$d_n1UVu;xXS*FRR7+B79U`*C#BzEdzrzW7X1a&bOHL^IPMAPF_>4(K8zdp}- zY42%IY+j4aYm*60k3DJ8lDw*Ytm(mQWe4ROdXJ`u_F5DcUgsVj5#7$io+!qX5QC&# zcCP0&3+ejOD#rF%bY^xR3nIfXQN!4U83&7?M_~+hJpOWF07-IJ_9@OMq?BYlYui#1~|pw<8OWd)wEgv4e8$nv>#7IDVH5#$&|Wz}7Rdt#`$ zofa)1m&DbgZ~0f^Tyb0ffo_YKq3lLvdzrF(VYpP;4$ReJZfZJZ{}s0ABjl{9Y_~|+ z^XNWgak{71z&T{lRKG!quT5(C)Fel@K?_F=I$O!e{jNcCm>u#*oSJl-*crkcM3?IKs1V5k^J4B;OUrVPAxZ zxL#6F*o(FwpG)9~tz?h``-07k28Ab3%XY=y5IOC|mV27L^q{cWQi{7}|B5=L*km!&Qr8DD}6(19o3p%_qh3(>9-zb}3o_n$oO4q)g-5uz8tV?wL4B^&ocyV~pYvL|zvpr`Z;@3moY8RbYmox~)JBtr z8(@P;;{z7`5@M8u-ZqoolKRm$UX?RqQ1X9DUtO6CzP6IbGVABX>jNeCKQL}k5cWVP z9D!l@<2I+z-^q-pkqMZEALY$~`*{EMZ%hh7K_;t^n_wGUhsBK3$cwj)KgsktWNV>C zCo`tkt@O4BEgGq}sJX$S4)__;y?T>Hqk#W9{Svi(r7|)2RE3W!{=1Tu>6Q21e*w|t BzBB*; delta 1188 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 delta 23603 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 diff --git a/firmware/libmqtt.a b/firmware/libmqtt.a deleted file mode 100644 index 488d338a259306132fd74b4d12384772a318d30e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 292160 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 Sc~cj?y{C0jM@weT?EOzv;KOqO diff --git a/firmware/sha1sums b/firmware/sha1sums index a9c429b..710cf5a 100644 --- a/firmware/sha1sums +++ b/firmware/sha1sums @@ -1,2 +1,2 @@ -2e1def6f5b226e294097069debab364588066cf6 0x00000.bin -d58a90297c78ac2106abb8ea7f0864927b75727a 0x10000.bin +3fa016262b70ebe0dd23d7e99c34df7d7e6ae5b7 0x00000.bin +bfe86bb5e16d735add5eda99d77c90598d8b59e3 0x10000.bin diff --git a/mqtt/Makefile b/mqtt/Makefile deleted file mode 100644 index 18afce5..0000000 --- a/mqtt/Makefile +++ /dev/null @@ -1,45 +0,0 @@ - -############################################################# -# Required variables for each makefile -# Discard this section from all parent makefiles -# Expected variables (with automatic defaults): -# CSRCS (all "C" files in the dir) -# SUBDIRS (all subdirs with a Makefile) -# GEN_LIBS - list of libs to be generated () -# GEN_IMAGES - list of images to be generated () -# COMPONENTS_xxx - a list of libs/objs in the form -# subdir/lib to be extracted and rolled up into -# a generated lib/image xxx.a () -# -ifndef PDIR -GEN_LIBS = libmqtt.a -endif - - -############################################################# -# Configuration i.e. compile options etc. -# Target specific stuff (defines etc.) goes in here! -# Generally values applying to a tree are captured in the -# makefile at its root level - these are then overridden -# for a subtree within the makefile rooted therein -# -#DEFINES += - -############################################################# -# Recursion Magic - Don't touch this!! -# -# Each subtree potentially has an include directory -# corresponding to the common APIs applicable to modules -# rooted at that subtree. Accordingly, the INCLUDE PATH -# of a module can only contain the include directories up -# its parent path, and not its siblings -# -# Required for each makefile to inherit from the parent -# - -INCLUDES := $(INCLUDES) -I $(PDIR)include -INCLUDES += -I ./ -PDIR := ../$(PDIR) -sinclude $(PDIR)Makefile - - diff --git a/mqtt/include/debug.h b/mqtt/include/debug.h deleted file mode 100644 index 32b9b66..0000000 --- a/mqtt/include/debug.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * debug.h - * - * Created on: Dec 4, 2014 - * Author: Minh - */ - -#ifndef USER_DEBUG_H_ -#define USER_DEBUG_H_ - -#if defined(MQTT_DEBUG_ON) -#define MQTT_INFO( format, ... ) os_printf( format, ## __VA_ARGS__ ) -#else -#define MQTT_INFO( format, ... ) -#endif - - -#endif /* USER_DEBUG_H_ */ diff --git a/mqtt/include/mqtt.h b/mqtt/include/mqtt.h deleted file mode 100644 index 9832393..0000000 --- a/mqtt/include/mqtt.h +++ /dev/null @@ -1,149 +0,0 @@ -/* mqtt.h -* -* Copyright (c) 2014-2015, Tuan PM -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* * Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* * 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. -* * Neither the name of Redis 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 OWNER 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. -*/ -#ifndef USER_AT_MQTT_H_ -#define USER_AT_MQTT_H_ -#include "user_config.h" -#include "mqtt_msg.h" -#include "user_interface.h" - -#include "queue.h" -typedef struct mqtt_event_data_t -{ - uint8_t type; - const char* topic; - const char* data; - uint16_t topic_length; - uint16_t data_length; - uint16_t data_offset; -} mqtt_event_data_t; - -typedef struct mqtt_state_t -{ - uint16_t port; - int auto_reconnect; - mqtt_connect_info_t* connect_info; - uint8_t* in_buffer; - uint8_t* out_buffer; - int in_buffer_length; - int out_buffer_length; - uint16_t message_length; - uint16_t message_length_read; - mqtt_message_t* outbound_message; - mqtt_connection_t mqtt_connection; - uint16_t pending_msg_id; - int pending_msg_type; - int pending_publish_qos; -} mqtt_state_t; - -typedef enum { - WIFI_INIT, - WIFI_CONNECTING, - WIFI_CONNECTING_ERROR, - WIFI_CONNECTED, - DNS_RESOLVE, - TCP_DISCONNECTING, - TCP_DISCONNECTED, - TCP_DISCONNECT, - TCP_RECONNECT_DISCONNECTING, - TCP_RECONNECT_REQ, - TCP_RECONNECT, - TCP_CONNECTING, - TCP_CONNECTING_ERROR, - TCP_CONNECTED, - MQTT_CONNECT_SEND, - MQTT_CONNECT_SENDING, - MQTT_SUBSCIBE_SEND, - MQTT_SUBSCIBE_SENDING, - MQTT_DATA, - MQTT_KEEPALIVE_SEND, - MQTT_PUBLISH_RECV, - MQTT_PUBLISHING, - MQTT_DELETING, - MQTT_DELETED, -} tConnState; - -typedef void (*MqttCallback)(uint32_t *args); -typedef void (*MqttDataCallback)(uint32_t *args, const char* topic, uint32_t topic_len, const char *data, uint32_t lengh); - -typedef struct { - struct espconn *pCon; - uint8_t security; - uint8_t* host; - uint32_t port; - ip_addr_t ip; - mqtt_state_t mqtt_state; - mqtt_connect_info_t connect_info; - MqttCallback connectedCb; - MqttCallback disconnectedCb; - MqttCallback publishedCb; - MqttCallback timeoutCb; - MqttDataCallback dataCb; - ETSTimer mqttTimer; - uint32_t keepAliveTick; - uint32_t reconnectTick; - uint32_t sendTimeout; - tConnState connState; - QUEUE msgQueue; - void* user_data; -} MQTT_Client; - -#define SEC_NONSSL 0 -#define SEC_SSL 1 - -#define MQTT_FLAG_CONNECTED 1 -#define MQTT_FLAG_READY 2 -#define MQTT_FLAG_EXIT 4 - -#define MQTT_EVENT_TYPE_NONE 0 -#define MQTT_EVENT_TYPE_CONNECTED 1 -#define MQTT_EVENT_TYPE_DISCONNECTED 2 -#define MQTT_EVENT_TYPE_SUBSCRIBED 3 -#define MQTT_EVENT_TYPE_UNSUBSCRIBED 4 -#define MQTT_EVENT_TYPE_PUBLISH 5 -#define MQTT_EVENT_TYPE_PUBLISHED 6 -#define MQTT_EVENT_TYPE_EXITED 7 -#define MQTT_EVENT_TYPE_PUBLISH_CONTINUATION 8 - -void ICACHE_FLASH_ATTR MQTT_InitConnection(MQTT_Client *mqttClient, uint8_t* host, uint32_t port, uint8_t security); -BOOL ICACHE_FLASH_ATTR MQTT_InitClient(MQTT_Client *mqttClient, uint8_t* client_id, uint8_t* client_user, uint8_t* client_pass, uint32_t keepAliveTime, uint8_t cleanSession); -void ICACHE_FLASH_ATTR MQTT_DeleteClient(MQTT_Client *mqttClient); -void ICACHE_FLASH_ATTR MQTT_InitLWT(MQTT_Client *mqttClient, uint8_t* will_topic, uint8_t* will_msg, uint8_t will_qos, uint8_t will_retain); -void ICACHE_FLASH_ATTR MQTT_OnConnected(MQTT_Client *mqttClient, MqttCallback connectedCb); -void ICACHE_FLASH_ATTR MQTT_OnDisconnected(MQTT_Client *mqttClient, MqttCallback disconnectedCb); -void ICACHE_FLASH_ATTR MQTT_OnPublished(MQTT_Client *mqttClient, MqttCallback publishedCb); -void ICACHE_FLASH_ATTR MQTT_OnTimeout(MQTT_Client *mqttClient, MqttCallback timeoutCb); -void ICACHE_FLASH_ATTR MQTT_OnData(MQTT_Client *mqttClient, MqttDataCallback dataCb); -BOOL ICACHE_FLASH_ATTR MQTT_Subscribe(MQTT_Client *client, char* topic, uint8_t qos); -BOOL ICACHE_FLASH_ATTR MQTT_UnSubscribe(MQTT_Client *client, char* topic); -void ICACHE_FLASH_ATTR MQTT_Connect(MQTT_Client *mqttClient); -void ICACHE_FLASH_ATTR MQTT_Disconnect(MQTT_Client *mqttClient); -BOOL ICACHE_FLASH_ATTR MQTT_Publish(MQTT_Client *client, const char* topic, const char* data, int data_length, int qos, int retain); - -#endif /* USER_AT_MQTT_H_ */ diff --git a/mqtt/include/mqtt_msg.h b/mqtt/include/mqtt_msg.h deleted file mode 100644 index 5f24cc8..0000000 --- a/mqtt/include/mqtt_msg.h +++ /dev/null @@ -1,194 +0,0 @@ -/* - * File: mqtt_msg.h - * Author: Minh Tuan - * - * Created on July 12, 2014, 1:05 PM - */ - -#ifndef MQTT_MSG_H -#define MQTT_MSG_H -#include "user_config.h" -#include "c_types.h" -#ifdef __cplusplus -extern "C" { -#endif - -/* -* Copyright (c) 2014, Stephen Robinson -* 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. -* -*/ -/* 7 6 5 4 3 2 1 0*/ -/*| --- Message Type---- | DUP Flag | QoS Level | Retain | -/* Remaining Length */ - -enum mqtt_message_type -{ - MQTT_MSG_TYPE_CONNECT = 1, - MQTT_MSG_TYPE_CONNACK = 2, - MQTT_MSG_TYPE_PUBLISH = 3, - MQTT_MSG_TYPE_PUBACK = 4, - MQTT_MSG_TYPE_PUBREC = 5, - MQTT_MSG_TYPE_PUBREL = 6, - MQTT_MSG_TYPE_PUBCOMP = 7, - MQTT_MSG_TYPE_SUBSCRIBE = 8, - MQTT_MSG_TYPE_SUBACK = 9, - MQTT_MSG_TYPE_UNSUBSCRIBE = 10, - MQTT_MSG_TYPE_UNSUBACK = 11, - MQTT_MSG_TYPE_PINGREQ = 12, - MQTT_MSG_TYPE_PINGRESP = 13, - MQTT_MSG_TYPE_DISCONNECT = 14 -}; - -enum mqtt_connect_return_code -{ - CONNECTION_ACCEPTED = 0, - CONNECTION_REFUSE_PROTOCOL, - CONNECTION_REFUSE_ID_REJECTED, - CONNECTION_REFUSE_SERVER_UNAVAILABLE, - CONNECTION_REFUSE_BAD_USERNAME, - CONNECTION_REFUSE_NOT_AUTHORIZED -}; - -typedef struct mqtt_message -{ - uint8_t* data; - uint16_t length; - -} mqtt_message_t; - -typedef struct mqtt_connection -{ - mqtt_message_t message; - - uint16_t message_id; - uint8_t* buffer; - uint16_t buffer_length; - -} mqtt_connection_t; - -typedef struct mqtt_connect_info -{ - char* client_id; - char* username; - char* password; - char* will_topic; - char* will_data; - uint16_t will_data_len; - uint32_t keepalive; - int will_qos; - int will_retain; - int clean_session; - -} mqtt_connect_info_t; - -#define MQTT_MAX_FIXED_HEADER_SIZE 3 - -enum mqtt_connect_flag -{ - MQTT_CONNECT_FLAG_USERNAME = 1 << 7, - MQTT_CONNECT_FLAG_PASSWORD = 1 << 6, - MQTT_CONNECT_FLAG_WILL_RETAIN = 1 << 5, - MQTT_CONNECT_FLAG_WILL = 1 << 2, - MQTT_CONNECT_FLAG_CLEAN_SESSION = 1 << 1 -}; - -struct __attribute((__packed__)) mqtt_connect_variable_header -{ - uint8_t lengthMsb; - uint8_t lengthLsb; -#if defined(PROTOCOL_NAMEv31) - uint8_t magic[6]; -#elif defined(PROTOCOL_NAMEv311) - uint8_t magic[4]; -#else -#error "Please define protocol name" -#endif - uint8_t version; - uint8_t flags; - uint8_t keepaliveMsb; - uint8_t keepaliveLsb; -}; - -struct __attribute((__packed__)) mqtt_connect_variable_header3 -{ - uint8_t lengthMsb; - uint8_t lengthLsb; - uint8_t magic[6]; - uint8_t version; - uint8_t flags; - uint8_t keepaliveMsb; - uint8_t keepaliveLsb; -}; - -struct __attribute((__packed__)) mqtt_connect_variable_header4 -{ - uint8_t lengthMsb; - uint8_t lengthLsb; - uint8_t magic[4]; - uint8_t version; - uint8_t flags; - uint8_t keepaliveMsb; - uint8_t keepaliveLsb; -}; - -static inline int ICACHE_FLASH_ATTR mqtt_get_type(uint8_t* buffer) { return (buffer[0] & 0xf0) >> 4; } -static inline int ICACHE_FLASH_ATTR mqtt_get_connect_return_code(uint8_t* buffer) { return buffer[3]; } -static inline int ICACHE_FLASH_ATTR mqtt_get_dup(uint8_t* buffer) { return (buffer[0] & 0x08) >> 3; } -static inline int ICACHE_FLASH_ATTR mqtt_get_qos(uint8_t* buffer) { return (buffer[0] & 0x06) >> 1; } -static inline int ICACHE_FLASH_ATTR mqtt_get_retain(uint8_t* buffer) { return (buffer[0] & 0x01); } - -void ICACHE_FLASH_ATTR mqtt_msg_init(mqtt_connection_t* connection, uint8_t* buffer, uint16_t buffer_length); -int ICACHE_FLASH_ATTR mqtt_get_total_length(uint8_t* buffer, uint16_t length); -char* ICACHE_FLASH_ATTR mqtt_get_str(uint8_t* buffer, uint16_t* length); -const char* ICACHE_FLASH_ATTR mqtt_get_publish_topic(uint8_t* buffer, uint16_t* length); -const char* ICACHE_FLASH_ATTR mqtt_get_publish_data(uint8_t* buffer, uint16_t* length); -uint16_t ICACHE_FLASH_ATTR mqtt_get_id(uint8_t* buffer, uint16_t length); - -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_connect(mqtt_connection_t* connection, mqtt_connect_info_t* info); -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_connack(mqtt_connection_t* connection, enum mqtt_connect_return_code retcode); -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_publish(mqtt_connection_t* connection, const char* topic, const char* data, int data_length, int qos, int retain, uint16_t* message_id); -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_puback(mqtt_connection_t* connection, uint16_t message_id); -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_pubrec(mqtt_connection_t* connection, uint16_t message_id); -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_pubrel(mqtt_connection_t* connection, uint16_t message_id); -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_pubcomp(mqtt_connection_t* connection, uint16_t message_id); -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_subscribe(mqtt_connection_t* connection, const char* topic, int qos, uint16_t* message_id); -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_suback(mqtt_connection_t* connection, uint8_t *ret_codes, uint8_t ret_codes_len, uint16_t message_id); -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_unsubscribe(mqtt_connection_t* connection, const char* topic, uint16_t* message_id); -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_unsuback(mqtt_connection_t* connection, uint16_t message_id); -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_pingreq(mqtt_connection_t* connection); -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_pingresp(mqtt_connection_t* connection); -mqtt_message_t* ICACHE_FLASH_ATTR mqtt_msg_disconnect(mqtt_connection_t* connection); - - -#ifdef __cplusplus -} -#endif - -#endif /* MQTT_MSG_H */ - diff --git a/mqtt/include/mqtt_retainedlist.h b/mqtt/include/mqtt_retainedlist.h deleted file mode 100644 index def1145..0000000 --- a/mqtt/include/mqtt_retainedlist.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef _MQTT_RETAINEDLIST_H_ -#define _MQTT_RETAINEDLIST_H_ - -#include "mqtt_server.h" - -typedef struct _retained_entry { - uint8_t *topic; - uint8_t *data; - uint16_t data_len; - uint8_t qos; -} retained_entry; - -typedef bool (*iterate_retainedtopic_cb)(retained_entry *topic, void *user_data); -typedef bool (*find_retainedtopic_cb)(retained_entry *topic, void *user_data); -typedef void (*on_retainedtopic_cb)(retained_entry *topic); - -bool create_retainedlist(uint16_t num_entires); -void clear_retainedtopics(); -bool update_retainedtopic(uint8_t *topic, uint8_t *data, uint16_t data_len, uint8_t qos); -bool find_retainedtopic(uint8_t *topic, find_retainedtopic_cb cb, void *user_data); -void iterate_retainedtopics(iterate_retainedtopic_cb cb, void *user_data); - -int serialize_retainedtopics(char *buf, int len); -bool deserialize_retainedtopics(char *buf, int len); - -void set_on_retainedtopic_cb(on_retainedtopic_cb cb); - -#endif /* _MQTT_RETAINEDLIST_H_ */ diff --git a/mqtt/include/mqtt_server.h b/mqtt/include/mqtt_server.h deleted file mode 100644 index e5362d5..0000000 --- a/mqtt/include/mqtt_server.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef _MQTT_SERVER_H_ -#define _MQTT_SERVER_H_ - -#include "c_types.h" -#include "osapi.h" -#include "os_type.h" -#include "ip_addr.h" -#include "espconn.h" -//#include "lwip/ip.h" -//#include "lwip/app/espconn.h" -//#include "lwip/app/espconn_tcp.h" - -#include "mqtt.h" - -#define LOCAL_MQTT_CLIENT ((void*)-1) - -typedef bool (*MqttAuthCallback)(const char* username, const char *password, struct espconn *pesp_conn); -typedef bool (*MqttConnectCallback)(struct espconn *pesp_conn, uint16_t client_count); - -typedef struct _MQTT_ClientCon { - struct espconn *pCon; -// uint8_t security; -// uint32_t port; -// ip_addr_t ip; - mqtt_state_t mqtt_state; - mqtt_connect_info_t connect_info; -// MqttCallback connectedCb; -// MqttCallback disconnectedCb; -// MqttCallback publishedCb; -// MqttCallback timeoutCb; -// MqttDataCallback dataCb; - ETSTimer mqttTimer; - uint32_t sendTimeout; - tConnState connState; - QUEUE msgQueue; - uint8_t protocolVersion; - void* user_data; - struct _MQTT_ClientCon *next; -} MQTT_ClientCon; - -extern MQTT_ClientCon *clientcon_list; - -uint16_t MQTT_server_countClientCon(); -void MQTT_server_disconnectClientCon(MQTT_ClientCon *mqttClientCon); -bool MQTT_server_deleteClientCon(MQTT_ClientCon *mqttClientCon); -void MQTT_server_cleanupClientCons(); - -bool MQTT_server_start(uint16_t portno, uint16_t max_subscriptions, uint16_t max_retained_topics); -void MQTT_server_onConnect(MqttConnectCallback connectCb); -void MQTT_server_onAuth(MqttAuthCallback authCb); -void MQTT_server_onData(MqttDataCallback dataCb); - -bool MQTT_local_publish(uint8_t* topic, uint8_t* data, uint16_t data_length, uint8_t qos, uint8_t retain); -bool MQTT_local_subscribe(uint8_t* topic, uint8_t qos); -bool MQTT_local_unsubscribe(uint8_t* topic); - -#endif /* _MQTT_SERVER_H_ */ diff --git a/mqtt/include/mqtt_topiclist.h b/mqtt/include/mqtt_topiclist.h deleted file mode 100644 index ce67917..0000000 --- a/mqtt/include/mqtt_topiclist.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef _MQTT_TOPICLIST_H_ -#define _MQTT_TOPICLIST_H_ - -#include "mqtt_server.h" - -typedef struct _topic_entry { - MQTT_ClientCon *clientcon; - uint8_t *topic; - uint8_t qos; -} topic_entry; - -typedef bool (*iterate_topic_cb)(topic_entry *topic, void *user_data); -typedef bool (*find_topic_cb)(topic_entry *topic_e, uint8_t *topic, uint8_t *data, uint16_t data_len); - -bool create_topiclist(uint16_t num_entires); -bool add_topic(MQTT_ClientCon *clientcon, uint8_t *topic, uint8_t qos); -bool delete_topic(MQTT_ClientCon *clientcon, uint8_t *topic); -bool find_topic(uint8_t *topic, find_topic_cb cb, uint8_t *data, uint16_t data_len); -void iterate_topics(iterate_topic_cb cb, void *user_data); - -#endif /* _MQTT_TOPICLIST_H_ */ diff --git a/mqtt/include/mqtt_topics.h b/mqtt/include/mqtt_topics.h deleted file mode 100644 index 8c17712..0000000 --- a/mqtt/include/mqtt_topics.h +++ /dev/null @@ -1,32 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2007, 2013 IBM Corp. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * Ian Craggs - initial API and implementation and/or initial documentation - *******************************************************************************/ - - -#if !defined(MQTT_TOPICS_H) -#define MQTT_TOPICS_H - -#define TOPIC_LEVEL_SEPARATOR "/" -#define SINGLE_LEVEL_WILDCARD "+" -#define MULTI_LEVEL_WILDCARD "#" - -int Topics_isValidName(char* aName); - -int Topics_hasWildcards(char* topic); - -int Topics_matches(char* wildTopic, int wildcards, char* topic); - -#endif /* MQTT_TOPICS_H */ - diff --git a/mqtt/include/proto.h b/mqtt/include/proto.h deleted file mode 100644 index d1ac30f..0000000 --- a/mqtt/include/proto.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * File: proto.h - * Author: ThuHien - * - * Created on November 23, 2012, 8:57 AM - */ - -#ifndef _PROTO_H_ -#define _PROTO_H_ -#include -#include "typedef.h" -#include "ringbuf_mqtt.h" - -typedef void(PROTO_PARSE_CALLBACK)(); - -typedef struct { - U8 *buf; - U16 bufSize; - U16 dataLen; - U8 isEsc; - U8 isBegin; - PROTO_PARSE_CALLBACK* callback; -} PROTO_PARSER; - -I8 ICACHE_FLASH_ATTR PROTO_Init(PROTO_PARSER *parser, PROTO_PARSE_CALLBACK *completeCallback, U8 *buf, U16 bufSize); -I8 ICACHE_FLASH_ATTR PROTO_Parse(PROTO_PARSER *parser, U8 *buf, U16 len); -I16 ICACHE_FLASH_ATTR PROTO_Add(U8 *buf, const U8 *packet, I16 bufSize); -I16 ICACHE_FLASH_ATTR PROTO_AddRb(RINGBUF *rb, const U8 *packet, I16 len); -I8 ICACHE_FLASH_ATTR PROTO_ParseByte(PROTO_PARSER *parser, U8 value); -I16 ICACHE_FLASH_ATTR PROTO_ParseRb(RINGBUF *rb, U8 *bufOut, U16* len, U16 maxBufLen); -#endif - diff --git a/mqtt/include/queue.h b/mqtt/include/queue.h deleted file mode 100644 index b6c0266..0000000 --- a/mqtt/include/queue.h +++ /dev/null @@ -1,44 +0,0 @@ -/* str_queue.h -- -* -* Copyright (c) 2014-2015, Tuan PM -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* * Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* * 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. -* * Neither the name of Redis 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 OWNER 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. -*/ - -#ifndef USER_QUEUE_H_ -#define USER_QUEUE_H_ -#include "os_type.h" -#include "ringbuf_mqtt.h" -typedef struct { - uint8_t *buf; - RINGBUF rb; -} QUEUE; - -void ICACHE_FLASH_ATTR QUEUE_Init(QUEUE *queue, int bufferSize); -int32_t ICACHE_FLASH_ATTR QUEUE_Puts(QUEUE *queue, uint8_t* buffer, uint16_t len); -int32_t ICACHE_FLASH_ATTR QUEUE_Gets(QUEUE *queue, uint8_t* buffer, uint16_t* len, uint16_t maxLen); -BOOL ICACHE_FLASH_ATTR QUEUE_IsEmpty(QUEUE *queue); -#endif /* USER_QUEUE_H_ */ diff --git a/mqtt/include/ringbuf_mqtt.h b/mqtt/include/ringbuf_mqtt.h deleted file mode 100644 index 6ac6c22..0000000 --- a/mqtt/include/ringbuf_mqtt.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef _RING_BUF_MQTT_H_ -#define _RING_BUF_MQTT_H_ - -#include -#include -#include "typedef.h" - -typedef struct { - U8* p_o; /**< Original pointer */ - U8* volatile p_r; /**< Read pointer */ - U8* volatile p_w; /**< Write pointer */ - volatile I32 fill_cnt; /**< Number of filled slots */ - I32 size; /**< Buffer size */ -} RINGBUF; - -I16 ICACHE_FLASH_ATTR RINGBUF_Init(RINGBUF *r, U8* buf, I32 size); -I16 ICACHE_FLASH_ATTR RINGBUF_Put(RINGBUF *r, U8 c); -I16 ICACHE_FLASH_ATTR RINGBUF_Get(RINGBUF *r, U8* c); -#endif diff --git a/mqtt/include/typedef.h b/mqtt/include/typedef.h deleted file mode 100644 index 887001a..0000000 --- a/mqtt/include/typedef.h +++ /dev/null @@ -1,17 +0,0 @@ -/** -* \file -* Standard Types definition -*/ - -#ifndef _TYPE_DEF_H_ -#define _TYPE_DEF_H_ - -typedef char I8; -typedef unsigned char U8; -typedef short I16; -typedef unsigned short U16; -typedef long I32; -typedef unsigned long U32; -typedef unsigned long long U64; - -#endif diff --git a/mqtt/include/utils.h b/mqtt/include/utils.h deleted file mode 100644 index fe28748..0000000 --- a/mqtt/include/utils.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef _UTILS_H_ -#define _UTILS_H_ - -#include "c_types.h" - -uint32_t ICACHE_FLASH_ATTR UTILS_Atoh(const int8_t *s); -uint8_t ICACHE_FLASH_ATTR UTILS_StrToIP(const int8_t* str, void *ip); -uint8_t ICACHE_FLASH_ATTR UTILS_IsIPV4 (int8_t *str); -#endif diff --git a/mqtt/mqtt.c b/mqtt/mqtt.c deleted file mode 100644 index aa5154f..0000000 --- a/mqtt/mqtt.c +++ /dev/null @@ -1,943 +0,0 @@ -/* mqtt.c -* Protocol: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html -* -* Copyright (c) 2014-2015, Tuan PM -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* * Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* * 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. -* * Neither the name of Redis 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 OWNER 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. -*/ - -#include "user_interface.h" -#include "osapi.h" -#include "espconn.h" -#include "os_type.h" -#include "mem.h" -#include "mqtt_msg.h" -#include "debug.h" -#include "user_config.h" -#include "mqtt.h" -#include "queue.h" - -#define MQTT_TASK_PRIO 2 -#define MQTT_TASK_QUEUE_SIZE 1 -#define MQTT_SEND_TIMOUT 5 - -#ifndef MQTT_SSL_SIZE -#define MQTT_SSL_SIZE 5120 -#endif - -#ifndef QUEUE_BUFFER_SIZE -#define QUEUE_BUFFER_SIZE 2048 -#endif - -unsigned char *default_certificate; -unsigned int default_certificate_len = 0; -unsigned char *default_private_key; -unsigned int default_private_key_len = 0; - -os_event_t mqtt_procTaskQueue[MQTT_TASK_QUEUE_SIZE]; - -#ifdef PROTOCOL_NAMEv311 -LOCAL uint8_t zero_len_id[2] = { 0, 0 }; -#endif - -LOCAL void ICACHE_FLASH_ATTR mqtt_dns_found(const char *name, ip_addr_t * ipaddr, void *arg) { - struct espconn *pConn = (struct espconn *)arg; - MQTT_Client *client = (MQTT_Client *) pConn->reverse; - - if (ipaddr == NULL) { - MQTT_INFO("DNS: Found, but got no ip, try to reconnect\r\n"); - client->connState = TCP_RECONNECT_REQ; - return; - } - - MQTT_INFO("DNS: found ip %d.%d.%d.%d\n", - *((uint8 *) & ipaddr->addr), - *((uint8 *) & ipaddr->addr + 1), *((uint8 *) & ipaddr->addr + 2), *((uint8 *) & ipaddr->addr + 3)); - - if (client->ip.addr == 0 && ipaddr->addr != 0) { - os_memcpy(client->pCon->proto.tcp->remote_ip, &ipaddr->addr, 4); - if (client->security) { -#ifdef MQTT_SSL_ENABLE - espconn_secure_set_size(ESPCONN_CLIENT, MQTT_SSL_SIZE); - espconn_secure_connect(client->pCon); -#else - MQTT_INFO("TCP: Do not support SSL\r\n"); -#endif - } else { - espconn_connect(client->pCon); - } - - client->connState = TCP_CONNECTING; - MQTT_INFO("TCP: connecting...\r\n"); - } - - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); -} - -LOCAL void ICACHE_FLASH_ATTR deliver_publish(MQTT_Client * client, uint8_t * message, int length) { - mqtt_event_data_t event_data; - - event_data.topic_length = length; - event_data.topic = mqtt_get_publish_topic(message, &event_data.topic_length); - event_data.data_length = length; - event_data.data = mqtt_get_publish_data(message, &event_data.data_length); - - if (client->dataCb) - client->dataCb((uint32_t *) client, event_data.topic, event_data.topic_length, event_data.data, - event_data.data_length); - -} - -void ICACHE_FLASH_ATTR mqtt_send_keepalive(MQTT_Client * client) { - MQTT_INFO("\r\nMQTT: Send keepalive packet to %s:%d!\r\n", client->host, client->port); - client->mqtt_state.outbound_message = mqtt_msg_pingreq(&client->mqtt_state.mqtt_connection); - client->mqtt_state.pending_msg_type = MQTT_MSG_TYPE_PINGREQ; - client->mqtt_state.pending_msg_type = mqtt_get_type(client->mqtt_state.outbound_message->data); - client->mqtt_state.pending_msg_id = - mqtt_get_id(client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length); - - client->sendTimeout = MQTT_SEND_TIMOUT; - MQTT_INFO("MQTT: Sending, type: %d, id: %04X\r\n", client->mqtt_state.pending_msg_type, - client->mqtt_state.pending_msg_id); - err_t result = ESPCONN_OK; - if (client->security) { -#ifdef MQTT_SSL_ENABLE - result = - espconn_secure_send(client->pCon, client->mqtt_state.outbound_message->data, - client->mqtt_state.outbound_message->length); -#else - MQTT_INFO("TCP: Do not support SSL\r\n"); -#endif - } else { - result = - espconn_send(client->pCon, client->mqtt_state.outbound_message->data, - client->mqtt_state.outbound_message->length); - } - - client->mqtt_state.outbound_message = NULL; - if (ESPCONN_OK == result) { - client->keepAliveTick = 0; - client->connState = MQTT_DATA; - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); - } else { - client->connState = TCP_RECONNECT_DISCONNECTING; - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); - } -} - -/** - * @brief Delete tcp client and free all memory - * @param mqttClient: The mqtt client which contain TCP client - * @retval None - */ -void ICACHE_FLASH_ATTR mqtt_tcpclient_delete(MQTT_Client * mqttClient) { - if (mqttClient->pCon != NULL) { - MQTT_INFO("TCP: Free memory\r\n"); - // Force abort connections - espconn_abort(mqttClient->pCon); - // Delete connections - espconn_delete(mqttClient->pCon); - - if (mqttClient->pCon->proto.tcp) { - os_free(mqttClient->pCon->proto.tcp); - mqttClient->pCon->proto.tcp = NULL; - } - os_free(mqttClient->pCon); - mqttClient->pCon = NULL; - } -} - -/** - * @brief Delete MQTT client and free all memory - * @param mqttClient: The mqtt client - * @retval None - */ -void ICACHE_FLASH_ATTR mqtt_client_delete(MQTT_Client * mqttClient) { - if (mqttClient == NULL) - return; - - if (mqttClient->pCon != NULL) { - mqtt_tcpclient_delete(mqttClient); - } - - if (mqttClient->host != NULL) { - os_free(mqttClient->host); - mqttClient->host = NULL; - } - - if (mqttClient->user_data != NULL) { - os_free(mqttClient->user_data); - mqttClient->user_data = NULL; - } - - if (mqttClient->mqtt_state.in_buffer != NULL) { - os_free(mqttClient->mqtt_state.in_buffer); - mqttClient->mqtt_state.in_buffer = NULL; - } - - if (mqttClient->mqtt_state.out_buffer != NULL) { - os_free(mqttClient->mqtt_state.out_buffer); - mqttClient->mqtt_state.out_buffer = NULL; - } - - if (mqttClient->mqtt_state.outbound_message != NULL) { - if (mqttClient->mqtt_state.outbound_message->data != NULL) { - os_free(mqttClient->mqtt_state.outbound_message->data); - mqttClient->mqtt_state.outbound_message->data = NULL; - } - } - - if (mqttClient->mqtt_state.mqtt_connection.buffer != NULL) { - // Already freed but not NULL - mqttClient->mqtt_state.mqtt_connection.buffer = NULL; - } - - if (mqttClient->connect_info.client_id != NULL) { -#ifdef PROTOCOL_NAMEv311 - /* Don't attempt to free if it's the zero_len array */ - if (((uint8_t *) mqttClient->connect_info.client_id) != zero_len_id) - os_free(mqttClient->connect_info.client_id); -#else - os_free(mqttClient->connect_info.client_id); -#endif - mqttClient->connect_info.client_id = NULL; - } - - if (mqttClient->connect_info.username != NULL) { - os_free(mqttClient->connect_info.username); - mqttClient->connect_info.username = NULL; - } - - if (mqttClient->connect_info.password != NULL) { - os_free(mqttClient->connect_info.password); - mqttClient->connect_info.password = NULL; - } - - if (mqttClient->connect_info.will_topic != NULL) { - os_free(mqttClient->connect_info.will_topic); - mqttClient->connect_info.will_topic = NULL; - } - - if (mqttClient->connect_info.will_data != NULL) { - os_free(mqttClient->connect_info.will_data); - mqttClient->connect_info.will_data = NULL; - } - - if (mqttClient->msgQueue.buf != NULL) { - os_free(mqttClient->msgQueue.buf); - mqttClient->msgQueue.buf = NULL; - } - // Initialize state - mqttClient->connState = WIFI_INIT; - // Clear callback functions to avoid abnormal callback - mqttClient->connectedCb = NULL; - mqttClient->disconnectedCb = NULL; - mqttClient->publishedCb = NULL; - mqttClient->timeoutCb = NULL; - mqttClient->dataCb = NULL; - - MQTT_INFO("MQTT: client already deleted\r\n"); -} - -/** - * @brief Client received callback function. - * @param arg: contain the ip link information - * @param pdata: received data - * @param len: the lenght of received data - * @retval None - */ -void ICACHE_FLASH_ATTR mqtt_tcpclient_recv(void *arg, char *pdata, unsigned short len) { - uint8_t msg_type; - uint8_t msg_qos; - uint16_t msg_id; - uint8_t msg_conn_ret; - - struct espconn *pCon = (struct espconn *)arg; - MQTT_Client *client = (MQTT_Client *) pCon->reverse; - - //client->keepAliveTick = 0; - READPACKET: - MQTT_INFO("TCP: data received %d bytes\r\n", len); - // MQTT_INFO("STATE: %d\r\n", client->connState); - if (len < MQTT_BUF_SIZE && len > 0) { - os_memcpy(client->mqtt_state.in_buffer, pdata, len); - - msg_type = mqtt_get_type(client->mqtt_state.in_buffer); - msg_qos = mqtt_get_qos(client->mqtt_state.in_buffer); - msg_id = mqtt_get_id(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_length); - switch (client->connState) { - case MQTT_CONNECT_SENDING: - if (msg_type == MQTT_MSG_TYPE_CONNACK) { - if (client->mqtt_state.pending_msg_type != MQTT_MSG_TYPE_CONNECT) { - MQTT_INFO("MQTT: Invalid packet\r\n"); - if (client->security) { -#ifdef MQTT_SSL_ENABLE - espconn_secure_disconnect(client->pCon); -#else - MQTT_INFO("TCP: Do not support SSL\r\n"); -#endif - } else { - espconn_disconnect(client->pCon); - } - } else { - msg_conn_ret = mqtt_get_connect_return_code(client->mqtt_state.in_buffer); - switch (msg_conn_ret) { - case CONNECTION_ACCEPTED: - MQTT_INFO("MQTT: Connected to %s:%d\r\n", client->host, client->port); - client->connState = MQTT_DATA; - if (client->connectedCb) - client->connectedCb((uint32_t *) client); - break; - case CONNECTION_REFUSE_PROTOCOL: - case CONNECTION_REFUSE_SERVER_UNAVAILABLE: - case CONNECTION_REFUSE_BAD_USERNAME: - case CONNECTION_REFUSE_NOT_AUTHORIZED: - MQTT_INFO("MQTT: Connection refuse, reason code: %d\r\n", msg_conn_ret); - default: - if (client->security) { -#ifdef MQTT_SSL_ENABLE - espconn_secure_disconnect(client->pCon); -#else - MQTT_INFO("TCP: Do not support SSL\r\n"); -#endif - } else { - espconn_disconnect(client->pCon); - } - - } - - } - - } - break; - case MQTT_DATA: - case MQTT_KEEPALIVE_SEND: - client->mqtt_state.message_length_read = len; - client->mqtt_state.message_length = - mqtt_get_total_length(client->mqtt_state.in_buffer, client->mqtt_state.message_length_read); - - switch (msg_type) { - - case MQTT_MSG_TYPE_SUBACK: - if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_SUBSCRIBE - && client->mqtt_state.pending_msg_id == msg_id) - MQTT_INFO("MQTT: Subscribe successful\r\n"); - break; - case MQTT_MSG_TYPE_UNSUBACK: - if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_UNSUBSCRIBE - && client->mqtt_state.pending_msg_id == msg_id) - MQTT_INFO("MQTT: UnSubscribe successful\r\n"); - break; - case MQTT_MSG_TYPE_PUBLISH: - if (msg_qos == 1) - client->mqtt_state.outbound_message = mqtt_msg_puback(&client->mqtt_state.mqtt_connection, msg_id); - else if (msg_qos == 2) - client->mqtt_state.outbound_message = mqtt_msg_pubrec(&client->mqtt_state.mqtt_connection, msg_id); - if (msg_qos == 1 || msg_qos == 2) { - MQTT_INFO("MQTT: Queue response QoS: %d\r\n", msg_qos); - if (QUEUE_Puts - (&client->msgQueue, client->mqtt_state.outbound_message->data, - client->mqtt_state.outbound_message->length) == -1) { - MQTT_INFO("MQTT: Queue full\r\n"); - } - } - - deliver_publish(client, client->mqtt_state.in_buffer, client->mqtt_state.message_length_read); - break; - case MQTT_MSG_TYPE_PUBACK: - if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBLISH - && client->mqtt_state.pending_msg_id == msg_id) { - MQTT_INFO("MQTT: received MQTT_MSG_TYPE_PUBACK, finish QoS1 publish\r\n"); - } - - break; - case MQTT_MSG_TYPE_PUBREC: - client->mqtt_state.outbound_message = mqtt_msg_pubrel(&client->mqtt_state.mqtt_connection, msg_id); - if (QUEUE_Puts - (&client->msgQueue, client->mqtt_state.outbound_message->data, - client->mqtt_state.outbound_message->length) == -1) { - MQTT_INFO("MQTT: Queue full\r\n"); - } - break; - case MQTT_MSG_TYPE_PUBREL: - client->mqtt_state.outbound_message = mqtt_msg_pubcomp(&client->mqtt_state.mqtt_connection, msg_id); - if (QUEUE_Puts - (&client->msgQueue, client->mqtt_state.outbound_message->data, - client->mqtt_state.outbound_message->length) == -1) { - MQTT_INFO("MQTT: Queue full\r\n"); - } - break; - case MQTT_MSG_TYPE_PUBCOMP: - if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBLISH - && client->mqtt_state.pending_msg_id == msg_id) { - MQTT_INFO("MQTT: receive MQTT_MSG_TYPE_PUBCOMP, finish QoS2 publish\r\n"); - } - break; - case MQTT_MSG_TYPE_PINGREQ: - client->mqtt_state.outbound_message = mqtt_msg_pingresp(&client->mqtt_state.mqtt_connection); - if (QUEUE_Puts - (&client->msgQueue, client->mqtt_state.outbound_message->data, - client->mqtt_state.outbound_message->length) == -1) { - MQTT_INFO("MQTT: Queue full\r\n"); - } - break; - case MQTT_MSG_TYPE_PINGRESP: - // Ignore - break; - } - // NOTE: this is done down here and not in the switch case above - // because the PSOCK_READBUF_LEN() won't work inside a switch - // statement due to the way protothreads resume. - if (msg_type == MQTT_MSG_TYPE_PUBLISH) { - len = client->mqtt_state.message_length_read; - - if (client->mqtt_state.message_length < client->mqtt_state.message_length_read) { - //client->connState = MQTT_PUBLISH_RECV; - //Not Implement yet - len -= client->mqtt_state.message_length; - pdata += client->mqtt_state.message_length; - - MQTT_INFO("Get another published message\r\n"); - goto READPACKET; - } - - } - break; - } - } else { - MQTT_INFO("ERROR: Message too long\r\n"); - } - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); -} - -/** - * @brief Client send over callback function. - * @param arg: contain the ip link information - * @retval None - */ -void ICACHE_FLASH_ATTR mqtt_tcpclient_sent_cb(void *arg) { - struct espconn *pCon = (struct espconn *)arg; - MQTT_Client *client = (MQTT_Client *) pCon->reverse; - MQTT_INFO("TCP: Sent\r\n"); - client->sendTimeout = 0; - client->keepAliveTick = 0; - - if ((client->connState == MQTT_DATA || client->connState == MQTT_KEEPALIVE_SEND) - && client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBLISH) { - if (client->publishedCb) - client->publishedCb((uint32_t *) client); - } - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); -} - -void ICACHE_FLASH_ATTR mqtt_timer(void *arg) { - MQTT_Client *client = (MQTT_Client *) arg; - if (client->connState == MQTT_DATA) { - client->keepAliveTick++; - if (client->keepAliveTick > (client->mqtt_state.connect_info->keepalive / 2)) { - client->connState = MQTT_KEEPALIVE_SEND; - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); - } - - } else if (client->connState == TCP_RECONNECT_REQ) { - client->reconnectTick++; - if (client->reconnectTick > MQTT_RECONNECT_TIMEOUT) { - client->reconnectTick = 0; - client->connState = TCP_RECONNECT; - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); - if (client->timeoutCb) - client->timeoutCb((uint32_t *) client); - } - } - if (client->sendTimeout > 0) - client->sendTimeout--; -} - -void ICACHE_FLASH_ATTR mqtt_tcpclient_discon_cb(void *arg) { - - struct espconn *pespconn = (struct espconn *)arg; - MQTT_Client *client = (MQTT_Client *) pespconn->reverse; - MQTT_INFO("TCP: Disconnected callback\r\n"); - if (TCP_DISCONNECTING == client->connState) { - client->connState = TCP_DISCONNECTED; - } else if (MQTT_DELETING == client->connState) { - client->connState = MQTT_DELETED; - } else { - client->connState = TCP_RECONNECT_REQ; - } - if (client->disconnectedCb) - client->disconnectedCb((uint32_t *) client); - - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); -} - -/** - * @brief Tcp client connect success callback function. - * @param arg: contain the ip link information - * @retval None - */ -void ICACHE_FLASH_ATTR mqtt_tcpclient_connect_cb(void *arg) { - struct espconn *pCon = (struct espconn *)arg; - MQTT_Client *client = (MQTT_Client *) pCon->reverse; - - espconn_regist_disconcb(client->pCon, mqtt_tcpclient_discon_cb); - espconn_regist_recvcb(client->pCon, mqtt_tcpclient_recv); //////// - espconn_regist_sentcb(client->pCon, mqtt_tcpclient_sent_cb); /////// - MQTT_INFO("MQTT: Connected to broker %s:%d\r\n", client->host, client->port); - - mqtt_msg_init(&client->mqtt_state.mqtt_connection, client->mqtt_state.out_buffer, - client->mqtt_state.out_buffer_length); - client->mqtt_state.outbound_message = - mqtt_msg_connect(&client->mqtt_state.mqtt_connection, client->mqtt_state.connect_info); - client->mqtt_state.pending_msg_type = mqtt_get_type(client->mqtt_state.outbound_message->data); - client->mqtt_state.pending_msg_id = - mqtt_get_id(client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length); - - client->sendTimeout = MQTT_SEND_TIMOUT; - MQTT_INFO("MQTT: Sending, type: %d, id: %04X\r\n", client->mqtt_state.pending_msg_type, - client->mqtt_state.pending_msg_id); - if (client->security) { -#ifdef MQTT_SSL_ENABLE - espconn_secure_send(client->pCon, client->mqtt_state.outbound_message->data, - client->mqtt_state.outbound_message->length); -#else - MQTT_INFO("TCP: Do not support SSL\r\n"); -#endif - } else { - espconn_send(client->pCon, client->mqtt_state.outbound_message->data, - client->mqtt_state.outbound_message->length); - } - - client->mqtt_state.outbound_message = NULL; - client->connState = MQTT_CONNECT_SENDING; - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); -} - -/** - * @brief Tcp client connect repeat callback function. - * @param arg: contain the ip link information - * @retval None - */ -void ICACHE_FLASH_ATTR mqtt_tcpclient_recon_cb(void *arg, sint8 errType) { - struct espconn *pCon = (struct espconn *)arg; - MQTT_Client *client = (MQTT_Client *) pCon->reverse; - - MQTT_INFO("TCP: Reconnect to %s:%d\r\n", client->host, client->port); - - client->connState = TCP_RECONNECT_REQ; - - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); - -} - -/** - * @brief MQTT publish function. - * @param client: MQTT_Client reference - * @param topic: string topic will publish to - * @param data: buffer data send point to - * @param data_length: length of data - * @param qos: qos - * @param retain: retain - * @retval TRUE if success queue - */ -BOOL ICACHE_FLASH_ATTR -MQTT_Publish(MQTT_Client * client, const char *topic, const char *data, int data_length, int qos, int retain) { - uint8_t dataBuffer[MQTT_BUF_SIZE]; - uint16_t dataLen; - client->mqtt_state.outbound_message = mqtt_msg_publish(&client->mqtt_state.mqtt_connection, - topic, data, data_length, - qos, retain, &client->mqtt_state.pending_msg_id); - if (client->mqtt_state.outbound_message->length == 0) { - MQTT_INFO("MQTT: Queuing publish failed\r\n"); - return FALSE; - } - MQTT_INFO("MQTT: queuing publish, length: %d, queue size(%d/%d)\r\n", client->mqtt_state.outbound_message->length, - client->msgQueue.rb.fill_cnt, client->msgQueue.rb.size); - while (QUEUE_Puts - (&client->msgQueue, client->mqtt_state.outbound_message->data, - client->mqtt_state.outbound_message->length) == -1) { - MQTT_INFO("MQTT: Queue full\r\n"); - if (QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == -1) { - MQTT_INFO("MQTT: Serious buffer error\r\n"); - return FALSE; - } - } - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); - return TRUE; -} - -/** - * @brief MQTT subscibe function. - * @param client: MQTT_Client reference - * @param topic: string topic will subscribe - * @param qos: qos - * @retval TRUE if success queue - */ -BOOL ICACHE_FLASH_ATTR MQTT_Subscribe(MQTT_Client * client, char *topic, uint8_t qos) { - uint8_t dataBuffer[MQTT_BUF_SIZE]; - uint16_t dataLen; - - client->mqtt_state.outbound_message = mqtt_msg_subscribe(&client->mqtt_state.mqtt_connection, - topic, qos, &client->mqtt_state.pending_msg_id); - MQTT_INFO("MQTT: queue subscribe, topic\"%s\", id: %d\r\n", topic, client->mqtt_state.pending_msg_id); - while (QUEUE_Puts - (&client->msgQueue, client->mqtt_state.outbound_message->data, - client->mqtt_state.outbound_message->length) == -1) { - MQTT_INFO("MQTT: Queue full\r\n"); - if (QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == -1) { - MQTT_INFO("MQTT: Serious buffer error\r\n"); - return FALSE; - } - } - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); - - return TRUE; -} - -/** - * @brief MQTT un-subscibe function. - * @param client: MQTT_Client reference - * @param topic: String topic will un-subscribe - * @retval TRUE if success queue - */ -BOOL ICACHE_FLASH_ATTR MQTT_UnSubscribe(MQTT_Client * client, char *topic) { - uint8_t dataBuffer[MQTT_BUF_SIZE]; - uint16_t dataLen; - client->mqtt_state.outbound_message = mqtt_msg_unsubscribe(&client->mqtt_state.mqtt_connection, - topic, &client->mqtt_state.pending_msg_id); - MQTT_INFO("MQTT: queue un-subscribe, topic\"%s\", id: %d\r\n", topic, client->mqtt_state.pending_msg_id); - while (QUEUE_Puts - (&client->msgQueue, client->mqtt_state.outbound_message->data, - client->mqtt_state.outbound_message->length) == -1) { - MQTT_INFO("MQTT: Queue full\r\n"); - if (QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == -1) { - MQTT_INFO("MQTT: Serious buffer error\r\n"); - return FALSE; - } - } - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); - return TRUE; -} - -/** - * @brief MQTT ping function. - * @param client: MQTT_Client reference - * @retval TRUE if success queue - */ -BOOL ICACHE_FLASH_ATTR MQTT_Ping(MQTT_Client * client) { - uint8_t dataBuffer[MQTT_BUF_SIZE]; - uint16_t dataLen; - client->mqtt_state.outbound_message = mqtt_msg_pingreq(&client->mqtt_state.mqtt_connection); - if (client->mqtt_state.outbound_message->length == 0) { - MQTT_INFO("MQTT: Queuing publish failed\r\n"); - return FALSE; - } - MQTT_INFO("MQTT: queuing publish, length: %d, queue size(%d/%d)\r\n", client->mqtt_state.outbound_message->length, - client->msgQueue.rb.fill_cnt, client->msgQueue.rb.size); - while (QUEUE_Puts - (&client->msgQueue, client->mqtt_state.outbound_message->data, - client->mqtt_state.outbound_message->length) == -1) { - MQTT_INFO("MQTT: Queue full\r\n"); - if (QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == -1) { - MQTT_INFO("MQTT: Serious buffer error\r\n"); - return FALSE; - } - } - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) client); - return TRUE; -} - -void ICACHE_FLASH_ATTR MQTT_Task(os_event_t * e) { - MQTT_Client *client = (MQTT_Client *) e->par; - uint8_t dataBuffer[MQTT_BUF_SIZE]; - uint16_t dataLen; - if (e->par == 0) - return; - MQTT_INFO("MQTT: Client task activated - state %d\r\n", client->connState); - switch (client->connState) { - - case TCP_RECONNECT_REQ: - break; - case TCP_RECONNECT: - mqtt_tcpclient_delete(client); - MQTT_Connect(client); - MQTT_INFO("TCP: Reconnect to: %s:%d\r\n", client->host, client->port); - client->connState = TCP_CONNECTING; - break; - case MQTT_DELETING: - case TCP_DISCONNECTING: - case TCP_RECONNECT_DISCONNECTING: - if (client->security) { -#ifdef MQTT_SSL_ENABLE - espconn_secure_disconnect(client->pCon); -#else - MQTT_INFO("TCP: Do not support SSL\r\n"); -#endif - } else { - espconn_disconnect(client->pCon); - } - break; - case TCP_DISCONNECTED: - MQTT_INFO("MQTT: Disconnected\r\n"); - mqtt_tcpclient_delete(client); - break; - case MQTT_DELETED: - MQTT_INFO("MQTT: Deleted client\r\n"); - mqtt_client_delete(client); - break; - case MQTT_KEEPALIVE_SEND: - mqtt_send_keepalive(client); - break; - case MQTT_DATA: - if (QUEUE_IsEmpty(&client->msgQueue) || client->sendTimeout != 0) { - break; - } - if (QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == 0) { - client->mqtt_state.pending_msg_type = mqtt_get_type(dataBuffer); - client->mqtt_state.pending_msg_id = mqtt_get_id(dataBuffer, dataLen); - - client->sendTimeout = MQTT_SEND_TIMOUT; - MQTT_INFO("MQTT: Sending, type: %d, id: %04X\r\n", client->mqtt_state.pending_msg_type, - client->mqtt_state.pending_msg_id); - client->keepAliveTick = 0; - if (client->security) { -#ifdef MQTT_SSL_ENABLE - espconn_secure_send(client->pCon, dataBuffer, dataLen); -#else - MQTT_INFO("TCP: Do not support SSL\r\n"); -#endif - } else { - espconn_send(client->pCon, dataBuffer, dataLen); - } - - client->mqtt_state.outbound_message = NULL; - break; - } - break; - } -} - -/** - * @brief MQTT initialization connection function - * @param client: MQTT_Client reference - * @param host: Domain or IP string - * @param port: Port to connect - * @param security: 1 for ssl, 0 for none - * @retval None - */ -void ICACHE_FLASH_ATTR MQTT_InitConnection(MQTT_Client * mqttClient, uint8_t * host, uint32_t port, uint8_t security) { - uint32_t temp; - MQTT_INFO("MQTT:InitConnection\r\n"); - os_memset(mqttClient, 0, sizeof(MQTT_Client)); - temp = os_strlen(host); - mqttClient->host = (uint8_t *) os_zalloc(temp + 1); - os_strcpy(mqttClient->host, host); - mqttClient->host[temp] = 0; - mqttClient->port = port; - mqttClient->security = security; - -} - -/** - * @brief MQTT initialization mqtt client function - * @param client: MQTT_Client reference - * @param clientid: MQTT client id - * @param client_user:MQTT client user - * @param client_pass:MQTT client password - * @param client_pass:MQTT keep alive timer, in second - * @retval None - */ -BOOL ICACHE_FLASH_ATTR -MQTT_InitClient(MQTT_Client * mqttClient, uint8_t * client_id, uint8_t * client_user, uint8_t * client_pass, - uint32_t keepAliveTime, uint8_t cleanSession) { - uint32_t temp; - MQTT_INFO("MQTT:InitClient\r\n"); - - os_memset(&mqttClient->connect_info, 0, sizeof(mqtt_connect_info_t)); - - if (!client_id) { - /* Should be allowed by broker, but clean session flag must be set. */ -#ifdef PROTOCOL_NAMEv311 - if (cleanSession) { - mqttClient->connect_info.client_id = zero_len_id; - } else { - MQTT_INFO("cleanSession must be set to use 0 length client_id\r\n"); - return false; - } - /* Not supported. Return. */ -#else - MQTT_INFO("Client ID required for MQTT < 3.1.1!\r\n"); - return false; -#endif - } - - /* If connect_info's client_id is still NULL and we get here, we can * - * assume the passed client_id is non-NULL. */ - if (!(mqttClient->connect_info.client_id)) { - temp = os_strlen(client_id); - mqttClient->connect_info.client_id = (uint8_t *) os_zalloc(temp + 1); - os_strcpy(mqttClient->connect_info.client_id, client_id); - mqttClient->connect_info.client_id[temp] = 0; - } - - if (client_user) { - temp = os_strlen(client_user); - mqttClient->connect_info.username = (uint8_t *) os_zalloc(temp + 1); - os_strcpy(mqttClient->connect_info.username, client_user); - mqttClient->connect_info.username[temp] = 0; - } - - if (client_pass) { - temp = os_strlen(client_pass); - mqttClient->connect_info.password = (uint8_t *) os_zalloc(temp + 1); - os_strcpy(mqttClient->connect_info.password, client_pass); - mqttClient->connect_info.password[temp] = 0; - } - - mqttClient->connect_info.keepalive = keepAliveTime; - mqttClient->connect_info.clean_session = cleanSession; - - mqttClient->mqtt_state.in_buffer = (uint8_t *) os_zalloc(MQTT_BUF_SIZE); - mqttClient->mqtt_state.in_buffer_length = MQTT_BUF_SIZE; - mqttClient->mqtt_state.out_buffer = (uint8_t *) os_zalloc(MQTT_BUF_SIZE); - mqttClient->mqtt_state.out_buffer_length = MQTT_BUF_SIZE; - mqttClient->mqtt_state.connect_info = &mqttClient->connect_info; - - mqtt_msg_init(&mqttClient->mqtt_state.mqtt_connection, mqttClient->mqtt_state.out_buffer, - mqttClient->mqtt_state.out_buffer_length); - - QUEUE_Init(&mqttClient->msgQueue, QUEUE_BUFFER_SIZE); - - system_os_task(MQTT_Task, MQTT_TASK_PRIO, mqtt_procTaskQueue, MQTT_TASK_QUEUE_SIZE); - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) mqttClient); - return true; -} -void ICACHE_FLASH_ATTR -MQTT_InitLWT(MQTT_Client * mqttClient, uint8_t * will_topic, uint8_t * will_msg, uint8_t will_qos, uint8_t will_retain) -{ - uint32_t temp; - temp = os_strlen(will_topic); - mqttClient->connect_info.will_topic = (uint8_t *) os_zalloc(temp + 1); - os_strcpy(mqttClient->connect_info.will_topic, will_topic); - mqttClient->connect_info.will_topic[temp] = 0; - - temp = os_strlen(will_msg); - mqttClient->connect_info.will_data = (uint8_t *) os_zalloc(temp + 1); - os_strcpy(mqttClient->connect_info.will_data, will_msg); - mqttClient->connect_info.will_data[temp] = 0; - - mqttClient->connect_info.will_qos = will_qos; - mqttClient->connect_info.will_retain = will_retain; -} - -/** - * @brief Begin connect to MQTT broker - * @param client: MQTT_Client reference - * @retval None - */ -void ICACHE_FLASH_ATTR MQTT_Connect(MQTT_Client * mqttClient) { - if (mqttClient->pCon) { - // Clean up the old connection forcefully - using MQTT_Disconnect - // does not actually release the old connection until the - // disconnection callback is invoked. - mqtt_tcpclient_delete(mqttClient); - } - mqttClient->pCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); - mqttClient->pCon->type = ESPCONN_TCP; - mqttClient->pCon->state = ESPCONN_NONE; - mqttClient->pCon->proto.tcp = (esp_tcp *) os_zalloc(sizeof(esp_tcp)); - mqttClient->pCon->proto.tcp->local_port = espconn_port(); - mqttClient->pCon->proto.tcp->remote_port = mqttClient->port; - mqttClient->pCon->reverse = mqttClient; - espconn_regist_connectcb(mqttClient->pCon, mqtt_tcpclient_connect_cb); - espconn_regist_reconcb(mqttClient->pCon, mqtt_tcpclient_recon_cb); - - mqttClient->keepAliveTick = 0; - mqttClient->reconnectTick = 0; - - os_timer_disarm(&mqttClient->mqttTimer); - os_timer_setfn(&mqttClient->mqttTimer, (os_timer_func_t *) mqtt_timer, mqttClient); - os_timer_arm(&mqttClient->mqttTimer, 1000, 1); - - if (UTILS_StrToIP(mqttClient->host, &mqttClient->pCon->proto.tcp->remote_ip)) { - MQTT_INFO("TCP: Connect to ip %s:%d\r\n", mqttClient->host, mqttClient->port); - if (mqttClient->security) { -#ifdef MQTT_SSL_ENABLE - espconn_secure_set_size(ESPCONN_CLIENT, MQTT_SSL_SIZE); - espconn_secure_connect(mqttClient->pCon); -#else - MQTT_INFO("TCP: Do not support SSL\r\n"); -#endif - } else { - espconn_connect(mqttClient->pCon); - } - } else { - MQTT_INFO("TCP: Connect to domain %s:%d\r\n", mqttClient->host, mqttClient->port); - espconn_gethostbyname(mqttClient->pCon, mqttClient->host, &mqttClient->ip, mqtt_dns_found); - } - mqttClient->connState = TCP_CONNECTING; -} - -void ICACHE_FLASH_ATTR MQTT_Disconnect(MQTT_Client * mqttClient) { - mqttClient->connState = TCP_DISCONNECTING; - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) mqttClient); - os_timer_disarm(&mqttClient->mqttTimer); -} - -void ICACHE_FLASH_ATTR MQTT_DeleteClient(MQTT_Client * mqttClient) { - if (NULL == mqttClient) - return; - - mqttClient->connState = MQTT_DELETED; - // if(TCP_DISCONNECTED == mqttClient->connState) { - // mqttClient->connState = MQTT_DELETED; - // } else if(MQTT_DELETED != mqttClient->connState) { - // mqttClient->connState = MQTT_DELETING; - // } - - system_os_post(MQTT_TASK_PRIO, 0, (os_param_t) mqttClient); - os_timer_disarm(&mqttClient->mqttTimer); -} - -void ICACHE_FLASH_ATTR MQTT_OnConnected(MQTT_Client * mqttClient, MqttCallback connectedCb) { - mqttClient->connectedCb = connectedCb; -} - -void ICACHE_FLASH_ATTR MQTT_OnDisconnected(MQTT_Client * mqttClient, MqttCallback disconnectedCb) { - mqttClient->disconnectedCb = disconnectedCb; -} - -void ICACHE_FLASH_ATTR MQTT_OnData(MQTT_Client * mqttClient, MqttDataCallback dataCb) { - mqttClient->dataCb = dataCb; -} - -void ICACHE_FLASH_ATTR MQTT_OnPublished(MQTT_Client * mqttClient, MqttCallback publishedCb) { - mqttClient->publishedCb = publishedCb; -} - -void ICACHE_FLASH_ATTR MQTT_OnTimeout(MQTT_Client * mqttClient, MqttCallback timeoutCb) { - mqttClient->timeoutCb = timeoutCb; -} diff --git a/mqtt/mqtt_msg.c b/mqtt/mqtt_msg.c deleted file mode 100644 index 22e09b4..0000000 --- a/mqtt/mqtt_msg.c +++ /dev/null @@ -1,475 +0,0 @@ -/* -* Copyright (c) 2014, Stephen Robinson -* 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. -* -*/ - -#include "c_types.h" -#include "ets_sys.h" -#include "osapi.h" -#include "os_type.h" - -#include -#include "mqtt_msg.h" -#include "user_config.h" - -static int ICACHE_FLASH_ATTR append_string(mqtt_connection_t * connection, const char *string, int len) { - if (connection->message.length + len + 2 > connection->buffer_length) - return -1; - - connection->buffer[connection->message.length++] = len >> 8; - connection->buffer[connection->message.length++] = len & 0xff; - os_memcpy(connection->buffer + connection->message.length, string, len); - connection->message.length += len; - - return len + 2; -} - -static uint16_t ICACHE_FLASH_ATTR append_message_id(mqtt_connection_t * connection, uint16_t message_id) { - // If message_id is zero then we should assign one, otherwise - // we'll use the one supplied by the caller - while (message_id == 0) - message_id = ++connection->message_id; - - if (connection->message.length + 2 > connection->buffer_length) - return 0; - - connection->buffer[connection->message.length++] = message_id >> 8; - connection->buffer[connection->message.length++] = message_id & 0xff; - - return message_id; -} - -static int ICACHE_FLASH_ATTR init_message(mqtt_connection_t * connection) { - connection->message.length = MQTT_MAX_FIXED_HEADER_SIZE; - return MQTT_MAX_FIXED_HEADER_SIZE; -} - -static mqtt_message_t *ICACHE_FLASH_ATTR fail_message(mqtt_connection_t * connection) { - connection->message.data = connection->buffer; - connection->message.length = 0; - return &connection->message; -} - -static mqtt_message_t *ICACHE_FLASH_ATTR fini_message(mqtt_connection_t * connection, int type, int dup, int qos, - int retain) { - int remaining_length = connection->message.length - MQTT_MAX_FIXED_HEADER_SIZE; - - if (remaining_length > 127) { - connection->buffer[0] = ((type & 0x0f) << 4) | ((dup & 1) << 3) | ((qos & 3) << 1) | (retain & 1); - connection->buffer[1] = 0x80 | (remaining_length % 128); - connection->buffer[2] = remaining_length / 128; - connection->message.length = remaining_length + 3; - connection->message.data = connection->buffer; - } else { - connection->buffer[1] = ((type & 0x0f) << 4) | ((dup & 1) << 3) | ((qos & 3) << 1) | (retain & 1); - connection->buffer[2] = remaining_length; - connection->message.length = remaining_length + 2; - connection->message.data = connection->buffer + 1; - } - - return &connection->message; -} - -void ICACHE_FLASH_ATTR mqtt_msg_init(mqtt_connection_t * connection, uint8_t * buffer, uint16_t buffer_length) { - os_memset(connection, 0, sizeof(mqtt_connection_t)); - connection->buffer = buffer; - connection->buffer_length = buffer_length; -} - -int ICACHE_FLASH_ATTR mqtt_get_total_length(uint8_t * buffer, uint16_t length) { - int i; - int totlen = 0; - - for (i = 1; i < length; ++i) { - totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); - if ((buffer[i] & 0x80) == 0) { - ++i; - break; - } - } - totlen += i; - - return totlen; -} - -char *ICACHE_FLASH_ATTR mqtt_get_str(uint8_t * buffer, uint16_t * length) { - int i = 0; - int topiclen; - - if (i + 2 >= *length) - return NULL; - topiclen = buffer[i++] << 8; - topiclen |= buffer[i++]; - - if (i + topiclen > *length) - return NULL; - - *length = topiclen; - return buffer + i; -} - -const char *ICACHE_FLASH_ATTR mqtt_get_publish_topic(uint8_t * buffer, uint16_t * length) { - int i; - int totlen = 0; - int topiclen; - - for (i = 1; i < *length; ++i) { - totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); - if ((buffer[i] & 0x80) == 0) { - ++i; - break; - } - } - totlen += i; - - if (i + 2 >= *length) - return NULL; - topiclen = buffer[i++] << 8; - topiclen |= buffer[i++]; - - if (i + topiclen > *length) - return NULL; - - *length = topiclen; - return (const char *)(buffer + i); -} - -const char *ICACHE_FLASH_ATTR mqtt_get_publish_data(uint8_t * buffer, uint16_t * length) { - int i; - int totlen = 0; - int topiclen; - int blength = *length; - *length = 0; - - for (i = 1; i < blength; ++i) { - totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); - if ((buffer[i] & 0x80) == 0) { - ++i; - break; - } - } - totlen += i; - - if (i + 2 >= blength) - return NULL; - topiclen = buffer[i++] << 8; - topiclen |= buffer[i++]; - - if (i + topiclen >= blength) - return NULL; - - i += topiclen; - - if (mqtt_get_qos(buffer) > 0) { - if (i + 2 >= blength) - return NULL; - i += 2; - } - - if (totlen < i) - return NULL; - - if (totlen <= blength) - *length = totlen - i; - else - *length = blength - i; - return (const char *)(buffer + i); -} - -uint16_t ICACHE_FLASH_ATTR mqtt_get_id(uint8_t * buffer, uint16_t length) { - if (length < 1) - return 0; - - switch (mqtt_get_type(buffer)) { - case MQTT_MSG_TYPE_PUBLISH: - { - int i; - int topiclen; - - for (i = 1; i < length; ++i) { - if ((buffer[i] & 0x80) == 0) { - ++i; - break; - } - } - - if (i + 2 >= length) - return 0; - topiclen = buffer[i++] << 8; - topiclen |= buffer[i++]; - - if (i + topiclen >= length) - return 0; - i += topiclen; - - if (mqtt_get_qos(buffer) > 0) { - if (i + 2 >= length) - return 0; - //i += 2; - } else { - return 0; - } - - return (buffer[i] << 8) | buffer[i + 1]; - } - case MQTT_MSG_TYPE_PUBACK: - case MQTT_MSG_TYPE_PUBREC: - case MQTT_MSG_TYPE_PUBREL: - case MQTT_MSG_TYPE_PUBCOMP: - case MQTT_MSG_TYPE_SUBACK: - case MQTT_MSG_TYPE_UNSUBACK: - case MQTT_MSG_TYPE_SUBSCRIBE: - case MQTT_MSG_TYPE_UNSUBSCRIBE: - { - // This requires the remaining length to be encoded in 1 byte, - // which it should be. - if (length >= 4 && (buffer[1] & 0x80) == 0) - return (buffer[2] << 8) | buffer[3]; - else - return 0; - } - - default: - return 0; - } -} - -mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_connect(mqtt_connection_t * connection, mqtt_connect_info_t * info) { - struct mqtt_connect_variable_header *variable_header; - - init_message(connection); - - if (connection->message.length + sizeof(*variable_header) > connection->buffer_length) - return fail_message(connection); - variable_header = (void *)(connection->buffer + connection->message.length); - connection->message.length += sizeof(*variable_header); - - variable_header->lengthMsb = 0; -#if defined(PROTOCOL_NAMEv31) - variable_header->lengthLsb = 6; - os_memcpy(variable_header->magic, "MQIsdp", 6); - variable_header->version = 3; -#elif defined(PROTOCOL_NAMEv311) - variable_header->lengthLsb = 4; - os_memcpy(variable_header->magic, "MQTT", 4); - variable_header->version = 4; -#else -#error "Please define protocol name" -#endif - - variable_header->flags = 0; - variable_header->keepaliveMsb = info->keepalive >> 8; - variable_header->keepaliveLsb = info->keepalive & 0xff; - - if (info->clean_session) - variable_header->flags |= MQTT_CONNECT_FLAG_CLEAN_SESSION; - - if (info->client_id == NULL) { - /* Never allowed */ - return fail_message(connection); - } else if (info->client_id[0] == '\0') { -#ifdef PROTOCOL_NAMEv311 - /* Allowed. Format 0 Length ID */ - append_string(connection, info->client_id, 2); -#else - /* 0 Length not allowed */ - return fail_message(connection); -#endif - } else { - /* No 0 data and at least 1 long. Good to go. */ - if (append_string(connection, info->client_id, os_strlen(info->client_id)) < 0) - return fail_message(connection); - } - - if (info->will_topic != NULL && info->will_topic[0] != '\0') { - if (append_string(connection, info->will_topic, os_strlen(info->will_topic)) < 0) - return fail_message(connection); - - if (append_string(connection, info->will_data, os_strlen(info->will_data)) < 0) - return fail_message(connection); - - variable_header->flags |= MQTT_CONNECT_FLAG_WILL; - if (info->will_retain) - variable_header->flags |= MQTT_CONNECT_FLAG_WILL_RETAIN; - variable_header->flags |= (info->will_qos & 3) << 3; - } - - if (info->username != NULL && info->username[0] != '\0') { - if (append_string(connection, info->username, os_strlen(info->username)) < 0) - return fail_message(connection); - - variable_header->flags |= MQTT_CONNECT_FLAG_USERNAME; - } - - if (info->password != NULL && info->password[0] != '\0') { - if (append_string(connection, info->password, os_strlen(info->password)) < 0) - return fail_message(connection); - - variable_header->flags |= MQTT_CONNECT_FLAG_PASSWORD; - } - - return fini_message(connection, MQTT_MSG_TYPE_CONNECT, 0, 0, 0); -} - -mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_connack(mqtt_connection_t * connection, - enum mqtt_connect_return_code retcode) { - init_message(connection); - connection->buffer[connection->message.length++] = 0; // Connect Acknowledge Flags - connection->buffer[connection->message.length++] = retcode; // Connect Return code - return fini_message(connection, MQTT_MSG_TYPE_CONNACK, 0, 0, 0); -} - -mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_publish(mqtt_connection_t * connection, const char *topic, const char *data, - int data_length, int qos, int retain, uint16_t * message_id) { - init_message(connection); - - if (topic == NULL || topic[0] == '\0') - return fail_message(connection); - - if (append_string(connection, topic, os_strlen(topic)) < 0) - return fail_message(connection); - - if (qos > 0) { - if ((*message_id = append_message_id(connection, 0)) == 0) - return fail_message(connection); - } else - *message_id = 0; - - if (connection->message.length + data_length > connection->buffer_length) - return fail_message(connection); - os_memcpy(connection->buffer + connection->message.length, data, data_length); - connection->message.length += data_length; - - return fini_message(connection, MQTT_MSG_TYPE_PUBLISH, 0, qos, retain); -} - -mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_puback(mqtt_connection_t * connection, uint16_t message_id) { - init_message(connection); - if (append_message_id(connection, message_id) == 0) - return fail_message(connection); - return fini_message(connection, MQTT_MSG_TYPE_PUBACK, 0, 0, 0); -} - -mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_pubrec(mqtt_connection_t * connection, uint16_t message_id) { - init_message(connection); - if (append_message_id(connection, message_id) == 0) - return fail_message(connection); - return fini_message(connection, MQTT_MSG_TYPE_PUBREC, 0, 0, 0); -} - -mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_pubrel(mqtt_connection_t * connection, uint16_t message_id) { - init_message(connection); - if (append_message_id(connection, message_id) == 0) - return fail_message(connection); - return fini_message(connection, MQTT_MSG_TYPE_PUBREL, 0, 1, 0); -} - -mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_pubcomp(mqtt_connection_t * connection, uint16_t message_id) { - init_message(connection); - if (append_message_id(connection, message_id) == 0) - return fail_message(connection); - return fini_message(connection, MQTT_MSG_TYPE_PUBCOMP, 0, 0, 0); -} - -mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_subscribe(mqtt_connection_t * connection, const char *topic, int qos, - uint16_t * message_id) { - init_message(connection); - - if (topic == NULL || topic[0] == '\0') - return fail_message(connection); - - if ((*message_id = append_message_id(connection, 0)) == 0) - return fail_message(connection); - - if (append_string(connection, topic, os_strlen(topic)) < 0) - return fail_message(connection); - - if (connection->message.length + 1 > connection->buffer_length) - return fail_message(connection); - connection->buffer[connection->message.length++] = qos; - - return fini_message(connection, MQTT_MSG_TYPE_SUBSCRIBE, 0, 1, 0); -} - -mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_suback(mqtt_connection_t * connection, uint8_t * ret_codes, - uint8_t ret_codes_len, uint16_t message_id) { - uint8_t i; - - init_message(connection); - - if ((append_message_id(connection, message_id)) == 0) - return fail_message(connection); - - for (i = 0; i < ret_codes_len; i++) - connection->buffer[connection->message.length++] = ret_codes[i]; - - return fini_message(connection, MQTT_MSG_TYPE_SUBACK, 0, 0, 0); -} - -mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_unsubscribe(mqtt_connection_t * connection, const char *topic, - uint16_t * message_id) { - init_message(connection); - - if (topic == NULL || topic[0] == '\0') - return fail_message(connection); - - if ((*message_id = append_message_id(connection, 0)) == 0) - return fail_message(connection); - - if (append_string(connection, topic, os_strlen(topic)) < 0) - return fail_message(connection); - - return fini_message(connection, MQTT_MSG_TYPE_UNSUBSCRIBE, 0, 1, 0); -} - -mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_unsuback(mqtt_connection_t * connection, uint16_t message_id) { - uint8_t i; - - init_message(connection); - - if ((append_message_id(connection, message_id)) == 0) - return fail_message(connection); - - return fini_message(connection, MQTT_MSG_TYPE_UNSUBACK, 0, 0, 0); -} - -mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_pingreq(mqtt_connection_t * connection) { - init_message(connection); - return fini_message(connection, MQTT_MSG_TYPE_PINGREQ, 0, 0, 0); -} - -mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_pingresp(mqtt_connection_t * connection) { - init_message(connection); - return fini_message(connection, MQTT_MSG_TYPE_PINGRESP, 0, 0, 0); -} - -mqtt_message_t *ICACHE_FLASH_ATTR mqtt_msg_disconnect(mqtt_connection_t * connection) { - init_message(connection); - return fini_message(connection, MQTT_MSG_TYPE_DISCONNECT, 0, 0, 0); -} diff --git a/mqtt/mqtt_retainedlist.c b/mqtt/mqtt_retainedlist.c deleted file mode 100644 index fac026c..0000000 --- a/mqtt/mqtt_retainedlist.c +++ /dev/null @@ -1,191 +0,0 @@ -#include "c_types.h" -#include "mem.h" -#include "ets_sys.h" -#include "osapi.h" -#include "os_type.h" - -#include -//#include "user_config.h" - -#include "mqtt_retainedlist.h" -#include "mqtt_topics.h" - -static retained_entry *retained_list = NULL; -static uint16_t max_entry; -static on_retainedtopic_cb retained_cb = NULL; - -bool ICACHE_FLASH_ATTR create_retainedlist(uint16_t num_entires) { - max_entry = num_entires; - retained_list = (retained_entry *) os_zalloc(num_entires * sizeof(retained_entry)); - retained_cb = NULL; - return retained_list != NULL; -} - -bool ICACHE_FLASH_ATTR update_retainedtopic(uint8_t * topic, uint8_t * data, uint16_t data_len, uint8_t qos) { - uint16_t i; - - if (retained_list == NULL) - return false; - - // look for topic in list - for (i = 0; i < max_entry; i++) { - if (retained_list[i].topic != NULL && os_strcmp(retained_list[i].topic, topic) == 0) - break; - } - - // not yet in list - if (i >= max_entry) { - - // if empty new data - no entry required - if (data_len == 0) - return true; - - // find free - for (i = 0; i < max_entry; i++) { - if (retained_list[i].topic == NULL) - break; - } - if (i >= max_entry) { - // list full - return false; - } - retained_list[i].topic = (uint8_t *) os_malloc(os_strlen(topic) + 1); - if (retained_list[i].topic == NULL) { - // out of mem - return false; - } - os_strcpy(retained_list[i].topic, topic); - } - // if empty new data - delete - if (data_len == 0) { - os_free(retained_list[i].topic); - retained_list[i].topic = NULL; - os_free(retained_list[i].data); - retained_list[i].data = NULL; - retained_list[i].data_len = 0; - if (retained_cb != NULL) - retained_cb(NULL); - return true; - } - - if (retained_list[i].data == NULL) { - // no data till now, new memory allocation - retained_list[i].data = (uint8_t *) os_malloc(data_len); - } else { - if (data_len != retained_list[i].data_len) { - // not same size as before, new memory allocation - os_free(retained_list[i].data); - retained_list[i].data = (uint8_t *) os_malloc(data_len); - } - } - if (retained_list[i].data == NULL) { - // out of mem - os_free(retained_list[i].topic); - retained_list[i].topic = NULL; - retained_list[i].data_len = 0; - return false; - } - - os_memcpy(retained_list[i].data, data, data_len); - retained_list[i].data_len = data_len; - retained_list[i].qos = qos; - if (retained_cb != NULL) - retained_cb(&retained_list[i]); - - return true; -} - -bool ICACHE_FLASH_ATTR find_retainedtopic(uint8_t * topic, find_retainedtopic_cb cb, void *user_data) { - uint16_t i; - bool retval = false; - - if (retained_list == NULL) - return false; - - for (i = 0; i < max_entry; i++) { - if (retained_list[i].topic != NULL) { - if (Topics_matches(topic, 1, retained_list[i].topic)) { - (*cb) (&retained_list[i], user_data); - retval = true; - } - } - } - return retval; -} - -void ICACHE_FLASH_ATTR iterate_retainedtopics(iterate_retainedtopic_cb cb, void *user_data) { - uint16_t i; - - if (retained_list == NULL) - return; - - for (i = 0; i < max_entry; i++) { - if (retained_list[i].topic != NULL) { - if ((*cb) (&retained_list[i], user_data) == true) - return; - } - } -} - -bool ICACHE_FLASH_ATTR clear_cb(retained_entry *entry, void *user_data) { - update_retainedtopic(entry->topic, "", 0, entry->qos); - return false; -} - -void ICACHE_FLASH_ATTR clear_retainedtopics() { - iterate_retainedtopics(clear_cb, NULL); -} - -int ICACHE_FLASH_ATTR serialize_retainedtopics(char *buf, int len) { - uint16_t i; - uint16_t pos = 0; - - if (retained_list == NULL) - return 0; - - for (i = 0; i < max_entry; i++) { - if (retained_list[i].topic != NULL) { - uint16_t data_len = retained_list[i].data_len; - if (pos + os_strlen(retained_list[i].topic) + 4 + data_len + 1 >= len-1) - return 0; - os_strcpy(&buf[pos], retained_list[i].topic); - pos += os_strlen(retained_list[i].topic) + 1; - - buf[pos++] = data_len & 0xff; - buf[pos++] = (data_len >> 8) & 0xff; - os_memcpy(&buf[pos], retained_list[i].data, data_len); - pos += data_len; - buf[pos++] = retained_list[i].qos; - buf[pos] = '\0'; - } - } - - if (pos == 0) { - buf[pos++] = '\0'; - } - - return pos; -} - -bool ICACHE_FLASH_ATTR deserialize_retainedtopics(char *buf, int len) { - uint16_t pos = 0; - - while (pos < len && buf[pos] != '\0') { - uint8_t *topic = &buf[pos]; - pos += os_strlen(topic) + 1; - if (pos >= len) return false; - uint16_t data_len = buf[pos++] + (buf[pos++] << 8); - uint8_t *data = &buf[pos]; - pos += data_len; - if (pos >= len) return false; - uint8_t qos = buf[pos++]; - - if (update_retainedtopic(topic, data, data_len, qos) == false) - return false; - } - return true; -} - -void ICACHE_FLASH_ATTR set_on_retainedtopic_cb(on_retainedtopic_cb cb) { - retained_cb = cb; -} diff --git a/mqtt/mqtt_server.c b/mqtt/mqtt_server.c deleted file mode 100644 index 4f3595f..0000000 --- a/mqtt/mqtt_server.c +++ /dev/null @@ -1,946 +0,0 @@ -#include "user_interface.h" -#include "mem.h" - -#include "mqtt_server.h" -#include "mqtt_topics.h" -#include "mqtt_topiclist.h" -#include "mqtt_retainedlist.h" -#include "debug.h" - -/* Mem Debug -#undef os_free -#define os_free(x) {os_printf("F:%d-> %x\r\n", __LINE__,(x));vPortFree(x, "", 0);} - -int my_os_zalloc(int len, int line) { -int _v = pvPortZalloc(len, "", 0); -os_printf("A:%d-> %x (%d)\r\n", line, _v, len); -return _v; -} -#undef os_zalloc -#define os_zalloc(x) my_os_zalloc(x, __LINE__) -#undef os_malloc -#define os_malloc(x) my_os_zalloc(x, __LINE__) -*/ - -#define MAX_SUBS_PER_REQ 16 - -#define MQTT_SERVER_TASK_PRIO 1 -#define MQTT_TASK_QUEUE_SIZE 1 -#define MQTT_SEND_TIMOUT 5 - -os_event_t mqtt_procServerTaskQueue[MQTT_TASK_QUEUE_SIZE]; - -LOCAL uint8_t zero_len_id[2] = { 0, 0 }; - -MQTT_ClientCon *clientcon_list; -LOCAL MqttDataCallback local_data_cb = NULL; -LOCAL MqttConnectCallback local_connect_cb = NULL; -LOCAL MqttAuthCallback local_auth_cb = NULL; - -//#undef MQTT_INFO -//#define MQTT_INFO os_printf -#define MQTT_WARNING os_printf -#define MQTT_ERROR os_printf - -bool ICACHE_FLASH_ATTR print_topic(topic_entry * topic, void *user_data) { - if (topic->clientcon == LOCAL_MQTT_CLIENT) { - MQTT_INFO("MQTT: Client: LOCAL Topic: \"%s\" QoS: %d\r\n", topic->topic, topic->qos); - } else { - MQTT_INFO("MQTT: Client: %s Topic: \"%s\" QoS: %d\r\n", topic->clientcon->connect_info.client_id, topic->topic, - topic->qos); - } - return false; -} - -bool ICACHE_FLASH_ATTR publish_topic(topic_entry * topic_e, uint8_t * topic, uint8_t * data, uint16_t data_len) { - MQTT_ClientCon *clientcon = topic_e->clientcon; - uint16_t message_id = 0; - - if (topic_e->clientcon == LOCAL_MQTT_CLIENT) { - MQTT_INFO("MQTT: Client: LOCAL Topic: \"%s\" QoS: %d\r\n", topic_e->topic, topic_e->qos); - if (local_data_cb != NULL) - local_data_cb(NULL, topic, os_strlen(topic), data, data_len); - return true; - } - - MQTT_INFO("MQTT: Client: %s Topic: \"%s\" QoS: %d\r\n", clientcon->connect_info.client_id, topic_e->topic, - topic_e->qos); - - clientcon->mqtt_state.outbound_message = - mqtt_msg_publish(&clientcon->mqtt_state.mqtt_connection, topic, data, data_len, topic_e->qos, 0, &message_id); - if (QUEUE_Puts - (&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, - clientcon->mqtt_state.outbound_message->length) == -1) { - MQTT_ERROR("MQTT: Queue full\r\n"); - return false; - } - return true; -} - -bool ICACHE_FLASH_ATTR publish_retainedtopic(retained_entry * entry, void* user_data) { - uint16_t message_id = 0; - MQTT_ClientCon *clientcon = (MQTT_ClientCon *)user_data; - - MQTT_INFO("MQTT: Client: %s Topic: \"%s\" QoS: %d\r\n", clientcon->connect_info.client_id, entry->topic, - entry->qos); - - clientcon->mqtt_state.outbound_message = - mqtt_msg_publish(&clientcon->mqtt_state.mqtt_connection, entry->topic, entry->data, entry->data_len, entry->qos, - 1, &message_id); - if (QUEUE_Puts - (&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, - clientcon->mqtt_state.outbound_message->length) == -1) { - MQTT_ERROR("MQTT: Queue full\r\n"); - return false; - } - return true; -} - -bool ICACHE_FLASH_ATTR delete_client_by_id(const uint8_t *id) { - MQTT_ClientCon *clientcon = clientcon_list; - - for (clientcon = clientcon_list; clientcon != NULL; clientcon = clientcon->next) { - if (os_strcmp(id, clientcon->connect_info.client_id) == 0) { - MQTT_INFO("MQTT: Disconnect client: %s\r\n", clientcon->connect_info.client_id); - clientcon->connState = TCP_DISCONNECT; - system_os_post(MQTT_SERVER_TASK_PRIO, 0, (os_param_t) clientcon); - return true; - } - } - return true; -} - -bool ICACHE_FLASH_ATTR activate_next_client() { - MQTT_ClientCon *clientcon = clientcon_list; - - for (clientcon = clientcon_list; clientcon != NULL; clientcon = clientcon->next) { - if ((!QUEUE_IsEmpty(&clientcon->msgQueue)) && clientcon->pCon->state != ESPCONN_CLOSE) { - MQTT_INFO("MQTT: Next message to client: %s\r\n", clientcon->connect_info.client_id); - system_os_post(MQTT_SERVER_TASK_PRIO, 0, (os_param_t) clientcon); - return true; - } - } - return true; -} - -static uint8_t shared_out_buffer[MQTT_BUF_SIZE]; - -bool ICACHE_FLASH_ATTR MQTT_server_initClientCon(MQTT_ClientCon * mqttClientCon) { - uint32_t temp; - MQTT_INFO("MQTT:InitClientCon\r\n"); - - mqttClientCon->connState = TCP_CONNECTED; - - os_memset(&mqttClientCon->connect_info, 0, sizeof(mqtt_connect_info_t)); - - mqttClientCon->connect_info.client_id = zero_len_id; - mqttClientCon->protocolVersion = 0; - - mqttClientCon->mqtt_state.in_buffer = (uint8_t *) os_zalloc(MQTT_BUF_SIZE); - mqttClientCon->mqtt_state.in_buffer_length = MQTT_BUF_SIZE; - // mqttClientCon->mqtt_state.out_buffer = (uint8_t *) os_zalloc(MQTT_BUF_SIZE); - mqttClientCon->mqtt_state.out_buffer = shared_out_buffer; - mqttClientCon->mqtt_state.out_buffer_length = MQTT_BUF_SIZE; - mqttClientCon->mqtt_state.connect_info = &mqttClientCon->connect_info; - - mqtt_msg_init(&mqttClientCon->mqtt_state.mqtt_connection, mqttClientCon->mqtt_state.out_buffer, - mqttClientCon->mqtt_state.out_buffer_length); - - QUEUE_Init(&mqttClientCon->msgQueue, QUEUE_BUFFER_SIZE); - - mqttClientCon->next = clientcon_list; - clientcon_list = mqttClientCon; - - return true; -} - -uint16_t ICACHE_FLASH_ATTR MQTT_server_countClientCon() { - MQTT_ClientCon *p; - uint16_t count = 0; - for (p = clientcon_list; p != NULL; p = p->next, count++); - return count; -} - -bool ICACHE_FLASH_ATTR MQTT_server_deleteClientCon(MQTT_ClientCon * mqttClientCon) { - MQTT_INFO("MQTT:DeleteClientCon\r\n"); - - if (mqttClientCon == NULL) - return; - - os_timer_disarm(&mqttClientCon->mqttTimer); - - if (mqttClientCon->pCon != NULL) { - espconn_delete(mqttClientCon->pCon); - } - - MQTT_ClientCon **p = &clientcon_list; - while (*p != mqttClientCon && *p != NULL) { - p = &((*p)->next); - } - if (*p == mqttClientCon) - *p = (*p)->next; - - if (mqttClientCon->user_data != NULL) { - os_free(mqttClientCon->user_data); - mqttClientCon->user_data = NULL; - } - - if (mqttClientCon->mqtt_state.in_buffer != NULL) { - os_free(mqttClientCon->mqtt_state.in_buffer); - mqttClientCon->mqtt_state.in_buffer = NULL; - } - -/* We use one static buffer for all connections -// if (mqttClientCon->mqtt_state.out_buffer != NULL) { -// os_free(mqttClientCon->mqtt_state.out_buffer); -// mqttClientCon->mqtt_state.out_buffer = NULL; -// } - - if (mqttClientCon->mqtt_state.outbound_message != NULL) { - if (mqttClientCon->mqtt_state.outbound_message->data != NULL) { - // Don't think, this is has ever been allocated separately - // os_free(mqttClientCon->mqtt_state.outbound_message->data); - mqttClientCon->mqtt_state.outbound_message->data = NULL; - } - } -*/ - if (mqttClientCon->mqtt_state.mqtt_connection.buffer != NULL) { - // Already freed but not NULL - mqttClientCon->mqtt_state.mqtt_connection.buffer = NULL; - } - - if (mqttClientCon->connect_info.client_id != NULL) { - /* Don't attempt to free if it's the zero_len array */ - if (((uint8_t *) mqttClientCon->connect_info.client_id) != zero_len_id) - os_free(mqttClientCon->connect_info.client_id); - mqttClientCon->connect_info.client_id = NULL; - } - - if (mqttClientCon->connect_info.username != NULL) { - os_free(mqttClientCon->connect_info.username); - mqttClientCon->connect_info.username = NULL; - } - - if (mqttClientCon->connect_info.password != NULL) { - os_free(mqttClientCon->connect_info.password); - mqttClientCon->connect_info.password = NULL; - } - - if (mqttClientCon->connect_info.will_topic != NULL) { - // Publish the LWT - find_topic(mqttClientCon->connect_info.will_topic, publish_topic, - mqttClientCon->connect_info.will_data, mqttClientCon->connect_info.will_data_len); - activate_next_client(); - - if (mqttClientCon->connect_info.will_retain) { - update_retainedtopic(mqttClientCon->connect_info.will_topic, mqttClientCon->connect_info.will_data, - mqttClientCon->connect_info.will_data_len, mqttClientCon->connect_info.will_qos); - } - - os_free(mqttClientCon->connect_info.will_topic); - mqttClientCon->connect_info.will_topic = NULL; - } - - if (mqttClientCon->connect_info.will_data != NULL) { - os_free(mqttClientCon->connect_info.will_data); - mqttClientCon->connect_info.will_data = NULL; - } - - if (mqttClientCon->msgQueue.buf != NULL) { - os_free(mqttClientCon->msgQueue.buf); - mqttClientCon->msgQueue.buf = NULL; - } - - delete_topic(mqttClientCon, 0); - - os_free(mqttClientCon); - - return true; -} - -void ICACHE_FLASH_ATTR MQTT_server_cleanupClientCons() { - MQTT_ClientCon *clientcon, *clientcon_tmp; - for (clientcon = clientcon_list; clientcon != NULL; ) { - clientcon_tmp = clientcon; - clientcon = clientcon->next; - if (clientcon_tmp->pCon->state == ESPCONN_CLOSE) { - MQTT_server_deleteClientCon(clientcon_tmp); - } - } -} - -void ICACHE_FLASH_ATTR MQTT_server_disconnectClientCon(MQTT_ClientCon * mqttClientCon) { - MQTT_INFO("MQTT:ServerDisconnect\r\n"); - - mqttClientCon->mqtt_state.message_length_read = 0; - mqttClientCon->connState = TCP_DISCONNECT; - system_os_post(MQTT_SERVER_TASK_PRIO, 0, (os_param_t) mqttClientCon); - os_timer_disarm(&mqttClientCon->mqttTimer); -} - -void ICACHE_FLASH_ATTR mqtt_server_timer(void *arg) { - MQTT_ClientCon *clientcon = (MQTT_ClientCon *) arg; - - if (clientcon->sendTimeout > 0) - clientcon->sendTimeout--; -} - -static void ICACHE_FLASH_ATTR MQTT_ClientCon_recv_cb(void *arg, char *pdata, unsigned short len) { - uint8_t msg_type; - uint8_t msg_qos; - uint16_t msg_id; - enum mqtt_connect_flag msg_conn_ret; - uint16_t topic_index; - uint16_t topic_len; - uint8_t *topic_str; - uint8_t topic_buffer[MQTT_BUF_SIZE]; - uint16_t data_len; - uint8_t *data; - - struct espconn *pCon = (struct espconn *)arg; - - MQTT_INFO("MQTT_ClientCon_recv_cb(): %d bytes of data received\n", len); - - MQTT_ClientCon *clientcon = (MQTT_ClientCon *) pCon->reverse; - if (clientcon == NULL) { - MQTT_ERROR("ERROR: No client status\r\n"); - return; - } - - MQTT_INFO("MQTT: TCP: data received %d bytes (State: %d)\r\n", len, clientcon->connState); - - // Expect minimum the full fixed size header - if (len + clientcon->mqtt_state.message_length_read > MQTT_BUF_SIZE || len < 2) { - MQTT_ERROR("MQTT: Message too short/long\r\n"); - clientcon->mqtt_state.message_length_read = 0; - return; - } - READPACKET: - os_memcpy(&clientcon->mqtt_state.in_buffer[clientcon->mqtt_state.message_length_read], pdata, len); - clientcon->mqtt_state.message_length_read += len; - - clientcon->mqtt_state.message_length = - mqtt_get_total_length(clientcon->mqtt_state.in_buffer, clientcon->mqtt_state.message_length_read); - MQTT_INFO("MQTT: total_len: %d\r\n", clientcon->mqtt_state.message_length); - if (clientcon->mqtt_state.message_length > clientcon->mqtt_state.message_length_read) { - MQTT_WARNING("MQTT: Partial message received\r\n"); - return; - } - - msg_type = mqtt_get_type(clientcon->mqtt_state.in_buffer); - MQTT_INFO("MQTT: message_type: %d\r\n", msg_type); - //msg_qos = mqtt_get_qos(clientcon->mqtt_state.in_buffer); - switch (clientcon->connState) { - case TCP_CONNECTED: - switch (msg_type) { - case MQTT_MSG_TYPE_CONNECT: - - MQTT_INFO("MQTT: Connect received, message_len: %d\r\n", clientcon->mqtt_state.message_length); - - if (clientcon->mqtt_state.message_length < sizeof(struct mqtt_connect_variable_header) + 3) { - MQTT_ERROR("MQTT: Too short Connect message\r\n"); - MQTT_server_disconnectClientCon(clientcon); - return; - } - - struct mqtt_connect_variable_header4 *variable_header = - (struct mqtt_connect_variable_header4 *)&clientcon->mqtt_state.in_buffer[2]; - uint16_t var_header_len = sizeof(struct mqtt_connect_variable_header4); - - // We check MQTT v3.11 (version 4) - if ((variable_header->lengthMsb << 8) + variable_header->lengthLsb == 4 && - variable_header->version == 4 && os_strncmp(variable_header->magic, "MQTT", 4) == 0) { - clientcon->protocolVersion = 4; - } else { - struct mqtt_connect_variable_header3 *variable_header3 = - (struct mqtt_connect_variable_header3 *)&clientcon->mqtt_state.in_buffer[2]; - var_header_len = sizeof(struct mqtt_connect_variable_header3); - - // We check MQTT v3.1 (version 3) - if ((variable_header3->lengthMsb << 8) + variable_header3->lengthLsb == 6 && - variable_header3->version == 3 && os_strncmp(variable_header3->magic, "MQIsdp", 6) == 0) { - clientcon->protocolVersion = 3; - // adapt the remaining header fields (dirty as we overlay the two structs of different length) - variable_header->version = variable_header3->version; - variable_header->flags = variable_header3->flags; - variable_header->keepaliveMsb = variable_header3->keepaliveMsb; - variable_header->keepaliveLsb = variable_header3->keepaliveLsb; - } else { - // Neither found - MQTT_WARNING("MQTT: Wrong protocoll version\r\n"); - msg_conn_ret = CONNECTION_REFUSE_PROTOCOL; - clientcon->connState = TCP_DISCONNECTING; - break; - } - } - - uint16_t msg_used_len = var_header_len; - - MQTT_INFO("MQTT: Connect flags %x\r\n", variable_header->flags); - clientcon->connect_info.clean_session = (variable_header->flags & MQTT_CONNECT_FLAG_CLEAN_SESSION) != 0; - - clientcon->connect_info.keepalive = (variable_header->keepaliveMsb << 8) + variable_header->keepaliveLsb; - espconn_regist_time(clientcon->pCon, 2 * clientcon->connect_info.keepalive, 1); - MQTT_INFO("MQTT: Keepalive %d\r\n", clientcon->connect_info.keepalive); - - // Get the client id - uint16_t id_len = clientcon->mqtt_state.message_length - (2 + msg_used_len); - const char *client_id = mqtt_get_str(&clientcon->mqtt_state.in_buffer[2 + msg_used_len], &id_len); - if (client_id == NULL || id_len > 80) { - MQTT_WARNING("MQTT: Client Id invalid\r\n"); - msg_conn_ret = CONNECTION_REFUSE_ID_REJECTED; - clientcon->connState = TCP_DISCONNECTING; - break; - } - if (id_len == 0) { - if (clientcon->protocolVersion == 3) { - MQTT_WARNING("MQTT: Empty client Id in MQTT 3.1\r\n"); - msg_conn_ret = CONNECTION_REFUSE_ID_REJECTED; - clientcon->connState = TCP_DISCONNECTING; - break; - } - if (!clientcon->connect_info.clean_session) { - MQTT_WARNING("MQTT: Null client Id and NOT cleansession\r\n"); - msg_conn_ret = CONNECTION_REFUSE_ID_REJECTED; - clientcon->connState = TCP_DISCONNECTING; - break; - } - clientcon->connect_info.client_id = zero_len_id; - } else { - uint8_t *new_id = (char *)os_zalloc(id_len + 1); - if (new_id == NULL) { - MQTT_ERROR("MQTT: Out of mem\r\n"); - msg_conn_ret = CONNECTION_REFUSE_SERVER_UNAVAILABLE; - clientcon->connState = TCP_DISCONNECTING; - break; - } - os_memcpy(new_id, client_id, id_len); - new_id[id_len] = '\0'; - - // Delete any existing status for that id - delete_client_by_id(client_id); - - clientcon->connect_info.client_id = new_id; - MQTT_INFO("MQTT: Client id %s\r\n", clientcon->connect_info.client_id); - } - msg_used_len += 2 + id_len; - - // Get the LWT - clientcon->connect_info.will_retain = (variable_header->flags & MQTT_CONNECT_FLAG_WILL_RETAIN) != 0; - clientcon->connect_info.will_qos = (variable_header->flags & 0x18) >> 3; - if (!(variable_header->flags & MQTT_CONNECT_FLAG_WILL)) { - // Must be all 0 if no lwt is given - if (clientcon->connect_info.will_retain || clientcon->connect_info.will_qos) { - MQTT_WARNING("MQTT: Last Will flags invalid\r\n"); - MQTT_server_disconnectClientCon(clientcon); - return; - } - } else { - uint16_t lw_topic_len = clientcon->mqtt_state.message_length - (2 + msg_used_len); - const char *lw_topic = - mqtt_get_str(&clientcon->mqtt_state.in_buffer[2 + msg_used_len], &lw_topic_len); - - if (lw_topic == NULL) { - MQTT_WARNING("MQTT: Last Will topic invalid\r\n"); - MQTT_server_disconnectClientCon(clientcon); - return; - } - - clientcon->connect_info.will_topic = (char *)os_zalloc(lw_topic_len + 1); - if (clientcon->connect_info.will_topic != NULL) { - os_memcpy(clientcon->connect_info.will_topic, lw_topic, lw_topic_len); - clientcon->connect_info.will_topic[lw_topic_len] = 0; - if (Topics_hasWildcards(clientcon->connect_info.will_topic)) { - MQTT_WARNING("MQTT: Last Will topic has wildcards\r\n"); - MQTT_server_disconnectClientCon(clientcon); - return; - } - MQTT_INFO("MQTT: LWT topic %s\r\n", clientcon->connect_info.will_topic); - } else { - MQTT_ERROR("MQTT: Out of mem\r\n"); - msg_conn_ret = CONNECTION_REFUSE_SERVER_UNAVAILABLE; - clientcon->connState = TCP_DISCONNECTING; - break; - } - msg_used_len += 2 + lw_topic_len; - - uint16_t lw_data_len = - clientcon->mqtt_state.message_length - (2 + msg_used_len); - const char *lw_data = - mqtt_get_str(&clientcon->mqtt_state.in_buffer[2 + msg_used_len], - &lw_data_len); - - if (lw_data == NULL) { - MQTT_WARNING("MQTT: Last Will data invalid\r\n"); - MQTT_server_disconnectClientCon(clientcon); - return; - } - - clientcon->connect_info.will_data = (char *)os_zalloc(lw_data_len); - clientcon->connect_info.will_data_len = lw_data_len; - if (clientcon->connect_info.will_data != NULL) { - os_memcpy(clientcon->connect_info.will_data, lw_data, lw_data_len); - MQTT_INFO("MQTT: %d bytes of LWT data\r\n", clientcon->connect_info.will_data_len); - } else { - MQTT_ERROR("MQTT: Out of mem\r\n"); - msg_conn_ret = CONNECTION_REFUSE_SERVER_UNAVAILABLE; - clientcon->connState = TCP_DISCONNECTING; - break; - } - msg_used_len += 2 + lw_data_len; - } - - // Get the username - if ((variable_header->flags & MQTT_CONNECT_FLAG_USERNAME) != 0) { - uint16_t username_len = clientcon->mqtt_state.message_length - (2 + msg_used_len); - const char *username = - mqtt_get_str(&clientcon->mqtt_state.in_buffer[2 + msg_used_len], &username_len); - - if (username == NULL) { - MQTT_WARNING("MQTT: Username invalid\r\n"); - MQTT_server_disconnectClientCon(clientcon); - return; - } - - clientcon->connect_info.username = (char *)os_zalloc(username_len+1); - if (clientcon->connect_info.username != NULL) { - os_memcpy(clientcon->connect_info.username, username, username_len); - clientcon->connect_info.username[username_len] = '\0'; - MQTT_INFO("MQTT: Username %s\r\n", clientcon->connect_info.username); - } else { - MQTT_ERROR("MQTT: Out of mem\r\n"); - msg_conn_ret = CONNECTION_REFUSE_SERVER_UNAVAILABLE; - clientcon->connState = TCP_DISCONNECTING; - break; - } - msg_used_len += 2 + username_len; - } - - // Get the password - if ((variable_header->flags & MQTT_CONNECT_FLAG_PASSWORD) != 0) { - - if ((variable_header->flags & MQTT_CONNECT_FLAG_USERNAME) == 0) { - MQTT_WARNING("MQTT: Password without username\r\n"); - MQTT_server_disconnectClientCon(clientcon); - return; - } - - uint16_t password_len = clientcon->mqtt_state.message_length - (2 + msg_used_len); - const char *password = - mqtt_get_str(&clientcon->mqtt_state.in_buffer[2 + msg_used_len], &password_len); - - clientcon->connect_info.password = (char *)os_zalloc(password_len+1); - if (clientcon->connect_info.password != NULL) { - os_memcpy(clientcon->connect_info.password, password, password_len); - clientcon->connect_info.password[password_len] = '\0'; - MQTT_INFO("MQTT: Password %s\r\n", clientcon->connect_info.password); - } else { - MQTT_ERROR("MQTT: Out of mem\r\n"); - msg_conn_ret = CONNECTION_REFUSE_SERVER_UNAVAILABLE; - clientcon->connState = TCP_DISCONNECTING; - break; - } - msg_used_len += 2 + password_len; - } - - // Check Auth - if ((local_auth_cb != NULL) && - local_auth_cb(clientcon->connect_info.username==NULL?"":clientcon->connect_info.username, - clientcon->connect_info.password==NULL?"":clientcon->connect_info.password, - clientcon->pCon) == false) { - MQTT_WARNING("MQTT: Authorization failed\r\n"); - - if (clientcon->connect_info.will_topic != NULL) { - os_free(clientcon->connect_info.will_topic); - clientcon->connect_info.will_topic = NULL; - } - msg_conn_ret = CONNECTION_REFUSE_NOT_AUTHORIZED; - clientcon->connState = TCP_DISCONNECTING; - break; - } - - msg_conn_ret = CONNECTION_ACCEPTED; - clientcon->connState = MQTT_DATA; - break; - - default: - MQTT_WARNING("MQTT: Invalid message\r\n"); - MQTT_server_disconnectClientCon(clientcon); - return; - } - clientcon->mqtt_state.outbound_message = mqtt_msg_connack(&clientcon->mqtt_state.mqtt_connection, msg_conn_ret); - if (QUEUE_Puts - (&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, - clientcon->mqtt_state.outbound_message->length) == -1) { - MQTT_ERROR("MQTT: Queue full\r\n"); - } - - break; - - case MQTT_DATA: - switch (msg_type) { - uint8_t ret_codes[MAX_SUBS_PER_REQ]; - uint8_t num_subs; - - case MQTT_MSG_TYPE_SUBSCRIBE: - MQTT_INFO("MQTT: Subscribe received, message_len: %d\r\n", clientcon->mqtt_state.message_length); - // 2B fixed header + 2B variable header + 2 len + 1 char + 1 QoS - if (clientcon->mqtt_state.message_length < 8) { - MQTT_ERROR("MQTT: Too short Subscribe message\r\n"); - MQTT_server_disconnectClientCon(clientcon); - return; - } - msg_id = mqtt_get_id(clientcon->mqtt_state.in_buffer, clientcon->mqtt_state.in_buffer_length); - MQTT_INFO("MQTT: Message id %d\r\n", msg_id); - topic_index = 4; - num_subs = 0; - while (topic_index < clientcon->mqtt_state.message_length && num_subs < MAX_SUBS_PER_REQ) { - topic_len = clientcon->mqtt_state.message_length - topic_index; - topic_str = mqtt_get_str(&clientcon->mqtt_state.in_buffer[topic_index], &topic_len); - if (topic_str == NULL) { - MQTT_WARNING("MQTT: Subscribe topic invalid\r\n"); - MQTT_server_disconnectClientCon(clientcon); - return; - } - topic_index += 2 + topic_len; - - if (topic_index >= clientcon->mqtt_state.message_length) { - MQTT_WARNING("MQTT: Subscribe QoS missing\r\n"); - MQTT_server_disconnectClientCon(clientcon); - return; - } - uint8_t topic_QoS = clientcon->mqtt_state.in_buffer[topic_index++]; - - os_memcpy(topic_buffer, topic_str, topic_len); - topic_buffer[topic_len] = 0; - MQTT_INFO("MQTT: Subscribed topic %s QoS %d\r\n", topic_buffer, topic_QoS); - - // the return codes, one per topic - // For now we always give back error or QoS = 0 !! - ret_codes[num_subs++] = add_topic(clientcon, topic_buffer, 0) ? 0 : 0x80; - //iterate_topics(print_topic, 0); - } - MQTT_INFO("MQTT: Subscribe successful\r\n"); - - clientcon->mqtt_state.outbound_message = - mqtt_msg_suback(&clientcon->mqtt_state.mqtt_connection, ret_codes, num_subs, msg_id); - if (QUEUE_Puts - (&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, - clientcon->mqtt_state.outbound_message->length) == -1) { - MQTT_ERROR("MQTT: Queue full\r\n"); - } - - find_retainedtopic(topic_buffer, publish_retainedtopic, clientcon); - - break; - - case MQTT_MSG_TYPE_UNSUBSCRIBE: - MQTT_INFO("MQTT: Unsubscribe received, message_len: %d\r\n", clientcon->mqtt_state.message_length); - // 2B fixed header + 2B variable header + 2 len + 1 char - if (clientcon->mqtt_state.message_length < 7) { - MQTT_ERROR("MQTT: Too short Unsubscribe message\r\n"); - MQTT_server_disconnectClientCon(clientcon); - return; - } - msg_id = mqtt_get_id(clientcon->mqtt_state.in_buffer, clientcon->mqtt_state.in_buffer_length); - MQTT_INFO("MQTT: Message id %d\r\n", msg_id); - topic_index = 4; - while (topic_index < clientcon->mqtt_state.message_length) { - uint16_t topic_len = clientcon->mqtt_state.message_length - topic_index; - char *topic_str = mqtt_get_str(&clientcon->mqtt_state.in_buffer[topic_index], &topic_len); - if (topic_str == NULL) { - MQTT_WARNING("MQTT: Subscribe topic invalid\r\n"); - MQTT_server_disconnectClientCon(clientcon); - return; - } - topic_index += 2 + topic_len; - - os_memcpy(topic_buffer, topic_str, topic_len); - topic_buffer[topic_len] = 0; - MQTT_INFO("MQTT: Unsubscribed topic %s\r\n", topic_buffer); - - delete_topic(clientcon, topic_buffer); - //iterate_topics(print_topic, 0); - } - MQTT_INFO("MQTT: Unubscribe successful\r\n"); - - clientcon->mqtt_state.outbound_message = mqtt_msg_unsuback(&clientcon->mqtt_state.mqtt_connection, msg_id); - if (QUEUE_Puts - (&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, - clientcon->mqtt_state.outbound_message->length) == -1) { - MQTT_ERROR("MQTT: Queue full\r\n"); - } - break; - - case MQTT_MSG_TYPE_PUBLISH: - MQTT_INFO("MQTT: Publish received, message_len: %d\r\n", clientcon->mqtt_state.message_length); - -/* if (msg_qos == 1) - clientcon->mqtt_state.outbound_message = mqtt_msg_puback(&clientcon->mqtt_state.mqtt_connection, msg_id); - else if (msg_qos == 2) - clientcon->mqtt_state.outbound_message = mqtt_msg_pubrec(&clientcon->mqtt_state.mqtt_connection, msg_id); - if (msg_qos == 1 || msg_qos == 2) { - MQTT_INFO("MQTT: Queue response QoS: %d\r\n", msg_qos); - if (QUEUE_Puts(&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, clientcon->mqtt_state.outbound_message->length) == -1) { - MQTT_ERROR("MQTT: Queue full\r\n"); - } - } -*/ - topic_len = clientcon->mqtt_state.in_buffer_length; - topic_str = (uint8_t *) mqtt_get_publish_topic(clientcon->mqtt_state.in_buffer, &topic_len); - os_memcpy(topic_buffer, topic_str, topic_len); - topic_buffer[topic_len] = 0; - data_len = clientcon->mqtt_state.in_buffer_length; - data = (uint8_t *) mqtt_get_publish_data(clientcon->mqtt_state.in_buffer, &data_len); - - MQTT_INFO("MQTT: Published topic \"%s\"\r\n", topic_buffer); - MQTT_INFO("MQTT: Matches to:\r\n"); - - // Now find, if anything matches and enque publish message - find_topic(topic_buffer, publish_topic, data, data_len); - - if (mqtt_get_retain(clientcon->mqtt_state.in_buffer)) { - update_retainedtopic(topic_buffer, data, data_len, mqtt_get_qos(clientcon->mqtt_state.in_buffer)); - } - - break; - - case MQTT_MSG_TYPE_PINGREQ: - MQTT_INFO("MQTT: receive MQTT_MSG_TYPE_PINGREQ\r\n"); - clientcon->mqtt_state.outbound_message = mqtt_msg_pingresp(&clientcon->mqtt_state.mqtt_connection); - if (QUEUE_Puts - (&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, - clientcon->mqtt_state.outbound_message->length) == -1) { - MQTT_ERROR("MQTT: Queue full\r\n"); - } - break; - - case MQTT_MSG_TYPE_DISCONNECT: - MQTT_INFO("MQTT: receive MQTT_MSG_TYPE_DISCONNECT\r\n"); - - // Clean session close: no LWT - if (clientcon->connect_info.will_topic != NULL) { - os_free(clientcon->connect_info.will_topic); - clientcon->connect_info.will_topic = NULL; - } - MQTT_server_disconnectClientCon(clientcon); - return; - -/* - case MQTT_MSG_TYPE_PUBACK: - if (clientcon->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBLISH && clientcon->mqtt_state.pending_msg_id == msg_id) { - MQTT_INFO("MQTT: received MQTT_MSG_TYPE_PUBACK, finish QoS1 publish\r\n"); - } - - break; - case MQTT_MSG_TYPE_PUBREC: - clientcon->mqtt_state.outbound_message = mqtt_msg_pubrel(&clientcon->mqtt_state.mqtt_connection, msg_id); - if (QUEUE_Puts(&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, clientcon->mqtt_state.outbound_message->length) == -1) { - MQTT_ERROR("MQTT: Queue full\r\n"); - } - break; - case MQTT_MSG_TYPE_PUBREL: - clientcon->mqtt_state.outbound_message = mqtt_msg_pubcomp(&clientcon->mqtt_state.mqtt_connection, msg_id); - if (QUEUE_Puts(&clientcon->msgQueue, clientcon->mqtt_state.outbound_message->data, clientcon->mqtt_state.outbound_message->length) == -1) { - MQTT_ERROR("MQTT: Queue full\r\n"); - } - break; - case MQTT_MSG_TYPE_PUBCOMP: - if (clientcon->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBLISH && clientcon->mqtt_state.pending_msg_id == msg_id) { - MQTT_INFO("MQTT: receive MQTT_MSG_TYPE_PUBCOMP, finish QoS2 publish\r\n"); - } - break; - case MQTT_MSG_TYPE_PINGRESP: - // Ignore - break; -*/ - - default: - // Ignore - break; - } - break; - } - - // More than one MQTT command in the packet? - len = clientcon->mqtt_state.message_length_read; - if (clientcon->mqtt_state.message_length < len) { - len -= clientcon->mqtt_state.message_length; - pdata += clientcon->mqtt_state.message_length; - clientcon->mqtt_state.message_length_read = 0; - - MQTT_INFO("MQTT: Get another received message\r\n"); - goto READPACKET; - } - clientcon->mqtt_state.message_length_read = 0; - - if (msg_type != MQTT_MSG_TYPE_PUBLISH) { - system_os_post(MQTT_SERVER_TASK_PRIO, 0, (os_param_t) clientcon); - } else { - activate_next_client(); - } -} - -/* Called when a client has disconnected from the MQTT server */ -static void ICACHE_FLASH_ATTR MQTT_ClientCon_discon_cb(void *arg) { - struct espconn *pCon = (struct espconn *)arg; - MQTT_ClientCon *clientcon = (MQTT_ClientCon *) pCon->reverse; - - MQTT_INFO("MQTT_ClientCon_discon_cb(): client disconnected\n"); - MQTT_server_deleteClientCon(clientcon); -} - -static void ICACHE_FLASH_ATTR MQTT_ClientCon_sent_cb(void *arg) { - struct espconn *pCon = (struct espconn *)arg; - MQTT_ClientCon *clientcon = (MQTT_ClientCon *) pCon->reverse; - - MQTT_INFO("MQTT_ClientCon_sent_cb(): Data sent\n"); - - clientcon->sendTimeout = 0; - - if (clientcon->connState == TCP_DISCONNECTING) { - clientcon->connState = TCP_DISCONNECT; - system_os_post(MQTT_SERVER_TASK_PRIO, 0, (os_param_t) clientcon); - } - - activate_next_client(); -} - -/* Called when a client connects to the MQTT server */ -static void ICACHE_FLASH_ATTR MQTT_ClientCon_connected_cb(void *arg) { - struct espconn *pespconn = (struct espconn *)arg; - MQTT_ClientCon *mqttClientCon; - pespconn->reverse = NULL; - - MQTT_INFO("MQTT_ClientCon_connected_cb(): Client connected\r\n"); - - espconn_regist_sentcb(pespconn, MQTT_ClientCon_sent_cb); - espconn_regist_disconcb(pespconn, MQTT_ClientCon_discon_cb); - espconn_regist_recvcb(pespconn, MQTT_ClientCon_recv_cb); - espconn_regist_time(pespconn, 30, 1); - - mqttClientCon = (MQTT_ClientCon *) os_zalloc(sizeof(MQTT_ClientCon)); - pespconn->reverse = mqttClientCon; - if (mqttClientCon == NULL) { - MQTT_ERROR("ERROR: Cannot allocate client status\r\n"); - return; - } - - mqttClientCon->pCon = pespconn; - - bool no_mem = (system_get_free_heap_size() < (MQTT_BUF_SIZE + QUEUE_BUFFER_SIZE + 0x400)); - if (no_mem) { - MQTT_ERROR("ERROR: No mem for new client connection\r\n"); - } - - if (no_mem || (local_connect_cb != NULL && local_connect_cb(pespconn, MQTT_server_countClientCon()+1) == false)) { - mqttClientCon->connState = TCP_DISCONNECT; - system_os_post(MQTT_SERVER_TASK_PRIO, 0, (os_param_t) mqttClientCon); - return; - } - - MQTT_server_initClientCon(mqttClientCon); - - os_timer_setfn(&mqttClientCon->mqttTimer, (os_timer_func_t *) mqtt_server_timer, mqttClientCon); - os_timer_arm(&mqttClientCon->mqttTimer, 1000, 1); -} - -void ICACHE_FLASH_ATTR MQTT_ServerTask(os_event_t * e) { - MQTT_ClientCon *clientcon = (MQTT_ClientCon *) e->par; - uint8_t dataBuffer[MQTT_BUF_SIZE]; - uint16_t dataLen; - if (e->par == 0) - return; - - MQTT_INFO("MQTT: Server task activated - state %d\r\n", clientcon->connState); - - switch (clientcon->connState) { - - case TCP_DISCONNECT: - MQTT_INFO("MQTT: Disconnect\r\n"); - espconn_disconnect(clientcon->pCon); - break; - case TCP_DISCONNECTING: - case MQTT_DATA: - if (!QUEUE_IsEmpty(&clientcon->msgQueue) && clientcon->sendTimeout == 0 && - QUEUE_Gets(&clientcon->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == 0) { - - clientcon->mqtt_state.pending_msg_type = mqtt_get_type(dataBuffer); - clientcon->mqtt_state.pending_msg_id = mqtt_get_id(dataBuffer, dataLen); - - clientcon->sendTimeout = MQTT_SEND_TIMOUT; - MQTT_INFO("MQTT: Sending, type: %d, id: %04X\r\n", clientcon->mqtt_state.pending_msg_type, - clientcon->mqtt_state.pending_msg_id); - espconn_send(clientcon->pCon, dataBuffer, dataLen); - - clientcon->mqtt_state.outbound_message = NULL; - break; - } - if (clientcon->connState == TCP_DISCONNECTING) { - MQTT_server_disconnectClientCon(clientcon); - } - break; - } -} - -bool ICACHE_FLASH_ATTR MQTT_server_start(uint16_t portno, uint16_t max_subscriptions, uint16_t max_retained_topics) { - MQTT_INFO("Starting MQTT server on port %d\r\n", portno); - - if (!create_topiclist(max_subscriptions)) - return false; - if (!create_retainedlist(max_retained_topics)) - return false; - clientcon_list = NULL; - - struct espconn *pCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); - if (pCon == NULL) - return false; - - /* Equivalent to bind */ - pCon->type = ESPCONN_TCP; - pCon->state = ESPCONN_NONE; - pCon->proto.tcp = (esp_tcp *) os_zalloc(sizeof(esp_tcp)); - if (pCon->proto.tcp == NULL) { - os_free(pCon); - return false; - } - pCon->proto.tcp->local_port = portno; - - /* Register callback when clients connect to the server */ - espconn_regist_connectcb(pCon, MQTT_ClientCon_connected_cb); - - /* Put the connection in accept mode */ - espconn_accept(pCon); - - system_os_task(MQTT_ServerTask, MQTT_SERVER_TASK_PRIO, mqtt_procServerTaskQueue, MQTT_TASK_QUEUE_SIZE); - return true; -} - -bool ICACHE_FLASH_ATTR MQTT_local_publish(uint8_t * topic, uint8_t * data, uint16_t data_length, uint8_t qos, - uint8_t retain) { - find_topic(topic, publish_topic, data, data_length); - if (retain) - update_retainedtopic(topic, data, data_length, qos); - activate_next_client(); - return true; -} - -bool ICACHE_FLASH_ATTR MQTT_local_subscribe(uint8_t * topic, uint8_t qos) { - return add_topic(LOCAL_MQTT_CLIENT, topic, 0); -} - -bool ICACHE_FLASH_ATTR MQTT_local_unsubscribe(uint8_t * topic) { - return delete_topic(LOCAL_MQTT_CLIENT, topic); -} - -void ICACHE_FLASH_ATTR MQTT_server_onData(MqttDataCallback dataCb) { - local_data_cb = dataCb; -} - -void ICACHE_FLASH_ATTR MQTT_server_onConnect(MqttConnectCallback connectCb) { - local_connect_cb = connectCb; -} - -void ICACHE_FLASH_ATTR MQTT_server_onAuth(MqttAuthCallback authCb) { - local_auth_cb = authCb; -} diff --git a/mqtt/mqtt_topiclist.c b/mqtt/mqtt_topiclist.c deleted file mode 100644 index b61ab03..0000000 --- a/mqtt/mqtt_topiclist.c +++ /dev/null @@ -1,92 +0,0 @@ -#include "c_types.h" -#include "mem.h" -#include "ets_sys.h" -#include "osapi.h" -#include "os_type.h" - -#include -#include "user_config.h" - -#include "mqtt_topiclist.h" -#include "mqtt_topics.h" - -static topic_entry *topic_list = NULL; -static uint16_t max_entry; - -bool ICACHE_FLASH_ATTR create_topiclist(uint16_t num_entires) { - max_entry = num_entires; - topic_list = (topic_entry *) os_zalloc(num_entires * sizeof(topic_entry)); - return topic_list != NULL; -} - -bool ICACHE_FLASH_ATTR add_topic(MQTT_ClientCon * clientcon, uint8_t * topic, uint8_t qos) { - uint16_t i; - - if (topic_list == NULL) - return false; - if (!Topics_isValidName(topic)) - return false; - - for (i = 0; i < max_entry; i++) { - if (topic_list[i].clientcon == NULL) { - topic_list[i].topic = (uint8_t *) os_malloc(os_strlen(topic) + 1); - if (topic_list[i].topic == NULL) - return false; - os_strcpy(topic_list[i].topic, topic); - topic_list[i].clientcon = clientcon; - topic_list[i].qos = qos; - return true; - } - } - return false; -} - -bool ICACHE_FLASH_ATTR delete_topic(MQTT_ClientCon * clientcon, uint8_t * topic) { - uint16_t i; - - if (topic_list == NULL) - return false; - - for (i = 0; i < max_entry; i++) { - if (topic_list[i].clientcon != NULL && (clientcon == NULL || topic_list[i].clientcon == clientcon)) { - if (topic == NULL || (topic_list[i].topic != NULL && strcmp(topic, topic_list[i].topic) == 0)) { - topic_list[i].clientcon = NULL; - os_free(topic_list[i].topic); - topic_list[i].qos = 0; - } - } - } - return true; -} - -bool ICACHE_FLASH_ATTR find_topic(uint8_t * topic, find_topic_cb cb, uint8_t * data, uint16_t data_len) { - uint16_t i; - bool retval = false; - - if (topic_list == NULL) - return false; - - for (i = 0; i < max_entry; i++) { - if (topic_list[i].clientcon != NULL) { - if (Topics_matches(topic_list[i].topic, 1, topic)) { - (*cb) (&topic_list[i], topic, data, data_len); - retval = true; - } - } - } - return retval; -} - -void ICACHE_FLASH_ATTR iterate_topics(iterate_topic_cb cb, void *user_data) { - uint16_t i; - - if (topic_list == NULL) - return; - - for (i = 0; i < max_entry; i++) { - if (topic_list[i].clientcon != NULL) { - if ((*cb) (&topic_list[i], user_data) == true) - return; - } - } -} diff --git a/mqtt/mqtt_topics.c b/mqtt/mqtt_topics.c deleted file mode 100644 index 18391a7..0000000 --- a/mqtt/mqtt_topics.c +++ /dev/null @@ -1,280 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2007, 2013 IBM Corp. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * Ian Craggs - initial API and implementation and/or initial documentation - *******************************************************************************/ - -/** - * @file - * Topic handling functions. - * - * Topic syntax matches that of other MQTT servers such as Micro broker. - */ - -#include "mqtt_topics.h" - -#include "os_type.h" -#include "osapi.h" -#include "mem.h" -#include "string.h" -/* -char *_strtok_r(char *s, const char *delim, char **last); - -char *_strchr(const char *s, int c) { - while (*s != (char)c) - if (!*s++) - return 0; - return (char *)s; -} -*/ -char ICACHE_FLASH_ATTR *_strdup(char *src) { - char *str; - char *p; - int len = 0; - - while (src[len]) - len++; - str = (char *)os_malloc(len + 1); - p = str; - while (*src) - *p++ = *src++; - *p = '\0'; - return str; -} - -/** - * Checks that the syntax of a topic string is correct. - * @param aName the topic name string - * @return boolean value indicating whether the topic name is valid - */ -int ICACHE_FLASH_ATTR Topics_isValidName(char *aName) { - int rc = true; - char *c = NULL; - int length = os_strlen(aName); - char *hashpos = strchr(aName, '#'); /* '#' wildcard can be only at the beginning or the end of a topic */ - - if (hashpos != NULL) { - char *second = strchr(hashpos + 1, '#'); - if ((hashpos != aName && hashpos != aName + (length - 1)) || second != NULL) - rc = false; - } - - /* '#' or '+' only next to a slash separator or end of name */ - for (c = "#+"; *c != '\0'; ++c) { - char *pos = strchr(aName, *c); - while (pos != NULL) { - if (pos > aName) { /* check previous char is '/' */ - if (*(pos - 1) != '/') - rc = false; - } - if (*(pos + 1) != '\0') { /* check that subsequent char is '/' */ - if (*(pos + 1) != '/') - rc = false; - } - pos = strchr(pos + 1, *c); - } - } - - return rc; -} - -/** - * Reverse a string. - * Linux utility function for Linux to enable Windows/Linux portability - * @param astr the character string to reverse - * @return pointer to the reversed string which was reversed in place - */ -char ICACHE_FLASH_ATTR *_strrev(char *astr) { - char *forwards = astr; - int len = os_strlen(astr); - if (len > 1) { - char *backwards = astr + len - 1; - while (forwards < backwards) { - char temp = *forwards; - *forwards++ = *backwards; - *backwards-- = temp; - } - } - return astr; -} - -/** - * Does a topic string contain wildcards? - * @param topic the topic name string - * @return boolean value indicating whether the topic contains a wildcard or not - */ -int ICACHE_FLASH_ATTR Topics_hasWildcards(char *topic) { - return (strchr(topic, '+') != NULL) || (strchr(topic, '#') != NULL); -} - -/** - * Tests whether one topic string matches another where one can contain wildcards. - * @param wildTopic a topic name string that can contain wildcards - * @param topic a topic name string that must not contain wildcards - * @return boolean value indicating whether topic matches wildTopic - */ -int ICACHE_FLASH_ATTR Topics_matches(char *wildTopic, int wildcards, char *topic) { - int rc = false; - char *last1 = NULL, *last2 = NULL; - char *pwild = NULL, *pmatch = NULL; - - if (!wildcards) { - rc = (os_strcmp(wildTopic, topic) == 0); - goto exit; - } - - if (Topics_hasWildcards(topic)) { - //os_printf("Topics_matches: should not be wildcard in topic %s", topic); - goto exit; - } - if (!Topics_isValidName(wildTopic)) { - //os_printf("Topics_matches: invalid topic name %s", wildTopic); - goto exit; - } - if (!Topics_isValidName(topic)) { - //os_printf("Topics_matches: invalid topic name %s", topic); - goto exit; - } - - if (strcmp(wildTopic, MULTI_LEVEL_WILDCARD) == 0 || /* Hash matches anything... */ - strcmp(wildTopic, topic) == 0) { - rc = true; - goto exit; - } - - if (strcmp(wildTopic, "/#") == 0) { /* Special case for /# matches anything starting with / */ - rc = (topic[0] == '/') ? true : false; - goto exit; - } - - /* because strtok will return bill when matching /bill/ or bill in a topic name for the first time, - * we have to check whether the first character is / explicitly. - */ - if ((wildTopic[0] == TOPIC_LEVEL_SEPARATOR[0]) && (topic[0] != TOPIC_LEVEL_SEPARATOR[0])) - goto exit; - - if ((wildTopic[0] == SINGLE_LEVEL_WILDCARD[0]) && (topic[0] == TOPIC_LEVEL_SEPARATOR[0])) - goto exit; - - /* We only match hash-first topics in reverse, for speed */ - if (wildTopic[0] == MULTI_LEVEL_WILDCARD[0]) { - wildTopic = (char *)_strrev(_strdup(wildTopic)); - topic = (char *)_strrev(_strdup(topic)); - } else { - wildTopic = (char *)_strdup(wildTopic); - topic = (char *)_strdup(topic); - } - - pwild = strtok_r(wildTopic, TOPIC_LEVEL_SEPARATOR, &last1); - pmatch = strtok_r(topic, TOPIC_LEVEL_SEPARATOR, &last2); - - /* Step through the subscription, level by level */ - while (pwild != NULL) { - /* Have we got # - if so, it matches anything. */ - if (strcmp(pwild, MULTI_LEVEL_WILDCARD) == 0) { - rc = true; - break; - } - /* Nope - check for matches... */ - if (pmatch != NULL) { - if (strcmp(pwild, SINGLE_LEVEL_WILDCARD) != 0 && strcmp(pwild, pmatch) != 0) - /* The two levels simply don't match... */ - break; - } else - break; /* No more tokens to match against further tokens in the wildcard stream... */ - pwild = strtok_r(NULL, TOPIC_LEVEL_SEPARATOR, &last1); - pmatch = strtok_r(NULL, TOPIC_LEVEL_SEPARATOR, &last2); - } - - /* All tokens up to here matched, and we didn't end in #. If there - are any topic tokens remaining, the match is bad, otherwise it was - a good match. */ - if (pmatch == NULL && pwild == NULL) - rc = true; - - /* Now free the memory allocated in _strdup() */ - os_free(wildTopic); - os_free(topic); - exit: - return rc; -} /* end matches */ - -#ifdef UNIT_TEST -#if !defined(ARRAY_SIZE) -/** - * Macro to calculate the number of entries in an array - */ -#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) -#endif - -int ICACHE_FLASH_ATTR test() { - int i; - - struct { - char *str; - } tests0[] = { - "#", "jj", "+/a", "adkj/a", "+/a", "adsjk/adakjd/a", "a/+", "a/#", "#/a"}; - - for (i = 0; i < sizeof(tests0) / sizeof(char *); ++i) { - os_printf("topic %s, isValidName %d\n", tests0[i].str, Topics_isValidName(tests0[i].str)); - //assert(Topics_isValidName(tests0[i].str) == 1); - } - - struct { - char *wild; - char *topic; - int result; - } tests1[] = { - { - "#", "jj", 1}, { - "+/a", "adkj/a", 1}, { - "+/a", "adsjk/adakjd/a", 0}, { - "+/+/a", "adsjk/adakjd/a", 1}, { - "#/a", "adsjk/adakjd/a", 1}, { - "test/#", "test/1", 1}, { - "test/+", "test/1", 1}, { - "+", "test1", 1}, { - "+", "test1/k", 0}, { - "+", "/test1/k", 0}, { - "/+", "test1/k", 0}, { - "+", "/jkj", 0}, { - "/+", "/test1", 1}, { - "+/+", "/test1", 0}, { - "+/+", "test1/k", 1}, { - "/#", "/test1/k", 1}, { - "/#", "test1/k", 0},}; - - for (i = 0; i < ARRAY_SIZE(tests1); ++i) { - os_printf("wild: %s, topic %s, result %d %d (should)\n", tests1[i].wild, tests1[i].topic, - Topics_matches(_strdup(tests1[i].wild), 1, _strdup(tests1[i].topic)), tests1[i].result); - //assert(Topics_matches(_strdup(tests1[i].wild), _strdup(tests1[i].topic)) == tests1[i].result); - } - - struct { - char *str; - char *result; - } tests2[] = { - { - "#", "#"}, { - "ab", "ba"}, { - "abc", "cba"}, { - "abcd", "dcba"}, { - "abcde", "edcba"} - }; - for (i = 0; i < 5; ++i) { - os_printf("str: %s, _strrev %s\n", tests2[i].str, _strrev(_strdup(tests2[i].str))); - //assert(strcmp(tests2[i].result, _strrev(_strdup(tests2[i].str))) == 0); - } -} - -#endif diff --git a/mqtt/proto.c b/mqtt/proto.c deleted file mode 100644 index 45bbe95..0000000 --- a/mqtt/proto.c +++ /dev/null @@ -1,132 +0,0 @@ -#include "proto.h" -#include "ringbuf_mqtt.h" -I8 ICACHE_FLASH_ATTR PROTO_Init(PROTO_PARSER * parser, PROTO_PARSE_CALLBACK * completeCallback, U8 * buf, U16 bufSize) { - parser->buf = buf; - parser->bufSize = bufSize; - parser->dataLen = 0; - parser->callback = completeCallback; - parser->isEsc = 0; - return 0; -} - -I8 ICACHE_FLASH_ATTR PROTO_ParseByte(PROTO_PARSER * parser, U8 value) { - switch (value) { - case 0x7D: - parser->isEsc = 1; - break; - - case 0x7E: - parser->dataLen = 0; - parser->isEsc = 0; - parser->isBegin = 1; - break; - - case 0x7F: - if (parser->callback != NULL) - parser->callback(); - parser->isBegin = 0; - return 0; - break; - - default: - if (parser->isBegin == 0) - break; - - if (parser->isEsc) { - value ^= 0x20; - parser->isEsc = 0; - } - - if (parser->dataLen < parser->bufSize) - parser->buf[parser->dataLen++] = value; - - break; - } - return -1; -} - -I8 ICACHE_FLASH_ATTR PROTO_Parse(PROTO_PARSER * parser, U8 * buf, U16 len) { - while (len--) - PROTO_ParseByte(parser, *buf++); - - return 0; -} -I16 ICACHE_FLASH_ATTR PROTO_ParseRb(RINGBUF * rb, U8 * bufOut, U16 * len, U16 maxBufLen) { - U8 c; - - PROTO_PARSER proto; - PROTO_Init(&proto, NULL, bufOut, maxBufLen); - while (RINGBUF_Get(rb, &c) == 0) { - if (PROTO_ParseByte(&proto, c) == 0) { - *len = proto.dataLen; - return 0; - } - } - return -1; -} -I16 ICACHE_FLASH_ATTR PROTO_Add(U8 * buf, const U8 * packet, I16 bufSize) { - U16 i = 2; - U16 len = *(U16 *) packet; - - if (bufSize < 1) - return -1; - - *buf++ = 0x7E; - bufSize--; - - while (len--) { - switch (*packet) { - case 0x7D: - case 0x7E: - case 0x7F: - if (bufSize < 2) - return -1; - *buf++ = 0x7D; - *buf++ = *packet++ ^ 0x20; - i += 2; - bufSize -= 2; - break; - default: - if (bufSize < 1) - return -1; - *buf++ = *packet++; - i++; - bufSize--; - break; - } - } - - if (bufSize < 1) - return -1; - *buf++ = 0x7F; - - return i; -} - -I16 ICACHE_FLASH_ATTR PROTO_AddRb(RINGBUF * rb, const U8 * packet, I16 len) { - U16 i = 2; - if (RINGBUF_Put(rb, 0x7E) == -1) - return -1; - while (len--) { - switch (*packet) { - case 0x7D: - case 0x7E: - case 0x7F: - if (RINGBUF_Put(rb, 0x7D) == -1) - return -1; - if (RINGBUF_Put(rb, *packet++ ^ 0x20) == -1) - return -1; - i += 2; - break; - default: - if (RINGBUF_Put(rb, *packet++) == -1) - return -1; - i++; - break; - } - } - if (RINGBUF_Put(rb, 0x7F) == -1) - return -1; - - return i; -} diff --git a/mqtt/queue.c b/mqtt/queue.c deleted file mode 100644 index 804dbb6..0000000 --- a/mqtt/queue.c +++ /dev/null @@ -1,53 +0,0 @@ -/* str_queue.c -* -* Copyright (c) 2014-2015, Tuan PM -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* * Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* * 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. -* * Neither the name of Redis 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 OWNER 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. -*/ -#include "queue.h" - -#include "user_interface.h" -#include "osapi.h" -#include "os_type.h" -#include "mem.h" -#include "proto.h" -void ICACHE_FLASH_ATTR QUEUE_Init(QUEUE * queue, int bufferSize) { - queue->buf = (uint8_t *) os_zalloc(bufferSize); - RINGBUF_Init(&queue->rb, queue->buf, bufferSize); -} -int32_t ICACHE_FLASH_ATTR QUEUE_Puts(QUEUE * queue, uint8_t * buffer, uint16_t len) { - return PROTO_AddRb(&queue->rb, buffer, len); -} -int32_t ICACHE_FLASH_ATTR QUEUE_Gets(QUEUE * queue, uint8_t * buffer, uint16_t * len, uint16_t maxLen) { - - return PROTO_ParseRb(&queue->rb, buffer, len, maxLen); -} - -BOOL ICACHE_FLASH_ATTR QUEUE_IsEmpty(QUEUE * queue) { - if (queue->rb.fill_cnt <= 0) - return TRUE; - return FALSE; -} diff --git a/mqtt/ringbuf_mqtt.c b/mqtt/ringbuf_mqtt.c deleted file mode 100644 index dd2f8e0..0000000 --- a/mqtt/ringbuf_mqtt.c +++ /dev/null @@ -1,64 +0,0 @@ -/** -* \file -* Ring Buffer library -*/ - -#include "ringbuf_mqtt.h" - -/** -* \brief init a RINGBUF object -* \param r pointer to a RINGBUF object -* \param buf pointer to a byte array -* \param size size of buf -* \return 0 if successfull, otherwise failed -*/ -I16 ICACHE_FLASH_ATTR RINGBUF_Init(RINGBUF * r, U8 * buf, I32 size) { - if (r == NULL || buf == NULL || size < 2) - return -1; - - r->p_o = r->p_r = r->p_w = buf; - r->fill_cnt = 0; - r->size = size; - - return 0; -} - -/** -* \brief put a character into ring buffer -* \param r pointer to a ringbuf object -* \param c character to be put -* \return 0 if successfull, otherwise failed -*/ -I16 ICACHE_FLASH_ATTR RINGBUF_Put(RINGBUF * r, U8 c) { - if (r->fill_cnt >= r->size) - return -1; // ring buffer is full, this should be atomic operation - - r->fill_cnt++; // increase filled slots count, this should be atomic operation - - *r->p_w++ = c; // put character into buffer - - if (r->p_w >= r->p_o + r->size) // rollback if write pointer go pass - r->p_w = r->p_o; // the physical boundary - - return 0; -} - -/** -* \brief get a character from ring buffer -* \param r pointer to a ringbuf object -* \param c read character -* \return 0 if successfull, otherwise failed -*/ -I16 ICACHE_FLASH_ATTR RINGBUF_Get(RINGBUF * r, U8 * c) { - if (r->fill_cnt <= 0) - return -1; // ring buffer is empty, this should be atomic operation - - r->fill_cnt--; // decrease filled slots count - - *c = *r->p_r++; // get the character out - - if (r->p_r >= r->p_o + r->size) // rollback if write pointer go pass - r->p_r = r->p_o; // the physical boundary - - return 0; -} diff --git a/mqtt/utils.c b/mqtt/utils.c deleted file mode 100644 index d3ef700..0000000 --- a/mqtt/utils.c +++ /dev/null @@ -1,144 +0,0 @@ -/* -* Copyright (c) 2014, Tuan PM -* Email: tuanpm@live.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. -* -*/ -#include -#include -#include -#include -#include -#include "utils.h" - -uint8_t ICACHE_FLASH_ATTR UTILS_IsIPV4(int8_t * str) { - uint8_t segs = 0; /* Segment count. */ - uint8_t chcnt = 0; /* Character count within segment. */ - uint8_t accum = 0; /* Accumulator for segment. */ - /* Catch NULL pointer. */ - if (str == 0) - return 0; - /* Process every character in string. */ - - while (*str != '\0') { - /* Segment changeover. */ - - if (*str == '.') { - /* Must have some digits in segment. */ - if (chcnt == 0) - return 0; - /* Limit number of segments. */ - if (++segs == 4) - return 0; - /* Reset segment values and restart loop. */ - chcnt = accum = 0; - str++; - continue; - } - - /* Check numeric. */ - if ((*str < '0') || (*str > '9')) - return 0; - - /* Accumulate and check segment. */ - - if ((accum = accum * 10 + *str - '0') > 255) - return 0; - /* Advance other segment specific stuff and continue loop. */ - - chcnt++; - str++; - } - - /* Check enough segments and enough characters in last segment. */ - - if (segs != 3) - return 0; - if (chcnt == 0) - return 0; - /* Address okay. */ - - return 1; -} -uint8_t ICACHE_FLASH_ATTR UTILS_StrToIP(const int8_t * str, void *ip) { - - /* The count of the number of bytes processed. */ - int i; - /* A pointer to the next digit to process. */ - const char *start; - - start = str; - for (i = 0; i < 4; i++) { - /* The digit being processed. */ - char c; - /* The value of this byte. */ - int n = 0; - while (1) { - c = *start; - start++; - if (c >= '0' && c <= '9') { - n *= 10; - n += c - '0'; - } - /* We insist on stopping at "." if we are still parsing - the first, second, or third numbers. If we have reached - the end of the numbers, we will allow any character. */ - else if ((i < 3 && c == '.') || i == 3) { - break; - } else { - return 0; - } - } - if (n >= 256) { - return 0; - } - ((uint8_t *) ip)[i] = n; - } - return 1; - -} -uint32_t ICACHE_FLASH_ATTR UTILS_Atoh(const int8_t * s) { - uint32_t value = 0, digit; - int8_t c; - - while ((c = *s++)) { - if ('0' <= c && c <= '9') - digit = c - '0'; - else if ('A' <= c && c <= 'F') - digit = c - 'A' + 10; - else if ('a' <= c && c <= 'f') - digit = c - 'a' + 10; - else - break; - - value = (value << 4) | digit; - } - - return value; -} diff --git a/uMQTTBroker b/uMQTTBroker new file mode 160000 index 0000000..9b654d4 --- /dev/null +++ b/uMQTTBroker @@ -0,0 +1 @@ +Subproject commit 9b654d4257bc007405e8ca0d56183b4b712ec7c9 diff --git a/user/lang.c b/user/lang.c index fceb53a..e3a05f8 100644 --- a/user/lang.c +++ b/user/lang.c @@ -4,8 +4,8 @@ #include "lang.h" #include "user_config.h" #include "config_flash.h" -#include "mqtt_topics.h" -#include "mqtt_retainedlist.h" +#include "mqtt/mqtt_topics.h" +#include "mqtt/mqtt_retainedlist.h" #ifdef NTP #include "ntp.h" #endif diff --git a/user/lang.h b/user/lang.h index 3523f49..012ef0b 100644 --- a/user/lang.h +++ b/user/lang.h @@ -1,7 +1,7 @@ #ifndef _LANG_ #define _LANG_ -#include "mqtt_server.h" +#include "mqtt/mqtt_server.h" typedef enum {SYNTAX_CHECK, CONFIG, INIT, MQTT_CLIENT_CONNECT, WIFI_CONNECT, TOPIC_LOCAL, TOPIC_REMOTE, TIMER, GPIO_INT, CLOCK, HTTP_RESPONSE} Interpreter_Status; diff --git a/user/user_main.c b/user/user_main.c index cb626a1..e51f0bb 100644 --- a/user/user_main.c +++ b/user/user_main.c @@ -13,9 +13,9 @@ #include "config_flash.h" #include "sys_time.h" -#include "mqtt_server.h" -#include "mqtt_topiclist.h" -#include "mqtt_retainedlist.h" +#include "mqtt/mqtt_server.h" +#include "mqtt/mqtt_topiclist.h" +#include "mqtt/mqtt_retainedlist.h" #ifdef GPIO //#include "easygpio.h" diff --git a/user_basic/user_main.c b/user_basic/user_main.c index d72f19a..aa525d1 100644 --- a/user_basic/user_main.c +++ b/user_basic/user_main.c @@ -1,5 +1,5 @@ #include "user_interface.h" -#include "mqtt_server.h" +#include "mqtt/mqtt_server.h" #include "user_config.h" void ICACHE_FLASH_ATTR user_init() {