diff --git a/libraries/AsyncTCP/.gitignore b/libraries/AsyncTCP/.gitignore new file mode 100644 index 0000000..9bea433 --- /dev/null +++ b/libraries/AsyncTCP/.gitignore @@ -0,0 +1,2 @@ + +.DS_Store diff --git a/libraries/AsyncTCP/.travis.yml b/libraries/AsyncTCP/.travis.yml new file mode 100644 index 0000000..bc36dba --- /dev/null +++ b/libraries/AsyncTCP/.travis.yml @@ -0,0 +1,34 @@ +sudo: false +language: python +os: + - linux + +git: + depth: false + +stages: + - build + +jobs: + include: + + - name: "Arduino Build" + if: tag IS blank AND (type = pull_request OR (type = push AND branch = master)) + stage: build + script: bash $TRAVIS_BUILD_DIR/travis/build.sh + + - name: "PlatformIO Build" + if: tag IS blank AND (type = pull_request OR (type = push AND branch = master)) + stage: build + script: bash $TRAVIS_BUILD_DIR/travis/build-pio.sh + +notifications: + email: + on_success: change + on_failure: change + webhooks: + urls: + - https://webhooks.gitter.im/e/60e65d0c78ea0a920347 + on_success: change # options: [always|never|change] default: always + on_failure: always # options: [always|never|change] default: always + on_start: false # default: false diff --git a/libraries/AsyncTCP/CMakeLists.txt b/libraries/AsyncTCP/CMakeLists.txt new file mode 100644 index 0000000..f52e1c9 --- /dev/null +++ b/libraries/AsyncTCP/CMakeLists.txt @@ -0,0 +1,15 @@ +set(COMPONENT_SRCDIRS + "src" +) + +set(COMPONENT_ADD_INCLUDEDIRS + "src" +) + +set(COMPONENT_REQUIRES + "arduino-esp32" +) + +register_component() + +target_compile_options(${COMPONENT_TARGET} PRIVATE -fno-rtti) diff --git a/libraries/AsyncTCP/Kconfig.projbuild b/libraries/AsyncTCP/Kconfig.projbuild new file mode 100644 index 0000000..1774926 --- /dev/null +++ b/libraries/AsyncTCP/Kconfig.projbuild @@ -0,0 +1,30 @@ +menu "AsyncTCP Configuration" + +choice ASYNC_TCP_RUNNING_CORE + bool "Core on which AsyncTCP's thread is running" + default ASYNC_TCP_RUN_CORE1 + help + Select on which core AsyncTCP is running + + config ASYNC_TCP_RUN_CORE0 + bool "CORE 0" + config ASYNC_TCP_RUN_CORE1 + bool "CORE 1" + config ASYNC_TCP_RUN_NO_AFFINITY + bool "BOTH" + +endchoice + +config ASYNC_TCP_RUNNING_CORE + int + default 0 if ASYNC_TCP_RUN_CORE0 + default 1 if ASYNC_TCP_RUN_CORE1 + default -1 if ASYNC_TCP_RUN_NO_AFFINITY + +config ASYNC_TCP_USE_WDT + bool "Enable WDT for the AsyncTCP task" + default "y" + help + Enable WDT for the AsyncTCP task, so it will trigger if a handler is locking the thread. + +endmenu diff --git a/libraries/AsyncTCP/LICENSE b/libraries/AsyncTCP/LICENSE new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/libraries/AsyncTCP/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/libraries/AsyncTCP/README.md b/libraries/AsyncTCP/README.md new file mode 100644 index 0000000..983aabd --- /dev/null +++ b/libraries/AsyncTCP/README.md @@ -0,0 +1,13 @@ +# AsyncTCP +[![Build Status](https://travis-ci.org/me-no-dev/AsyncTCP.svg?branch=master)](https://travis-ci.org/me-no-dev/AsyncTCP) ![](https://github.com/me-no-dev/AsyncTCP/workflows/Async%20TCP%20CI/badge.svg) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/2f7e4d1df8b446d192cbfec6dc174d2d)](https://www.codacy.com/manual/me-no-dev/AsyncTCP?utm_source=github.com&utm_medium=referral&utm_content=me-no-dev/AsyncTCP&utm_campaign=Badge_Grade) + +### Async TCP Library for ESP32 Arduino + +[![Join the chat at https://gitter.im/me-no-dev/ESPAsyncWebServer](https://badges.gitter.im/me-no-dev/ESPAsyncWebServer.svg)](https://gitter.im/me-no-dev/ESPAsyncWebServer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +This is a fully asynchronous TCP library, aimed at enabling trouble-free, multi-connection network environment for Espressif's ESP32 MCUs. + +This library is the base for [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) + +## AsyncClient and AsyncServer +The base classes on which everything else is built. They expose all possible scenarios, but are really raw and require more skills to use. diff --git a/libraries/AsyncTCP/component.mk b/libraries/AsyncTCP/component.mk new file mode 100644 index 0000000..bb5bb16 --- /dev/null +++ b/libraries/AsyncTCP/component.mk @@ -0,0 +1,3 @@ +COMPONENT_ADD_INCLUDEDIRS := src +COMPONENT_SRCDIRS := src +CXXFLAGS += -fno-rtti diff --git a/libraries/AsyncTCP/library.json b/libraries/AsyncTCP/library.json new file mode 100644 index 0000000..e542ab1 --- /dev/null +++ b/libraries/AsyncTCP/library.json @@ -0,0 +1,22 @@ +{ + "name":"AsyncTCP", + "description":"Asynchronous TCP Library for ESP32", + "keywords":"async,tcp", + "authors": + { + "name": "Hristo Gochkov", + "maintainer": true + }, + "repository": + { + "type": "git", + "url": "https://github.com/me-no-dev/AsyncTCP.git" + }, + "version": "1.1.0", + "license": "LGPL-3.0", + "frameworks": "arduino", + "platforms": "espressif32", + "build": { + "libCompatMode": 2 + } +} diff --git a/libraries/AsyncTCP/library.properties b/libraries/AsyncTCP/library.properties new file mode 100644 index 0000000..88cf013 --- /dev/null +++ b/libraries/AsyncTCP/library.properties @@ -0,0 +1,9 @@ +name=AsyncTCP +version=1.1.0 +author=Me-No-Dev +maintainer=Me-No-Dev +sentence=Async TCP Library for ESP32 +paragraph=Async TCP Library for ESP32 +category=Other +url=https://github.com/me-no-dev/AsyncTCP +architectures=* diff --git a/libraries/AsyncTCP/src/AsyncTCP.cpp b/libraries/AsyncTCP/src/AsyncTCP.cpp new file mode 100644 index 0000000..5259400 --- /dev/null +++ b/libraries/AsyncTCP/src/AsyncTCP.cpp @@ -0,0 +1,1333 @@ +/* + Asynchronous TCP library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "Arduino.h" + +#include "AsyncTCP.h" +extern "C"{ +#include "lwip/opt.h" +#include "lwip/tcp.h" +#include "lwip/inet.h" +#include "lwip/dns.h" +#include "lwip/err.h" +} +#include "esp_task_wdt.h" + +/* + * TCP/IP Event Task + * */ + +typedef enum { + LWIP_TCP_SENT, LWIP_TCP_RECV, LWIP_TCP_FIN, LWIP_TCP_ERROR, LWIP_TCP_POLL, LWIP_TCP_CLEAR, LWIP_TCP_ACCEPT, LWIP_TCP_CONNECTED, LWIP_TCP_DNS +} lwip_event_t; + +typedef struct { + lwip_event_t event; + void *arg; + union { + struct { + void * pcb; + int8_t err; + } connected; + struct { + int8_t err; + } error; + struct { + tcp_pcb * pcb; + uint16_t len; + } sent; + struct { + tcp_pcb * pcb; + pbuf * pb; + int8_t err; + } recv; + struct { + tcp_pcb * pcb; + int8_t err; + } fin; + struct { + tcp_pcb * pcb; + } poll; + struct { + AsyncClient * client; + } accept; + struct { + const char * name; + ip_addr_t addr; + } dns; + }; +} lwip_event_packet_t; + +static xQueueHandle _async_queue; +static TaskHandle_t _async_service_task_handle = NULL; +const int _number_of_closed_slots = CONFIG_LWIP_MAX_ACTIVE_TCP; +static int _closed_index = 0; +static int _closed_slots[_number_of_closed_slots]; + +static inline bool _init_async_event_queue(){ + if(!_async_queue){ + _async_queue = xQueueCreate(32, sizeof(lwip_event_packet_t *)); + if(!_async_queue){ + return false; + } + } + return true; +} + +static inline bool _send_async_event(lwip_event_packet_t ** e){ + return _async_queue && xQueueSend(_async_queue, e, portMAX_DELAY) == pdPASS; +} + +static inline bool _prepend_async_event(lwip_event_packet_t ** e){ + return _async_queue && xQueueSendToFront(_async_queue, e, portMAX_DELAY) == pdPASS; +} + +static inline bool _get_async_event(lwip_event_packet_t ** e){ + return _async_queue && xQueueReceive(_async_queue, e, portMAX_DELAY) == pdPASS; +} + +static bool _remove_events_with_arg(void * arg){ + lwip_event_packet_t * first_packet = NULL; + lwip_event_packet_t * packet = NULL; + + if(!_async_queue){ + return false; + } + //figure out which is the first packet so we can keep the order + while(!first_packet){ + if(xQueueReceive(_async_queue, &first_packet, 0) != pdPASS){ + return false; + } + //discard packet if matching + if((int)first_packet->arg == (int)arg){ + free(first_packet); + first_packet = NULL; + //return first packet to the back of the queue + } else if(xQueueSend(_async_queue, &first_packet, portMAX_DELAY) != pdPASS){ + return false; + } + } + + while(xQueuePeek(_async_queue, &packet, 0) == pdPASS && packet != first_packet){ + if(xQueueReceive(_async_queue, &packet, 0) != pdPASS){ + return false; + } + if((int)packet->arg == (int)arg){ + free(packet); + packet = NULL; + } else if(xQueueSend(_async_queue, &packet, portMAX_DELAY) != pdPASS){ + return false; + } + } + return true; +} + +static void _handle_async_event(lwip_event_packet_t * e){ + if(e->event == LWIP_TCP_CLEAR){ + _remove_events_with_arg(e->arg); + } else if(e->event == LWIP_TCP_RECV){ + //ets_printf("-R: 0x%08x\n", e->recv.pcb); + AsyncClient::_s_recv(e->arg, e->recv.pcb, e->recv.pb, e->recv.err); + } else if(e->event == LWIP_TCP_FIN){ + //ets_printf("-F: 0x%08x\n", e->fin.pcb); + AsyncClient::_s_fin(e->arg, e->fin.pcb, e->fin.err); + } else if(e->event == LWIP_TCP_SENT){ + //ets_printf("-S: 0x%08x\n", e->sent.pcb); + AsyncClient::_s_sent(e->arg, e->sent.pcb, e->sent.len); + } else if(e->event == LWIP_TCP_POLL){ + //ets_printf("-P: 0x%08x\n", e->poll.pcb); + AsyncClient::_s_poll(e->arg, e->poll.pcb); + } else if(e->event == LWIP_TCP_ERROR){ + //ets_printf("-E: 0x%08x %d\n", e->arg, e->error.err); + AsyncClient::_s_error(e->arg, e->error.err); + } else if(e->event == LWIP_TCP_CONNECTED){ + //ets_printf("C: 0x%08x 0x%08x %d\n", e->arg, e->connected.pcb, e->connected.err); + AsyncClient::_s_connected(e->arg, e->connected.pcb, e->connected.err); + } else if(e->event == LWIP_TCP_ACCEPT){ + //ets_printf("A: 0x%08x 0x%08x\n", e->arg, e->accept.client); + AsyncServer::_s_accepted(e->arg, e->accept.client); + } else if(e->event == LWIP_TCP_DNS){ + //ets_printf("D: 0x%08x %s = %s\n", e->arg, e->dns.name, ipaddr_ntoa(&e->dns.addr)); + AsyncClient::_s_dns_found(e->dns.name, &e->dns.addr, e->arg); + } + free((void*)(e)); +} + +static void _async_service_task(void *pvParameters){ + lwip_event_packet_t * packet = NULL; + for (;;) { + if(_get_async_event(&packet)){ +#if CONFIG_ASYNC_TCP_USE_WDT + if(esp_task_wdt_add(NULL) != ESP_OK){ + log_e("Failed to add async task to WDT"); + } +#endif + _handle_async_event(packet); +#if CONFIG_ASYNC_TCP_USE_WDT + if(esp_task_wdt_delete(NULL) != ESP_OK){ + log_e("Failed to remove loop task from WDT"); + } +#endif + } + } + vTaskDelete(NULL); + _async_service_task_handle = NULL; +} +/* +static void _stop_async_task(){ + if(_async_service_task_handle){ + vTaskDelete(_async_service_task_handle); + _async_service_task_handle = NULL; + } +} +*/ +static bool _start_async_task(){ + if(!_init_async_event_queue()){ + return false; + } + if(!_async_service_task_handle){ + xTaskCreateUniversal(_async_service_task, "async_tcp", 8192 * 2, NULL, 3, &_async_service_task_handle, CONFIG_ASYNC_TCP_RUNNING_CORE); + if(!_async_service_task_handle){ + return false; + } + } + return true; +} + +/* + * LwIP Callbacks + * */ + +static int8_t _tcp_clear_events(void * arg) { + lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); + e->event = LWIP_TCP_CLEAR; + e->arg = arg; + if (!_prepend_async_event(&e)) { + free((void*)(e)); + } + return ERR_OK; +} + +static int8_t _tcp_connected(void * arg, tcp_pcb * pcb, int8_t err) { + //ets_printf("+C: 0x%08x\n", pcb); + lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); + e->event = LWIP_TCP_CONNECTED; + e->arg = arg; + e->connected.pcb = pcb; + e->connected.err = err; + if (!_prepend_async_event(&e)) { + free((void*)(e)); + } + return ERR_OK; +} + +static int8_t _tcp_poll(void * arg, struct tcp_pcb * pcb) { + //ets_printf("+P: 0x%08x\n", pcb); + lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); + e->event = LWIP_TCP_POLL; + e->arg = arg; + e->poll.pcb = pcb; + if (!_send_async_event(&e)) { + free((void*)(e)); + } + return ERR_OK; +} + +static int8_t _tcp_recv(void * arg, struct tcp_pcb * pcb, struct pbuf *pb, int8_t err) { + lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); + e->arg = arg; + if(pb){ + //ets_printf("+R: 0x%08x\n", pcb); + e->event = LWIP_TCP_RECV; + e->recv.pcb = pcb; + e->recv.pb = pb; + e->recv.err = err; + } else { + //ets_printf("+F: 0x%08x\n", pcb); + e->event = LWIP_TCP_FIN; + e->fin.pcb = pcb; + e->fin.err = err; + //close the PCB in LwIP thread + AsyncClient::_s_lwip_fin(e->arg, e->fin.pcb, e->fin.err); + } + if (!_send_async_event(&e)) { + free((void*)(e)); + } + return ERR_OK; +} + +static int8_t _tcp_sent(void * arg, struct tcp_pcb * pcb, uint16_t len) { + //ets_printf("+S: 0x%08x\n", pcb); + lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); + e->event = LWIP_TCP_SENT; + e->arg = arg; + e->sent.pcb = pcb; + e->sent.len = len; + if (!_send_async_event(&e)) { + free((void*)(e)); + } + return ERR_OK; +} + +static void _tcp_error(void * arg, int8_t err) { + //ets_printf("+E: 0x%08x\n", arg); + lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); + e->event = LWIP_TCP_ERROR; + e->arg = arg; + e->error.err = err; + if (!_send_async_event(&e)) { + free((void*)(e)); + } +} + +static void _tcp_dns_found(const char * name, struct ip_addr * ipaddr, void * arg) { + lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); + //ets_printf("+DNS: name=%s ipaddr=0x%08x arg=%x\n", name, ipaddr, arg); + e->event = LWIP_TCP_DNS; + e->arg = arg; + e->dns.name = name; + if (ipaddr) { + memcpy(&e->dns.addr, ipaddr, sizeof(struct ip_addr)); + } else { + memset(&e->dns.addr, 0, sizeof(e->dns.addr)); + } + if (!_send_async_event(&e)) { + free((void*)(e)); + } +} + +//Used to switch out from LwIP thread +static int8_t _tcp_accept(void * arg, AsyncClient * client) { + lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); + e->event = LWIP_TCP_ACCEPT; + e->arg = arg; + e->accept.client = client; + if (!_prepend_async_event(&e)) { + free((void*)(e)); + } + return ERR_OK; +} + +/* + * TCP/IP API Calls + * */ + +#include "lwip/priv/tcpip_priv.h" + +typedef struct { + struct tcpip_api_call_data call; + tcp_pcb * pcb; + int8_t closed_slot; + int8_t err; + union { + struct { + const char* data; + size_t size; + uint8_t apiflags; + } write; + size_t received; + struct { + ip_addr_t * addr; + uint16_t port; + tcp_connected_fn cb; + } connect; + struct { + ip_addr_t * addr; + uint16_t port; + } bind; + uint8_t backlog; + }; +} tcp_api_call_t; + +static err_t _tcp_output_api(struct tcpip_api_call_data *api_call_msg){ + tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; + msg->err = ERR_CONN; + if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { + msg->err = tcp_output(msg->pcb); + } + return msg->err; +} + +static esp_err_t _tcp_output(tcp_pcb * pcb, int8_t closed_slot) { + if(!pcb){ + return ERR_CONN; + } + tcp_api_call_t msg; + msg.pcb = pcb; + msg.closed_slot = closed_slot; + tcpip_api_call(_tcp_output_api, (struct tcpip_api_call_data*)&msg); + return msg.err; +} + +static err_t _tcp_write_api(struct tcpip_api_call_data *api_call_msg){ + tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; + msg->err = ERR_CONN; + if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { + msg->err = tcp_write(msg->pcb, msg->write.data, msg->write.size, msg->write.apiflags); + } + return msg->err; +} + +static esp_err_t _tcp_write(tcp_pcb * pcb, int8_t closed_slot, const char* data, size_t size, uint8_t apiflags) { + if(!pcb){ + return ERR_CONN; + } + tcp_api_call_t msg; + msg.pcb = pcb; + msg.closed_slot = closed_slot; + msg.write.data = data; + msg.write.size = size; + msg.write.apiflags = apiflags; + tcpip_api_call(_tcp_write_api, (struct tcpip_api_call_data*)&msg); + return msg.err; +} + +static err_t _tcp_recved_api(struct tcpip_api_call_data *api_call_msg){ + tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; + msg->err = ERR_CONN; + if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { + msg->err = 0; + tcp_recved(msg->pcb, msg->received); + } + return msg->err; +} + +static esp_err_t _tcp_recved(tcp_pcb * pcb, int8_t closed_slot, size_t len) { + if(!pcb){ + return ERR_CONN; + } + tcp_api_call_t msg; + msg.pcb = pcb; + msg.closed_slot = closed_slot; + msg.received = len; + tcpip_api_call(_tcp_recved_api, (struct tcpip_api_call_data*)&msg); + return msg.err; +} + +static err_t _tcp_close_api(struct tcpip_api_call_data *api_call_msg){ + tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; + msg->err = ERR_CONN; + if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { + msg->err = tcp_close(msg->pcb); + } + return msg->err; +} + +static esp_err_t _tcp_close(tcp_pcb * pcb, int8_t closed_slot) { + if(!pcb){ + return ERR_CONN; + } + tcp_api_call_t msg; + msg.pcb = pcb; + msg.closed_slot = closed_slot; + tcpip_api_call(_tcp_close_api, (struct tcpip_api_call_data*)&msg); + return msg.err; +} + +static err_t _tcp_abort_api(struct tcpip_api_call_data *api_call_msg){ + tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; + msg->err = ERR_CONN; + if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { + tcp_abort(msg->pcb); + } + return msg->err; +} + +static esp_err_t _tcp_abort(tcp_pcb * pcb, int8_t closed_slot) { + if(!pcb){ + return ERR_CONN; + } + tcp_api_call_t msg; + msg.pcb = pcb; + msg.closed_slot = closed_slot; + tcpip_api_call(_tcp_abort_api, (struct tcpip_api_call_data*)&msg); + return msg.err; +} + +static err_t _tcp_connect_api(struct tcpip_api_call_data *api_call_msg){ + tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; + msg->err = tcp_connect(msg->pcb, msg->connect.addr, msg->connect.port, msg->connect.cb); + return msg->err; +} + +static esp_err_t _tcp_connect(tcp_pcb * pcb, int8_t closed_slot, ip_addr_t * addr, uint16_t port, tcp_connected_fn cb) { + if(!pcb){ + return ESP_FAIL; + } + tcp_api_call_t msg; + msg.pcb = pcb; + msg.closed_slot = closed_slot; + msg.connect.addr = addr; + msg.connect.port = port; + msg.connect.cb = cb; + tcpip_api_call(_tcp_connect_api, (struct tcpip_api_call_data*)&msg); + return msg.err; +} + +static err_t _tcp_bind_api(struct tcpip_api_call_data *api_call_msg){ + tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; + msg->err = tcp_bind(msg->pcb, msg->bind.addr, msg->bind.port); + return msg->err; +} + +static esp_err_t _tcp_bind(tcp_pcb * pcb, ip_addr_t * addr, uint16_t port) { + if(!pcb){ + return ESP_FAIL; + } + tcp_api_call_t msg; + msg.pcb = pcb; + msg.closed_slot = -1; + msg.bind.addr = addr; + msg.bind.port = port; + tcpip_api_call(_tcp_bind_api, (struct tcpip_api_call_data*)&msg); + return msg.err; +} + +static err_t _tcp_listen_api(struct tcpip_api_call_data *api_call_msg){ + tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; + msg->err = 0; + msg->pcb = tcp_listen_with_backlog(msg->pcb, msg->backlog); + return msg->err; +} + +static tcp_pcb * _tcp_listen_with_backlog(tcp_pcb * pcb, uint8_t backlog) { + if(!pcb){ + return NULL; + } + tcp_api_call_t msg; + msg.pcb = pcb; + msg.closed_slot = -1; + msg.backlog = backlog?backlog:0xFF; + tcpip_api_call(_tcp_listen_api, (struct tcpip_api_call_data*)&msg); + return msg.pcb; +} + + + +/* + Async TCP Client + */ + +AsyncClient::AsyncClient(tcp_pcb* pcb) +: _connect_cb(0) +, _connect_cb_arg(0) +, _discard_cb(0) +, _discard_cb_arg(0) +, _sent_cb(0) +, _sent_cb_arg(0) +, _error_cb(0) +, _error_cb_arg(0) +, _recv_cb(0) +, _recv_cb_arg(0) +, _pb_cb(0) +, _pb_cb_arg(0) +, _timeout_cb(0) +, _timeout_cb_arg(0) +, _pcb_busy(false) +, _pcb_sent_at(0) +, _ack_pcb(true) +, _rx_last_packet(0) +, _rx_since_timeout(0) +, _ack_timeout(ASYNC_MAX_ACK_TIME) +, _connect_port(0) +, prev(NULL) +, next(NULL) +{ + _pcb = pcb; + _closed_slot = -1; + if(_pcb){ + _closed_slot = 0; + if (_closed_index == 0) { + _closed_index = 1; + for (int i = 0; i < _number_of_closed_slots; ++ i) { + _closed_slots[i] = 1; + } + } else { + int closed_slot_min_index = _closed_slots[0]; + for (int i = 0; i < _number_of_closed_slots; ++ i) { + if (_closed_slots[i] <= closed_slot_min_index && _closed_slots[i] != 0) { + closed_slot_min_index = _closed_slots[i]; + _closed_slot = i; + } + } + } + _closed_slots[_closed_slot] = 0; + + _rx_last_packet = millis(); + tcp_arg(_pcb, this); + tcp_recv(_pcb, &_tcp_recv); + tcp_sent(_pcb, &_tcp_sent); + tcp_err(_pcb, &_tcp_error); + tcp_poll(_pcb, &_tcp_poll, 1); + } +} + +AsyncClient::~AsyncClient(){ + if(_pcb) { + _close(); + } +} + +/* + * Operators + * */ + +AsyncClient& AsyncClient::operator=(const AsyncClient& other){ + if (_pcb) { + _close(); + } + + _pcb = other._pcb; + _closed_slot = other._closed_slot; + if (_pcb) { + _rx_last_packet = millis(); + tcp_arg(_pcb, this); + tcp_recv(_pcb, &_tcp_recv); + tcp_sent(_pcb, &_tcp_sent); + tcp_err(_pcb, &_tcp_error); + tcp_poll(_pcb, &_tcp_poll, 1); + } + return *this; +} + +bool AsyncClient::operator==(const AsyncClient &other) { + return _pcb == other._pcb; +} + +AsyncClient & AsyncClient::operator+=(const AsyncClient &other) { + if(next == NULL){ + next = (AsyncClient*)(&other); + next->prev = this; + } else { + AsyncClient *c = next; + while(c->next != NULL) { + c = c->next; + } + c->next =(AsyncClient*)(&other); + c->next->prev = c; + } + return *this; +} + +/* + * Callback Setters + * */ + +void AsyncClient::onConnect(AcConnectHandler cb, void* arg){ + _connect_cb = cb; + _connect_cb_arg = arg; +} + +void AsyncClient::onDisconnect(AcConnectHandler cb, void* arg){ + _discard_cb = cb; + _discard_cb_arg = arg; +} + +void AsyncClient::onAck(AcAckHandler cb, void* arg){ + _sent_cb = cb; + _sent_cb_arg = arg; +} + +void AsyncClient::onError(AcErrorHandler cb, void* arg){ + _error_cb = cb; + _error_cb_arg = arg; +} + +void AsyncClient::onData(AcDataHandler cb, void* arg){ + _recv_cb = cb; + _recv_cb_arg = arg; +} + +void AsyncClient::onPacket(AcPacketHandler cb, void* arg){ + _pb_cb = cb; + _pb_cb_arg = arg; +} + +void AsyncClient::onTimeout(AcTimeoutHandler cb, void* arg){ + _timeout_cb = cb; + _timeout_cb_arg = arg; +} + +void AsyncClient::onPoll(AcConnectHandler cb, void* arg){ + _poll_cb = cb; + _poll_cb_arg = arg; +} + +/* + * Main Public Methods + * */ + +bool AsyncClient::connect(IPAddress ip, uint16_t port){ + if (_pcb){ + log_w("already connected, state %d", _pcb->state); + return false; + } + if(!_start_async_task()){ + log_e("failed to start task"); + return false; + } + + ip_addr_t addr; + addr.type = IPADDR_TYPE_V4; + addr.u_addr.ip4.addr = ip; + + tcp_pcb* pcb = tcp_new_ip_type(IPADDR_TYPE_V4); + if (!pcb){ + log_e("pcb == NULL"); + return false; + } + + tcp_arg(pcb, this); + tcp_err(pcb, &_tcp_error); + tcp_recv(pcb, &_tcp_recv); + tcp_sent(pcb, &_tcp_sent); + tcp_poll(pcb, &_tcp_poll, 1); + //_tcp_connect(pcb, &addr, port,(tcp_connected_fn)&_s_connected); + _tcp_connect(pcb, _closed_slot, &addr, port,(tcp_connected_fn)&_tcp_connected); + return true; +} + +bool AsyncClient::connect(const char* host, uint16_t port){ + ip_addr_t addr; + + if(!_start_async_task()){ + Serial.println("failed to start task"); + log_e("failed to start task"); + return false; + } + + err_t err = dns_gethostbyname(host, &addr, (dns_found_callback)&_tcp_dns_found, this); + if(err == ERR_OK) { + return connect(IPAddress(addr.u_addr.ip4.addr), port); + } else if(err == ERR_INPROGRESS) { + _connect_port = port; + return true; + } + log_e("error: %d", err); + return false; +} + +void AsyncClient::close(bool now){ + if(_pcb){ + _tcp_recved(_pcb, _closed_slot, _rx_ack_len); + } + _close(); +} + +int8_t AsyncClient::abort(){ + if(_pcb) { + _tcp_abort(_pcb, _closed_slot ); + _pcb = NULL; + } + return ERR_ABRT; +} + +size_t AsyncClient::space(){ + if((_pcb != NULL) && (_pcb->state == 4)){ + return tcp_sndbuf(_pcb); + } + return 0; +} + +size_t AsyncClient::add(const char* data, size_t size, uint8_t apiflags) { + if(!_pcb || size == 0 || data == NULL) { + return 0; + } + size_t room = space(); + if(!room) { + return 0; + } + size_t will_send = (room < size) ? room : size; + int8_t err = ERR_OK; + err = _tcp_write(_pcb, _closed_slot, data, will_send, apiflags); + if(err != ERR_OK) { + return 0; + } + return will_send; +} + +bool AsyncClient::send(){ + int8_t err = ERR_OK; + err = _tcp_output(_pcb, _closed_slot); + if(err == ERR_OK){ + _pcb_busy = true; + _pcb_sent_at = millis(); + return true; + } + return false; +} + +size_t AsyncClient::ack(size_t len){ + if(len > _rx_ack_len) + len = _rx_ack_len; + if(len){ + _tcp_recved(_pcb, _closed_slot, len); + } + _rx_ack_len -= len; + return len; +} + +void AsyncClient::ackPacket(struct pbuf * pb){ + if(!pb){ + return; + } + _tcp_recved(_pcb, _closed_slot, pb->len); + pbuf_free(pb); +} + +/* + * Main Private Methods + * */ + +int8_t AsyncClient::_close(){ + //ets_printf("X: 0x%08x\n", (uint32_t)this); + int8_t err = ERR_OK; + if(_pcb) { + //log_i(""); + tcp_arg(_pcb, NULL); + tcp_sent(_pcb, NULL); + tcp_recv(_pcb, NULL); + tcp_err(_pcb, NULL); + tcp_poll(_pcb, NULL, 0); + _tcp_clear_events(this); + err = _tcp_close(_pcb, _closed_slot); + if(err != ERR_OK) { + err = abort(); + } + _pcb = NULL; + if(_discard_cb) { + _discard_cb(_discard_cb_arg, this); + } + } + return err; +} + +/* + * Private Callbacks + * */ + +int8_t AsyncClient::_connected(void* pcb, int8_t err){ + _pcb = reinterpret_cast(pcb); + if(_pcb){ + _rx_last_packet = millis(); + _pcb_busy = false; +// tcp_recv(_pcb, &_tcp_recv); +// tcp_sent(_pcb, &_tcp_sent); +// tcp_poll(_pcb, &_tcp_poll, 1); + } + if(_connect_cb) { + _connect_cb(_connect_cb_arg, this); + } + return ERR_OK; +} + +void AsyncClient::_error(int8_t err) { + if(_pcb){ + tcp_arg(_pcb, NULL); + tcp_sent(_pcb, NULL); + tcp_recv(_pcb, NULL); + tcp_err(_pcb, NULL); + tcp_poll(_pcb, NULL, 0); + _pcb = NULL; + } + if(_error_cb) { + _error_cb(_error_cb_arg, this, err); + } + if(_discard_cb) { + _discard_cb(_discard_cb_arg, this); + } +} + +//In LwIP Thread +int8_t AsyncClient::_lwip_fin(tcp_pcb* pcb, int8_t err) { + if(!_pcb || pcb != _pcb){ + log_e("0x%08x != 0x%08x", (uint32_t)pcb, (uint32_t)_pcb); + return ERR_OK; + } + tcp_arg(_pcb, NULL); + tcp_sent(_pcb, NULL); + tcp_recv(_pcb, NULL); + tcp_err(_pcb, NULL); + tcp_poll(_pcb, NULL, 0); + if(tcp_close(_pcb) != ERR_OK) { + tcp_abort(_pcb); + } + _closed_slots[_closed_slot] = _closed_index; + ++ _closed_index; + _pcb = NULL; + return ERR_OK; +} + +//In Async Thread +int8_t AsyncClient::_fin(tcp_pcb* pcb, int8_t err) { + _tcp_clear_events(this); + if(_discard_cb) { + _discard_cb(_discard_cb_arg, this); + } + return ERR_OK; +} + +int8_t AsyncClient::_sent(tcp_pcb* pcb, uint16_t len) { + _rx_last_packet = millis(); + //log_i("%u", len); + _pcb_busy = false; + if(_sent_cb) { + _sent_cb(_sent_cb_arg, this, len, (millis() - _pcb_sent_at)); + } + return ERR_OK; +} + +int8_t AsyncClient::_recv(tcp_pcb* pcb, pbuf* pb, int8_t err) { + while(pb != NULL) { + _rx_last_packet = millis(); + //we should not ack before we assimilate the data + _ack_pcb = true; + pbuf *b = pb; + pb = b->next; + b->next = NULL; + if(_pb_cb){ + _pb_cb(_pb_cb_arg, this, b); + } else { + if(_recv_cb) { + _recv_cb(_recv_cb_arg, this, b->payload, b->len); + } + if(!_ack_pcb) { + _rx_ack_len += b->len; + } else if(_pcb) { + _tcp_recved(_pcb, _closed_slot, b->len); + } + pbuf_free(b); + } + } + return ERR_OK; +} + +int8_t AsyncClient::_poll(tcp_pcb* pcb){ + if(!_pcb){ + log_w("pcb is NULL"); + return ERR_OK; + } + if(pcb != _pcb){ + log_e("0x%08x != 0x%08x", (uint32_t)pcb, (uint32_t)_pcb); + return ERR_OK; + } + + uint32_t now = millis(); + + // ACK Timeout + if(_pcb_busy && _ack_timeout && (now - _pcb_sent_at) >= _ack_timeout){ + _pcb_busy = false; + log_w("ack timeout %d", pcb->state); + if(_timeout_cb) + _timeout_cb(_timeout_cb_arg, this, (now - _pcb_sent_at)); + return ERR_OK; + } + // RX Timeout + if(_rx_since_timeout && (now - _rx_last_packet) >= (_rx_since_timeout * 1000)){ + log_w("rx timeout %d", pcb->state); + _close(); + return ERR_OK; + } + // Everything is fine + if(_poll_cb) { + _poll_cb(_poll_cb_arg, this); + } + return ERR_OK; +} + +void AsyncClient::_dns_found(struct ip_addr *ipaddr){ + if(ipaddr && ipaddr->u_addr.ip4.addr){ + connect(IPAddress(ipaddr->u_addr.ip4.addr), _connect_port); + } else { + if(_error_cb) { + _error_cb(_error_cb_arg, this, -55); + } + if(_discard_cb) { + _discard_cb(_discard_cb_arg, this); + } + } +} + +/* + * Public Helper Methods + * */ + +void AsyncClient::stop() { + close(false); +} + +bool AsyncClient::free(){ + if(!_pcb) { + return true; + } + if(_pcb->state == 0 || _pcb->state > 4) { + return true; + } + return false; +} + +size_t AsyncClient::write(const char* data) { + if(data == NULL) { + return 0; + } + return write(data, strlen(data)); +} + +size_t AsyncClient::write(const char* data, size_t size, uint8_t apiflags) { + size_t will_send = add(data, size, apiflags); + if(!will_send || !send()) { + return 0; + } + return will_send; +} + +void AsyncClient::setRxTimeout(uint32_t timeout){ + _rx_since_timeout = timeout; +} + +uint32_t AsyncClient::getRxTimeout(){ + return _rx_since_timeout; +} + +uint32_t AsyncClient::getAckTimeout(){ + return _ack_timeout; +} + +void AsyncClient::setAckTimeout(uint32_t timeout){ + _ack_timeout = timeout; +} + +void AsyncClient::setNoDelay(bool nodelay){ + if(!_pcb) { + return; + } + if(nodelay) { + tcp_nagle_disable(_pcb); + } else { + tcp_nagle_enable(_pcb); + } +} + +bool AsyncClient::getNoDelay(){ + if(!_pcb) { + return false; + } + return tcp_nagle_disabled(_pcb); +} + +uint16_t AsyncClient::getMss(){ + if(!_pcb) { + return 0; + } + return tcp_mss(_pcb); +} + +uint32_t AsyncClient::getRemoteAddress() { + if(!_pcb) { + return 0; + } + return _pcb->remote_ip.u_addr.ip4.addr; +} + +uint16_t AsyncClient::getRemotePort() { + if(!_pcb) { + return 0; + } + return _pcb->remote_port; +} + +uint32_t AsyncClient::getLocalAddress() { + if(!_pcb) { + return 0; + } + return _pcb->local_ip.u_addr.ip4.addr; +} + +uint16_t AsyncClient::getLocalPort() { + if(!_pcb) { + return 0; + } + return _pcb->local_port; +} + +IPAddress AsyncClient::remoteIP() { + return IPAddress(getRemoteAddress()); +} + +uint16_t AsyncClient::remotePort() { + return getRemotePort(); +} + +IPAddress AsyncClient::localIP() { + return IPAddress(getLocalAddress()); +} + +uint16_t AsyncClient::localPort() { + return getLocalPort(); +} + +uint8_t AsyncClient::state() { + if(!_pcb) { + return 0; + } + return _pcb->state; +} + +bool AsyncClient::connected(){ + if (!_pcb) { + return false; + } + return _pcb->state == 4; +} + +bool AsyncClient::connecting(){ + if (!_pcb) { + return false; + } + return _pcb->state > 0 && _pcb->state < 4; +} + +bool AsyncClient::disconnecting(){ + if (!_pcb) { + return false; + } + return _pcb->state > 4 && _pcb->state < 10; +} + +bool AsyncClient::disconnected(){ + if (!_pcb) { + return true; + } + return _pcb->state == 0 || _pcb->state == 10; +} + +bool AsyncClient::freeable(){ + if (!_pcb) { + return true; + } + return _pcb->state == 0 || _pcb->state > 4; +} + +bool AsyncClient::canSend(){ + return space() > 0; +} + +const char * AsyncClient::errorToString(int8_t error){ + switch(error){ + case ERR_OK: return "OK"; + case ERR_MEM: return "Out of memory error"; + case ERR_BUF: return "Buffer error"; + case ERR_TIMEOUT: return "Timeout"; + case ERR_RTE: return "Routing problem"; + case ERR_INPROGRESS: return "Operation in progress"; + case ERR_VAL: return "Illegal value"; + case ERR_WOULDBLOCK: return "Operation would block"; + case ERR_USE: return "Address in use"; + case ERR_ALREADY: return "Already connected"; + case ERR_CONN: return "Not connected"; + case ERR_IF: return "Low-level netif error"; + case ERR_ABRT: return "Connection aborted"; + case ERR_RST: return "Connection reset"; + case ERR_CLSD: return "Connection closed"; + case ERR_ARG: return "Illegal argument"; + case -55: return "DNS failed"; + default: return "UNKNOWN"; + } +} + +const char * AsyncClient::stateToString(){ + switch(state()){ + case 0: return "Closed"; + case 1: return "Listen"; + case 2: return "SYN Sent"; + case 3: return "SYN Received"; + case 4: return "Established"; + case 5: return "FIN Wait 1"; + case 6: return "FIN Wait 2"; + case 7: return "Close Wait"; + case 8: return "Closing"; + case 9: return "Last ACK"; + case 10: return "Time Wait"; + default: return "UNKNOWN"; + } +} + +/* + * Static Callbacks (LwIP C2C++ interconnect) + * */ + +void AsyncClient::_s_dns_found(const char * name, struct ip_addr * ipaddr, void * arg){ + reinterpret_cast(arg)->_dns_found(ipaddr); +} + +int8_t AsyncClient::_s_poll(void * arg, struct tcp_pcb * pcb) { + return reinterpret_cast(arg)->_poll(pcb); +} + +int8_t AsyncClient::_s_recv(void * arg, struct tcp_pcb * pcb, struct pbuf *pb, int8_t err) { + return reinterpret_cast(arg)->_recv(pcb, pb, err); +} + +int8_t AsyncClient::_s_fin(void * arg, struct tcp_pcb * pcb, int8_t err) { + return reinterpret_cast(arg)->_fin(pcb, err); +} + +int8_t AsyncClient::_s_lwip_fin(void * arg, struct tcp_pcb * pcb, int8_t err) { + return reinterpret_cast(arg)->_lwip_fin(pcb, err); +} + +int8_t AsyncClient::_s_sent(void * arg, struct tcp_pcb * pcb, uint16_t len) { + return reinterpret_cast(arg)->_sent(pcb, len); +} + +void AsyncClient::_s_error(void * arg, int8_t err) { + reinterpret_cast(arg)->_error(err); +} + +int8_t AsyncClient::_s_connected(void * arg, void * pcb, int8_t err){ + return reinterpret_cast(arg)->_connected(pcb, err); +} + +/* + Async TCP Server + */ + +AsyncServer::AsyncServer(IPAddress addr, uint16_t port) +: _port(port) +, _addr(addr) +, _noDelay(false) +, _pcb(0) +, _connect_cb(0) +, _connect_cb_arg(0) +{} + +AsyncServer::AsyncServer(uint16_t port) +: _port(port) +, _addr((uint32_t) IPADDR_ANY) +, _noDelay(false) +, _pcb(0) +, _connect_cb(0) +, _connect_cb_arg(0) +{} + +AsyncServer::~AsyncServer(){ + end(); +} + +void AsyncServer::onClient(AcConnectHandler cb, void* arg){ + _connect_cb = cb; + _connect_cb_arg = arg; +} + +void AsyncServer::begin(){ + if(_pcb) { + return; + } + + if(!_start_async_task()){ + log_e("failed to start task"); + return; + } + int8_t err; + _pcb = tcp_new_ip_type(IPADDR_TYPE_V4); + if (!_pcb){ + log_e("_pcb == NULL"); + return; + } + + ip_addr_t local_addr; + local_addr.type = IPADDR_TYPE_V4; + local_addr.u_addr.ip4.addr = (uint32_t) _addr; + err = _tcp_bind(_pcb, &local_addr, _port); + + if (err != ERR_OK) { + _tcp_close(_pcb, -1); + log_e("bind error: %d", err); + return; + } + + static uint8_t backlog = 5; + _pcb = _tcp_listen_with_backlog(_pcb, backlog); + if (!_pcb) { + log_e("listen_pcb == NULL"); + return; + } + tcp_arg(_pcb, (void*) this); + tcp_accept(_pcb, &_s_accept); +} + +void AsyncServer::end(){ + if(_pcb){ + tcp_arg(_pcb, NULL); + tcp_accept(_pcb, NULL); + if(tcp_close(_pcb) != ERR_OK){ + _tcp_abort(_pcb, -1); + } + _pcb = NULL; + } +} + +//runs on LwIP thread +int8_t AsyncServer::_accept(tcp_pcb* pcb, int8_t err){ + //ets_printf("+A: 0x%08x\n", pcb); + if(_connect_cb){ + AsyncClient *c = new AsyncClient(pcb); + if(c){ + c->setNoDelay(_noDelay); + return _tcp_accept(this, c); + } + } + if(tcp_close(pcb) != ERR_OK){ + tcp_abort(pcb); + } + log_e("FAIL"); + return ERR_OK; +} + +int8_t AsyncServer::_accepted(AsyncClient* client){ + if(_connect_cb){ + _connect_cb(_connect_cb_arg, client); + } + return ERR_OK; +} + +void AsyncServer::setNoDelay(bool nodelay){ + _noDelay = nodelay; +} + +bool AsyncServer::getNoDelay(){ + return _noDelay; +} + +uint8_t AsyncServer::status(){ + if (!_pcb) { + return 0; + } + return _pcb->state; +} + +int8_t AsyncServer::_s_accept(void * arg, tcp_pcb * pcb, int8_t err){ + return reinterpret_cast(arg)->_accept(pcb, err); +} + +int8_t AsyncServer::_s_accepted(void *arg, AsyncClient* client){ + return reinterpret_cast(arg)->_accepted(client); +} diff --git a/libraries/AsyncTCP/src/AsyncTCP.h b/libraries/AsyncTCP/src/AsyncTCP.h new file mode 100644 index 0000000..fef890b --- /dev/null +++ b/libraries/AsyncTCP/src/AsyncTCP.h @@ -0,0 +1,215 @@ +/* + Asynchronous TCP library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ASYNCTCP_H_ +#define ASYNCTCP_H_ + +#include "IPAddress.h" +#include "sdkconfig.h" +#include +extern "C" { + #include "freertos/semphr.h" + #include "lwip/pbuf.h" +} + +//If core is not defined, then we are running in Arduino or PIO +#ifndef CONFIG_ASYNC_TCP_RUNNING_CORE +#define CONFIG_ASYNC_TCP_RUNNING_CORE -1 //any available core +#define CONFIG_ASYNC_TCP_USE_WDT 1 //if enabled, adds between 33us and 200us per event +#endif + +class AsyncClient; + +#define ASYNC_MAX_ACK_TIME 5000 +#define ASYNC_WRITE_FLAG_COPY 0x01 //will allocate new buffer to hold the data while sending (else will hold reference to the data given) +#define ASYNC_WRITE_FLAG_MORE 0x02 //will not send PSH flag, meaning that there should be more data to be sent before the application should react. + +typedef std::function AcConnectHandler; +typedef std::function AcAckHandler; +typedef std::function AcErrorHandler; +typedef std::function AcDataHandler; +typedef std::function AcPacketHandler; +typedef std::function AcTimeoutHandler; + +struct tcp_pcb; +struct ip_addr; + +class AsyncClient { + public: + AsyncClient(tcp_pcb* pcb = 0); + ~AsyncClient(); + + AsyncClient & operator=(const AsyncClient &other); + AsyncClient & operator+=(const AsyncClient &other); + + bool operator==(const AsyncClient &other); + + bool operator!=(const AsyncClient &other) { + return !(*this == other); + } + bool connect(IPAddress ip, uint16_t port); + bool connect(const char* host, uint16_t port); + void close(bool now = false); + void stop(); + int8_t abort(); + bool free(); + + bool canSend();//ack is not pending + size_t space();//space available in the TCP window + size_t add(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY);//add for sending + bool send();//send all data added with the method above + + //write equals add()+send() + size_t write(const char* data); + size_t write(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY); //only when canSend() == true + + uint8_t state(); + bool connecting(); + bool connected(); + bool disconnecting(); + bool disconnected(); + bool freeable();//disconnected or disconnecting + + uint16_t getMss(); + + uint32_t getRxTimeout(); + void setRxTimeout(uint32_t timeout);//no RX data timeout for the connection in seconds + + uint32_t getAckTimeout(); + void setAckTimeout(uint32_t timeout);//no ACK timeout for the last sent packet in milliseconds + + void setNoDelay(bool nodelay); + bool getNoDelay(); + + uint32_t getRemoteAddress(); + uint16_t getRemotePort(); + uint32_t getLocalAddress(); + uint16_t getLocalPort(); + + //compatibility + IPAddress remoteIP(); + uint16_t remotePort(); + IPAddress localIP(); + uint16_t localPort(); + + void onConnect(AcConnectHandler cb, void* arg = 0); //on successful connect + void onDisconnect(AcConnectHandler cb, void* arg = 0); //disconnected + void onAck(AcAckHandler cb, void* arg = 0); //ack received + void onError(AcErrorHandler cb, void* arg = 0); //unsuccessful connect or error + void onData(AcDataHandler cb, void* arg = 0); //data received (called if onPacket is not used) + void onPacket(AcPacketHandler cb, void* arg = 0); //data received + void onTimeout(AcTimeoutHandler cb, void* arg = 0); //ack timeout + void onPoll(AcConnectHandler cb, void* arg = 0); //every 125ms when connected + + void ackPacket(struct pbuf * pb);//ack pbuf from onPacket + size_t ack(size_t len); //ack data that you have not acked using the method below + void ackLater(){ _ack_pcb = false; } //will not ack the current packet. Call from onData + + const char * errorToString(int8_t error); + const char * stateToString(); + + //Do not use any of the functions below! + static int8_t _s_poll(void *arg, struct tcp_pcb *tpcb); + static int8_t _s_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *pb, int8_t err); + static int8_t _s_fin(void *arg, struct tcp_pcb *tpcb, int8_t err); + static int8_t _s_lwip_fin(void *arg, struct tcp_pcb *tpcb, int8_t err); + static void _s_error(void *arg, int8_t err); + static int8_t _s_sent(void *arg, struct tcp_pcb *tpcb, uint16_t len); + static int8_t _s_connected(void* arg, void* tpcb, int8_t err); + static void _s_dns_found(const char *name, struct ip_addr *ipaddr, void *arg); + + int8_t _recv(tcp_pcb* pcb, pbuf* pb, int8_t err); + tcp_pcb * pcb(){ return _pcb; } + + protected: + tcp_pcb* _pcb; + int8_t _closed_slot; + + AcConnectHandler _connect_cb; + void* _connect_cb_arg; + AcConnectHandler _discard_cb; + void* _discard_cb_arg; + AcAckHandler _sent_cb; + void* _sent_cb_arg; + AcErrorHandler _error_cb; + void* _error_cb_arg; + AcDataHandler _recv_cb; + void* _recv_cb_arg; + AcPacketHandler _pb_cb; + void* _pb_cb_arg; + AcTimeoutHandler _timeout_cb; + void* _timeout_cb_arg; + AcConnectHandler _poll_cb; + void* _poll_cb_arg; + + bool _pcb_busy; + uint32_t _pcb_sent_at; + bool _ack_pcb; + uint32_t _rx_ack_len; + uint32_t _rx_last_packet; + uint32_t _rx_since_timeout; + uint32_t _ack_timeout; + uint16_t _connect_port; + + int8_t _close(); + int8_t _connected(void* pcb, int8_t err); + void _error(int8_t err); + int8_t _poll(tcp_pcb* pcb); + int8_t _sent(tcp_pcb* pcb, uint16_t len); + int8_t _fin(tcp_pcb* pcb, int8_t err); + int8_t _lwip_fin(tcp_pcb* pcb, int8_t err); + void _dns_found(struct ip_addr *ipaddr); + + public: + AsyncClient* prev; + AsyncClient* next; +}; + +class AsyncServer { + public: + AsyncServer(IPAddress addr, uint16_t port); + AsyncServer(uint16_t port); + ~AsyncServer(); + void onClient(AcConnectHandler cb, void* arg); + void begin(); + void end(); + void setNoDelay(bool nodelay); + bool getNoDelay(); + uint8_t status(); + + //Do not use any of the functions below! + static int8_t _s_accept(void *arg, tcp_pcb* newpcb, int8_t err); + static int8_t _s_accepted(void *arg, AsyncClient* client); + + protected: + uint16_t _port; + IPAddress _addr; + bool _noDelay; + tcp_pcb* _pcb; + AcConnectHandler _connect_cb; + void* _connect_cb_arg; + + int8_t _accept(tcp_pcb* newpcb, int8_t err); + int8_t _accepted(AsyncClient* client); +}; + + +#endif /* ASYNCTCP_H_ */ diff --git a/libraries/AsyncTCP/travis/build-pio.sh b/libraries/AsyncTCP/travis/build-pio.sh new file mode 100644 index 0000000..083b473 --- /dev/null +++ b/libraries/AsyncTCP/travis/build-pio.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +echo -e "travis_fold:start:install_pio" +pip install -U platformio +if [ $? -ne 0 ]; then exit 1; fi +echo -e "travis_fold:end:install_pio" + +echo -e "travis_fold:start:install_lib" +python -m platformio lib --storage-dir $TRAVIS_BUILD_DIR install +if [ $? -ne 0 ]; then exit 1; fi +echo -e "travis_fold:end:install_lib" + +echo -e "travis_fold:start:test_pio" +if [ -d "$TRAVIS_BUILD_DIR/examples" ]; then + for EXAMPLE in $TRAVIS_BUILD_DIR/examples/*/*.ino; do + python -m platformio ci $EXAMPLE -l '.' -b esp32dev + if [ $? -ne 0 ]; then exit 1; fi + done +fi +echo -e "travis_fold:end:test_pio" + +echo -e "travis_fold:start:install_json" +python -m platformio lib -g install https://github.com/bblanchon/ArduinoJson.git +if [ $? -ne 0 ]; then exit 1; fi +echo -e "travis_fold:end:install_json" + +cd $HOME/ +echo -e "travis_fold:start:install_web_server" +git clone https://github.com/me-no-dev/ESPAsyncWebServer +if [ $? -ne 0 ]; then exit 1; fi +python -m platformio lib --storage-dir $HOME/ESPAsyncWebServer install +if [ $? -ne 0 ]; then exit 1; fi +echo -e "travis_fold:end:install_web_server" + +echo -e "travis_fold:start:test_web_server" +for EXAMPLE in $HOME/ESPAsyncWebServer/examples/*/*.ino; do + python -m platformio ci $EXAMPLE -l $TRAVIS_BUILD_DIR -l $HOME/ESPAsyncWebServer -b esp32dev + if [ $? -ne 0 ]; then exit 1; fi +done +echo -e "travis_fold:end:test_web_server" diff --git a/libraries/AsyncTCP/travis/build.sh b/libraries/AsyncTCP/travis/build.sh new file mode 100644 index 0000000..4992310 --- /dev/null +++ b/libraries/AsyncTCP/travis/build.sh @@ -0,0 +1,207 @@ +#!/bin/bash + +CHUNK_INDEX=$1 +CHUNKS_CNT=$2 +if [ "$#" -lt 2 ]; then + echo "Building all sketches" + CHUNK_INDEX=0 + CHUNKS_CNT=1 +fi +if [ "$CHUNKS_CNT" -le 0 ]; then + echo "Chunks count must be positive number" + exit 1 +fi +if [ "$CHUNK_INDEX" -ge "$CHUNKS_CNT" ]; then + echo "Chunk index must be less than chunks count" + exit 1 +fi + +echo -e "travis_fold:start:prep_arduino_ide" +# Install Arduino IDE +wget -O arduino.tar.xz https://www.arduino.cc/download.php?f=/arduino-nightly-linux64.tar.xz +tar xf arduino.tar.xz +mv arduino-nightly $HOME/arduino_ide +mkdir -p $HOME/Arduino/libraries +mkdir -p $HOME/Arduino/hardware +echo -e "travis_fold:end:prep_arduino_ide" + +echo -e "travis_fold:start:sketch_test_env_prepare" +cd $HOME/Arduino/libraries +cp -rf $TRAVIS_BUILD_DIR AsyncTCP +PLATFORM_EXAMPLES=$TRAVIS_BUILD_DIR/examples + +git clone https://github.com/me-no-dev/ESPAsyncWebServer +git clone https://github.com/bblanchon/ArduinoJson +LIB_EXAMPLES=$HOME/Arduino/libraries/ESPAsyncWebServer/examples + +cd $HOME/Arduino/hardware +pip install pyserial +mkdir espressif +cd espressif +git clone https://github.com/espressif/arduino-esp32.git esp32 +cd esp32 +git submodule update --init --recursive +cd tools +python get.py +PLATFORM_FQBN="espressif:esp32:esp32" +PLATFORM_SIZE_BIN=$HOME/Arduino/hardware/espressif/esp32/tools/xtensa-esp32-elf/bin/xtensa-esp32-elf-size +echo -e "travis_fold:end:sketch_test_env_prepare" + +cd $TRAVIS_BUILD_DIR + +ARDUINO_IDE_PATH=$HOME/arduino_ide +ARDUINO_USR_PATH=$HOME/Arduino +ARDUINO_BUILD_DIR=$HOME/build.tmp +ARDUINO_CACHE_DIR=$HOME/cache.tmp +ARDUINO_BUILD_CMD="$ARDUINO_IDE_PATH/arduino-builder -compile -logger=human -core-api-version=10810 -hardware \"$ARDUINO_IDE_PATH/hardware\" -hardware \"$ARDUINO_USR_PATH/hardware\" -tools \"$ARDUINO_IDE_PATH/tools-builder\" -built-in-libraries \"$ARDUINO_IDE_PATH/libraries\" -libraries \"$ARDUINO_USR_PATH/libraries\" -fqbn=$PLATFORM_FQBN -warnings=\"all\" -build-cache \"$ARDUINO_CACHE_DIR\" -build-path \"$ARDUINO_BUILD_DIR\" -verbose" + +function print_size_info() +{ + elf_file=$1 + + if [ -z "$elf_file" ]; then + printf "sketch iram0.text flash.text flash.rodata dram0.data dram0.bss dram flash\n" + return 0 + fi + + elf_name=$(basename $elf_file) + sketch_name="${elf_name%.*}" + declare -A segments + while read -a tokens; do + seg=${tokens[0]} + seg=${seg//./} + size=${tokens[1]} + addr=${tokens[2]} + if [ "$addr" -eq "$addr" -a "$addr" -ne "0" ] 2>/dev/null; then + segments[$seg]=$size + fi + done < <($PLATFORM_SIZE_BIN --format=sysv $elf_file) + + total_ram=$((${segments[dram0data]} + ${segments[dram0bss]})) + total_flash=$((${segments[iram0text]} + ${segments[flashtext]} + ${segments[dram0data]} + ${segments[flashrodata]})) + printf "%-32s %-8d %-8d %-8d %-8d %-8d %-8d %-8d\n" $sketch_name ${segments[iram0text]} ${segments[flashtext]} ${segments[flashrodata]} ${segments[dram0data]} ${segments[dram0bss]} $total_ram $total_flash + return 0 +} + +function build_sketch() +{ + local sketch=$1 + echo -e "\n------------ Building $sketch ------------\n"; + rm -rf $ARDUINO_BUILD_DIR/* + time ($ARDUINO_BUILD_CMD $sketch >build.log) + local result=$? + if [ $result -ne 0 ]; then + echo "Build failed ($1)" + echo "Build log:" + cat build.log + return $result + fi + rm build.log + return 0 +} + +function count_sketches() +{ + local path=$1 + if [ ! -d "$path" ]; then + return 0 + fi + local sketches=$(find $path -name *.ino) + local sketchnum=0 + for sketch in $sketches; do + local sketchdir=$(dirname $sketch) + local sketchdirname=$(basename $sketchdir) + local sketchname=$(basename $sketch) + if [[ "${sketchdirname}.ino" != "$sketchname" ]]; then + continue + fi + echo $sketch >> sketches.txt + sketchnum=$(($sketchnum + 1)) + done + return $sketchnum +} + +function build_sketches() +{ + mkdir -p $ARDUINO_BUILD_DIR + mkdir -p $ARDUINO_CACHE_DIR + mkdir -p $ARDUINO_USR_PATH/libraries + mkdir -p $ARDUINO_USR_PATH/hardware + + local chunk_idex=$1 + local chunks_num=$2 + rm -rf sketches.txt + count_sketches $PLATFORM_EXAMPLES + local sketchcount=$? + count_sketches $LIB_EXAMPLES + local libsketchcount=$? + sketchcount=$(($sketchcount + $libsketchcount)) + local sketches=$(cat sketches.txt) + + local chunk_size=$(( $sketchcount / $chunks_num )) + local all_chunks=$(( $chunks_num * $chunk_size )) + if [ "$all_chunks" -lt "$sketchcount" ]; then + chunk_size=$(( $chunk_size + 1 )) + fi + + local start_index=$(( $chunk_idex * $chunk_size )) + if [ "$sketchcount" -le "$start_index" ]; then + echo "Skipping job" + return 0 + fi + + local end_index=$(( $(( $chunk_idex + 1 )) * $chunk_size )) + if [ "$end_index" -gt "$sketchcount" ]; then + end_index=$sketchcount + fi + + local start_num=$(( $start_index + 1 )) + #echo -e "Sketches: \n$sketches\n" + echo "Found $sketchcount Sketches"; + echo "Chunk Count : $chunks_num" + echo "Chunk Size : $chunk_size" + echo "Start Sketch: $start_num" + echo "End Sketch : $end_index" + + local sketchnum=0 + print_size_info >size.log + for sketch in $sketches; do + local sketchdir=$(dirname $sketch) + local sketchdirname=$(basename $sketchdir) + local sketchname=$(basename $sketch) + if [[ "${sketchdirname}.ino" != "$sketchname" ]]; then + #echo "Skipping $sketch, beacause it is not the main sketch file"; + continue + fi; + if [[ -f "$sketchdir/.test.skip" ]]; then + #echo "Skipping $sketch marked"; + continue + fi + sketchnum=$(($sketchnum + 1)) + if [ "$sketchnum" -le "$start_index" ]; then + #echo "Skipping $sketch index low" + continue + fi + if [ "$sketchnum" -gt "$end_index" ]; then + #echo "Skipping $sketch index high" + continue + fi + build_sketch $sketch + local result=$? + if [ $result -ne 0 ]; then + return $result + fi + print_size_info $ARDUINO_BUILD_DIR/*.elf >>size.log + done + return 0 +} + +echo -e "travis_fold:start:test_arduino_ide" +# Build Examples +build_sketches $CHUNK_INDEX $CHUNKS_CNT +if [ $? -ne 0 ]; then exit 1; fi +echo -e "travis_fold:end:test_arduino_ide" + +echo -e "travis_fold:start:size_report" +cat size.log +echo -e "travis_fold:end:size_report" diff --git a/libraries/ESPAsyncTCP/.gitignore b/libraries/ESPAsyncTCP/.gitignore new file mode 100644 index 0000000..9bea433 --- /dev/null +++ b/libraries/ESPAsyncTCP/.gitignore @@ -0,0 +1,2 @@ + +.DS_Store diff --git a/libraries/ESPAsyncTCP/.travis.yml b/libraries/ESPAsyncTCP/.travis.yml new file mode 100644 index 0000000..bc36dba --- /dev/null +++ b/libraries/ESPAsyncTCP/.travis.yml @@ -0,0 +1,34 @@ +sudo: false +language: python +os: + - linux + +git: + depth: false + +stages: + - build + +jobs: + include: + + - name: "Arduino Build" + if: tag IS blank AND (type = pull_request OR (type = push AND branch = master)) + stage: build + script: bash $TRAVIS_BUILD_DIR/travis/build.sh + + - name: "PlatformIO Build" + if: tag IS blank AND (type = pull_request OR (type = push AND branch = master)) + stage: build + script: bash $TRAVIS_BUILD_DIR/travis/build-pio.sh + +notifications: + email: + on_success: change + on_failure: change + webhooks: + urls: + - https://webhooks.gitter.im/e/60e65d0c78ea0a920347 + on_success: change # options: [always|never|change] default: always + on_failure: always # options: [always|never|change] default: always + on_start: false # default: false diff --git a/libraries/ESPAsyncTCP/LICENSE.txt b/libraries/ESPAsyncTCP/LICENSE.txt new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/libraries/ESPAsyncTCP/LICENSE.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/libraries/ESPAsyncTCP/README.md b/libraries/ESPAsyncTCP/README.md new file mode 100644 index 0000000..593af58 --- /dev/null +++ b/libraries/ESPAsyncTCP/README.md @@ -0,0 +1,32 @@ +# ESPAsyncTCP +[![Build Status](https://travis-ci.org/me-no-dev/ESPAsyncTCP.svg?branch=master)](https://travis-ci.org/me-no-dev/ESPAsyncTCP) ![](https://github.com/me-no-dev/ESPAsyncTCP/workflows/ESP%20Async%20TCP%20CI/badge.svg) + +### Async TCP Library for ESP8266 Arduino + +For ESP32 look [HERE](https://github.com/me-no-dev/AsyncTCP) + +[![Join the chat at https://gitter.im/me-no-dev/ESPAsyncWebServer](https://badges.gitter.im/me-no-dev/ESPAsyncWebServer.svg)](https://gitter.im/me-no-dev/ESPAsyncWebServer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +This is a fully asynchronous TCP library, aimed at enabling trouble-free, multi-connection network environment for Espressif's ESP8266 MCUs. + +This library is the base for [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) + +## AsyncClient and AsyncServer +The base classes on which everything else is built. They expose all possible scenarios, but are really raw and require more skills to use. + +## AsyncPrinter +This class can be used to send data like any other ```Print``` interface (```Serial``` for example). +The object then can be used outside of the Async callbacks (the loop) and receive asynchronously data using ```onData```. The object can be checked if the underlying ```AsyncClient```is connected, or hook to the ```onDisconnect``` callback. + +## AsyncTCPbuffer +This class is really similar to the ```AsyncPrinter```, but it differs in the fact that it can buffer some of the incoming data. + +## SyncClient +It is exactly what it sounds like. This is a standard, blocking TCP Client, similar to the one included in ```ESP8266WiFi``` + +## Libraries and projects that use AsyncTCP +- [ESP Async Web Server](https://github.com/me-no-dev/ESPAsyncWebServer) +- [Async MQTT client](https://github.com/marvinroger/async-mqtt-client) +- [arduinoWebSockets](https://github.com/Links2004/arduinoWebSockets) +- [ESP8266 Smart Home](https://github.com/baruch/esp8266_smart_home) +- [KBox Firmware](https://github.com/sarfata/kbox-firmware) diff --git a/libraries/ESPAsyncTCP/examples/ClientServer/Client/Client.ino b/libraries/ESPAsyncTCP/examples/ClientServer/Client/Client.ino new file mode 100644 index 0000000..b30d791 --- /dev/null +++ b/libraries/ESPAsyncTCP/examples/ClientServer/Client/Client.ino @@ -0,0 +1,62 @@ +#include +#include + +extern "C" { +#include +#include +} + +#include "config.h" + +static os_timer_t intervalTimer; + +static void replyToServer(void* arg) { + AsyncClient* client = reinterpret_cast(arg); + + // send reply + if (client->space() > 32 && client->canSend()) { + char message[32]; + sprintf(message, "this is from %s", WiFi.localIP().toString().c_str()); + client->add(message, strlen(message)); + client->send(); + } +} + +/* event callbacks */ +static void handleData(void* arg, AsyncClient* client, void *data, size_t len) { + Serial.printf("\n data received from %s \n", client->remoteIP().toString().c_str()); + Serial.write((uint8_t*)data, len); + + os_timer_arm(&intervalTimer, 2000, true); // schedule for reply to server at next 2s +} + +void onConnect(void* arg, AsyncClient* client) { + Serial.printf("\n client has been connected to %s on port %d \n", SERVER_HOST_NAME, TCP_PORT); + replyToServer(client); +} + + +void setup() { + Serial.begin(115200); + delay(20); + + // connects to access point + WiFi.mode(WIFI_STA); + WiFi.begin(SSID, PASSWORD); + while (WiFi.status() != WL_CONNECTED) { + Serial.print('.'); + delay(500); + } + + AsyncClient* client = new AsyncClient; + client->onData(&handleData, client); + client->onConnect(&onConnect, client); + client->connect(SERVER_HOST_NAME, TCP_PORT); + + os_timer_disarm(&intervalTimer); + os_timer_setfn(&intervalTimer, &replyToServer, client); +} + +void loop() { + +} diff --git a/libraries/ESPAsyncTCP/examples/ClientServer/Client/config.h b/libraries/ESPAsyncTCP/examples/ClientServer/Client/config.h new file mode 100644 index 0000000..cf51e91 --- /dev/null +++ b/libraries/ESPAsyncTCP/examples/ClientServer/Client/config.h @@ -0,0 +1,23 @@ +#ifndef CONFIG_H +#define CONFIG_H + +/* + * This example demonstrate how to use asynchronous client & server APIs + * in order to establish tcp socket connections in client server manner. + * server is running (on port 7050) on one ESP, acts as AP, and other clients running on + * remaining ESPs acts as STAs. after connection establishment between server and clients + * there is a simple message transfer in every 2s. clients connect to server via it's host name + * (in this case 'esp_server') with help of DNS service running on server side. + * + * Note: default MSS for ESPAsyncTCP is 536 byte and defualt ACK timeout is 5s. +*/ + +#define SSID "ESP-TEST" +#define PASSWORD "123456789" + +#define SERVER_HOST_NAME "esp_server" + +#define TCP_PORT 7050 +#define DNS_PORT 53 + +#endif // CONFIG_H diff --git a/libraries/ESPAsyncTCP/examples/ClientServer/Server/Server.ino b/libraries/ESPAsyncTCP/examples/ClientServer/Server/Server.ino new file mode 100644 index 0000000..c8c9b7f --- /dev/null +++ b/libraries/ESPAsyncTCP/examples/ClientServer/Server/Server.ino @@ -0,0 +1,73 @@ +#include +#include +#include +#include + +#include "config.h" + +static DNSServer DNS; + +static std::vector clients; // a list to hold all clients + + /* clients events */ +static void handleError(void* arg, AsyncClient* client, int8_t error) { + Serial.printf("\n connection error %s from client %s \n", client->errorToString(error), client->remoteIP().toString().c_str()); +} + +static void handleData(void* arg, AsyncClient* client, void *data, size_t len) { + Serial.printf("\n data received from client %s \n", client->remoteIP().toString().c_str()); + Serial.write((uint8_t*)data, len); + + // reply to client + if (client->space() > 32 && client->canSend()) { + char reply[32]; + sprintf(reply, "this is from %s", SERVER_HOST_NAME); + client->add(reply, strlen(reply)); + client->send(); + } +} + +static void handleDisconnect(void* arg, AsyncClient* client) { + Serial.printf("\n client %s disconnected \n", client->remoteIP().toString().c_str()); +} + +static void handleTimeOut(void* arg, AsyncClient* client, uint32_t time) { + Serial.printf("\n client ACK timeout ip: %s \n", client->remoteIP().toString().c_str()); +} + + +/* server events */ +static void handleNewClient(void* arg, AsyncClient* client) { + Serial.printf("\n new client has been connected to server, ip: %s", client->remoteIP().toString().c_str()); + + // add to list + clients.push_back(client); + + // register events + client->onData(&handleData, NULL); + client->onError(&handleError, NULL); + client->onDisconnect(&handleDisconnect, NULL); + client->onTimeout(&handleTimeOut, NULL); +} + +void setup() { + Serial.begin(115200); + delay(20); + + // create access point + while (!WiFi.softAP(SSID, PASSWORD, 6, false, 15)) { + delay(500); + } + + // start dns server + if (!DNS.start(DNS_PORT, SERVER_HOST_NAME, WiFi.softAPIP())) + Serial.printf("\n failed to start dns service \n"); + + AsyncServer* server = new AsyncServer(TCP_PORT); // start listening on tcp port 7050 + server->onClient(&handleNewClient, server); + server->begin(); +} + +void loop() { + DNS.processNextRequest(); +} diff --git a/libraries/ESPAsyncTCP/examples/ClientServer/Server/config.h b/libraries/ESPAsyncTCP/examples/ClientServer/Server/config.h new file mode 100644 index 0000000..cf51e91 --- /dev/null +++ b/libraries/ESPAsyncTCP/examples/ClientServer/Server/config.h @@ -0,0 +1,23 @@ +#ifndef CONFIG_H +#define CONFIG_H + +/* + * This example demonstrate how to use asynchronous client & server APIs + * in order to establish tcp socket connections in client server manner. + * server is running (on port 7050) on one ESP, acts as AP, and other clients running on + * remaining ESPs acts as STAs. after connection establishment between server and clients + * there is a simple message transfer in every 2s. clients connect to server via it's host name + * (in this case 'esp_server') with help of DNS service running on server side. + * + * Note: default MSS for ESPAsyncTCP is 536 byte and defualt ACK timeout is 5s. +*/ + +#define SSID "ESP-TEST" +#define PASSWORD "123456789" + +#define SERVER_HOST_NAME "esp_server" + +#define TCP_PORT 7050 +#define DNS_PORT 53 + +#endif // CONFIG_H diff --git a/libraries/ESPAsyncTCP/examples/SyncClient/.esp31b.skip b/libraries/ESPAsyncTCP/examples/SyncClient/.esp31b.skip new file mode 100644 index 0000000..e69de29 diff --git a/libraries/ESPAsyncTCP/examples/SyncClient/SyncClient.ino b/libraries/ESPAsyncTCP/examples/SyncClient/SyncClient.ino new file mode 100644 index 0000000..6ecc525 --- /dev/null +++ b/libraries/ESPAsyncTCP/examples/SyncClient/SyncClient.ino @@ -0,0 +1,54 @@ +#ifdef ESP8266 +#include +#include +#include +#else +#include +#endif +#include "ESPAsyncTCP.h" +#include "SyncClient.h" + +const char* ssid = "**********"; +const char* password = "************"; + +void setup(){ + Serial.begin(115200); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.printf("WiFi Failed!\n"); + return; + } + Serial.printf("WiFi Connected!\n"); + Serial.println(WiFi.localIP()); +#ifdef ESP8266 + ArduinoOTA.begin(); +#endif + + SyncClient client; + if(!client.connect("www.google.com", 80)){ + Serial.println("Connect Failed"); + return; + } + client.setTimeout(2); + if(client.printf("GET / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n") > 0){ + while(client.connected() && client.available() == 0){ + delay(1); + } + while(client.available()){ + Serial.write(client.read()); + } + if(client.connected()){ + client.stop(); + } + } else { + client.stop(); + Serial.println("Send Failed"); + while(client.connected()) delay(0); + } +} + +void loop(){ +#ifdef ESP8266 + ArduinoOTA.handle(); +#endif +} diff --git a/libraries/ESPAsyncTCP/library.json b/libraries/ESPAsyncTCP/library.json new file mode 100644 index 0000000..d71d3fc --- /dev/null +++ b/libraries/ESPAsyncTCP/library.json @@ -0,0 +1,22 @@ +{ + "name":"ESPAsyncTCP", + "description":"Asynchronous TCP Library for ESP8266", + "keywords":"async,tcp", + "authors": + { + "name": "Hristo Gochkov", + "maintainer": true + }, + "repository": + { + "type": "git", + "url": "https://github.com/me-no-dev/ESPAsyncTCP.git" + }, + "version": "1.2.1", + "license": "LGPL-3.0", + "frameworks": "arduino", + "platforms": "espressif8266", + "build": { + "libCompatMode": 2 + } +} diff --git a/libraries/ESPAsyncTCP/library.properties b/libraries/ESPAsyncTCP/library.properties new file mode 100644 index 0000000..2059535 --- /dev/null +++ b/libraries/ESPAsyncTCP/library.properties @@ -0,0 +1,9 @@ +name=ESP AsyncTCP +version=1.2.1 +author=Me-No-Dev +maintainer=Me-No-Dev +sentence=Async TCP Library for ESP8266 and ESP31B +paragraph=Async TCP Library for ESP8266 and ESP31B +category=Other +url=https://github.com/me-no-dev/ESPAsyncTCP +architectures=* diff --git a/libraries/ESPAsyncTCP/src/AsyncPrinter.cpp b/libraries/ESPAsyncTCP/src/AsyncPrinter.cpp new file mode 100644 index 0000000..8a63f20 --- /dev/null +++ b/libraries/ESPAsyncTCP/src/AsyncPrinter.cpp @@ -0,0 +1,214 @@ +/* + Asynchronous TCP library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "AsyncPrinter.h" + +AsyncPrinter::AsyncPrinter() + : _client(NULL) + , _data_cb(NULL) + , _data_arg(NULL) + , _close_cb(NULL) + , _close_arg(NULL) + , _tx_buffer(NULL) + , _tx_buffer_size(TCP_MSS) + , next(NULL) +{} + +AsyncPrinter::AsyncPrinter(AsyncClient *client, size_t txBufLen) + : _client(client) + , _data_cb(NULL) + , _data_arg(NULL) + , _close_cb(NULL) + , _close_arg(NULL) + , _tx_buffer(NULL) + , _tx_buffer_size(txBufLen) + , next(NULL) +{ + _attachCallbacks(); + _tx_buffer = new (std::nothrow) cbuf(_tx_buffer_size); + if(_tx_buffer == NULL) { + panic(); //What should we do? + } +} + +AsyncPrinter::~AsyncPrinter(){ + _on_close(); +} + +void AsyncPrinter::onData(ApDataHandler cb, void *arg){ + _data_cb = cb; + _data_arg = arg; +} + +void AsyncPrinter::onClose(ApCloseHandler cb, void *arg){ + _close_cb = cb; + _close_arg = arg; +} + +int AsyncPrinter::connect(IPAddress ip, uint16_t port){ + if(_client != NULL && connected()) + return 0; + _client = new (std::nothrow) AsyncClient(); + if (_client == NULL) { + panic(); + } + + _client->onConnect([](void *obj, AsyncClient *c){ ((AsyncPrinter*)(obj))->_onConnect(c); }, this); + if(_client->connect(ip, port)){ + while(_client && _client->state() < 4) + delay(1); + return connected(); + } + return 0; +} + +int AsyncPrinter::connect(const char *host, uint16_t port){ + if(_client != NULL && connected()) + return 0; + _client = new (std::nothrow) AsyncClient(); + if (_client == NULL) { + panic(); + } + + _client->onConnect([](void *obj, AsyncClient *c){ ((AsyncPrinter*)(obj))->_onConnect(c); }, this); + if(_client->connect(host, port)){ + while(_client && _client->state() < 4) + delay(1); + return connected(); + } + return 0; +} + +void AsyncPrinter::_onConnect(AsyncClient *c){ + (void)c; + if(_tx_buffer != NULL){ + cbuf *b = _tx_buffer; + _tx_buffer = NULL; + delete b; + } + _tx_buffer = new (std::nothrow) cbuf(_tx_buffer_size); + if(_tx_buffer) { + panic(); + } + + _attachCallbacks(); +} + +AsyncPrinter::operator bool(){ return connected(); } + +AsyncPrinter & AsyncPrinter::operator=(const AsyncPrinter &other){ + if(_client != NULL){ + _client->close(true); + _client = NULL; + } + _tx_buffer_size = other._tx_buffer_size; + if(_tx_buffer != NULL){ + cbuf *b = _tx_buffer; + _tx_buffer = NULL; + delete b; + } + _tx_buffer = new (std::nothrow) cbuf(other._tx_buffer_size); + if(_tx_buffer == NULL) { + panic(); + } + + _client = other._client; + _attachCallbacks(); + return *this; +} + +size_t AsyncPrinter::write(uint8_t data){ + return write(&data, 1); +} + +size_t AsyncPrinter::write(const uint8_t *data, size_t len){ + if(_tx_buffer == NULL || !connected()) + return 0; + size_t toWrite = 0; + size_t toSend = len; + while(_tx_buffer->room() < toSend){ + toWrite = _tx_buffer->room(); + _tx_buffer->write((const char*)data, toWrite); + while(connected() && !_client->canSend()) + delay(0); + if(!connected()) + return 0; // or len - toSend; + _sendBuffer(); + toSend -= toWrite; + } + _tx_buffer->write((const char*)(data+(len - toSend)), toSend); + while(connected() && !_client->canSend()) delay(0); + if(!connected()) return 0; // or len - toSend; + _sendBuffer(); + return len; +} + +bool AsyncPrinter::connected(){ + return (_client != NULL && _client->connected()); +} + +void AsyncPrinter::close(){ + if(_client != NULL) + _client->close(true); +} + +size_t AsyncPrinter::_sendBuffer(){ + size_t available = _tx_buffer->available(); + if(!connected() || !_client->canSend() || available == 0) + return 0; + size_t sendable = _client->space(); + if(sendable < available) + available= sendable; + char *out = new (std::nothrow) char[available]; + if (out == NULL) { + panic(); // Connection should be aborted instead + } + + _tx_buffer->read(out, available); + size_t sent = _client->write(out, available); + delete out; + return sent; +} + +void AsyncPrinter::_onData(void *data, size_t len){ + if(_data_cb) + _data_cb(_data_arg, this, (uint8_t*)data, len); +} + +void AsyncPrinter::_on_close(){ + if(_client != NULL){ + _client = NULL; + } + if(_tx_buffer != NULL){ + cbuf *b = _tx_buffer; + _tx_buffer = NULL; + delete b; + } + if(_close_cb) + _close_cb(_close_arg, this); +} + +void AsyncPrinter::_attachCallbacks(){ + _client->onPoll([](void *obj, AsyncClient* c){ (void)c; ((AsyncPrinter*)(obj))->_sendBuffer(); }, this); + _client->onAck([](void *obj, AsyncClient* c, size_t len, uint32_t time){ (void)c; (void)len; (void)time; ((AsyncPrinter*)(obj))->_sendBuffer(); }, this); + _client->onDisconnect([](void *obj, AsyncClient* c){ ((AsyncPrinter*)(obj))->_on_close(); delete c; }, this); + _client->onData([](void *obj, AsyncClient* c, void *data, size_t len){ (void)c; ((AsyncPrinter*)(obj))->_onData(data, len); }, this); +} diff --git a/libraries/ESPAsyncTCP/src/AsyncPrinter.h b/libraries/ESPAsyncTCP/src/AsyncPrinter.h new file mode 100644 index 0000000..c3ebe3a --- /dev/null +++ b/libraries/ESPAsyncTCP/src/AsyncPrinter.h @@ -0,0 +1,73 @@ +/* + Asynchronous TCP library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ASYNCPRINTER_H_ +#define ASYNCPRINTER_H_ + +#include "Arduino.h" +#include "ESPAsyncTCP.h" +#include "cbuf.h" + +class AsyncPrinter; + +typedef std::function ApDataHandler; +typedef std::function ApCloseHandler; + +class AsyncPrinter: public Print { + private: + AsyncClient *_client; + ApDataHandler _data_cb; + void *_data_arg; + ApCloseHandler _close_cb; + void *_close_arg; + cbuf *_tx_buffer; + size_t _tx_buffer_size; + + void _onConnect(AsyncClient *c); + public: + AsyncPrinter *next; + + AsyncPrinter(); + AsyncPrinter(AsyncClient *client, size_t txBufLen = TCP_MSS); + virtual ~AsyncPrinter(); + + int connect(IPAddress ip, uint16_t port); + int connect(const char *host, uint16_t port); + + void onData(ApDataHandler cb, void *arg); + void onClose(ApCloseHandler cb, void *arg); + + operator bool(); + AsyncPrinter & operator=(const AsyncPrinter &other); + + size_t write(uint8_t data); + size_t write(const uint8_t *data, size_t len); + + bool connected(); + void close(); + + size_t _sendBuffer(); + void _onData(void *data, size_t len); + void _on_close(); + void _attachCallbacks(); +}; + +#endif /* ASYNCPRINTER_H_ */ diff --git a/libraries/ESPAsyncTCP/src/DebugPrintMacros.h b/libraries/ESPAsyncTCP/src/DebugPrintMacros.h new file mode 100644 index 0000000..29accaf --- /dev/null +++ b/libraries/ESPAsyncTCP/src/DebugPrintMacros.h @@ -0,0 +1,96 @@ +#ifndef _DEBUG_PRINT_MACROS_H +#define _DEBUG_PRINT_MACROS_H +// Some customizable print macros to suite the debug needs de jour. + +// Debug macros +// #include +// https://stackoverflow.com/questions/8487986/file-macro-shows-full-path +// This value is resolved at compile time. +#define _FILENAME_ strrchr("/" __FILE__, '/') + +// #define DEBUG_ESP_ASYNC_TCP 1 +// #define DEBUG_ESP_TCP_SSL 1 +// #define DEBUG_ESP_PORT Serial + +#if defined(DEBUG_ESP_PORT) && !defined(DEBUG_TIME_STAMP_FMT) +#define DEBUG_TIME_STAMP_FMT "%06u.%03u " +struct _DEBUG_TIME_STAMP { + unsigned dec; + unsigned whole; +}; +inline struct _DEBUG_TIME_STAMP debugTimeStamp(void) { + struct _DEBUG_TIME_STAMP st; + unsigned now = millis() % 1000000000; + st.dec = now % 1000; + st.whole = now / 1000; + return st; +} +#endif + +#if defined(DEBUG_ESP_PORT) && !defined(DEBUG_GENERIC) + #define DEBUG_GENERIC( module, format, ... ) \ + do { \ + struct _DEBUG_TIME_STAMP st = debugTimeStamp(); \ + DEBUG_ESP_PORT.printf( DEBUG_TIME_STAMP_FMT module " " format, st.whole, st.dec, ##__VA_ARGS__ ); \ + } while(false) +#endif +#if defined(DEBUG_ESP_PORT) && !defined(DEBUG_GENERIC_P) + #define DEBUG_GENERIC_P( module, format, ... ) \ + do { \ + struct _DEBUG_TIME_STAMP st = debugTimeStamp(); \ + DEBUG_ESP_PORT.printf_P(PSTR( DEBUG_TIME_STAMP_FMT module " " format ), st.whole, st.dec, ##__VA_ARGS__ ); \ + } while(false) +#endif + +#if defined(DEBUG_GENERIC) && !defined(ASSERT_GENERIC) +#define ASSERT_GENERIC( a, module ) \ + do { \ + if ( !(a) ) { \ + DEBUG_GENERIC( module, "%s:%s:%u: ASSERT("#a") failed!\n", __FILE__, __func__, __LINE__); \ + DEBUG_ESP_PORT.flush(); \ + } \ + } while(false) +#endif +#if defined(DEBUG_GENERIC_P) && !defined(ASSERT_GENERIC_P) +#define ASSERT_GENERIC_P( a, module ) \ + do { \ + if ( !(a) ) { \ + DEBUG_GENERIC_P( module, "%s:%s:%u: ASSERT("#a") failed!\n", __FILE__, __func__, __LINE__); \ + DEBUG_ESP_PORT.flush(); \ + } \ + } while(false) +#endif + +#ifndef DEBUG_GENERIC +#define DEBUG_GENERIC(...) do { (void)0;} while(false) +#endif + +#ifndef DEBUG_GENERIC_P +#define DEBUG_GENERIC_P(...) do { (void)0;} while(false) +#endif + +#ifndef ASSERT_GENERIC +#define ASSERT_GENERIC(...) do { (void)0;} while(false) +#endif + +#ifndef ASSERT_GENERIC_P +#define ASSERT_GENERIC_P(...) do { (void)0;} while(false) +#endif + +#ifndef DEBUG_ESP_PRINTF +#define DEBUG_ESP_PRINTF( format, ...) DEBUG_GENERIC_P("[%s]", format, &_FILENAME_[1], ##__VA_ARGS__) +#endif + +#if defined(DEBUG_ESP_ASYNC_TCP) && !defined(ASYNC_TCP_DEBUG) +#define ASYNC_TCP_DEBUG( format, ...) DEBUG_GENERIC_P("[ASYNC_TCP]", format, ##__VA_ARGS__) +#endif + +#ifndef ASYNC_TCP_ASSERT +#define ASYNC_TCP_ASSERT( a ) ASSERT_GENERIC_P( (a), "[ASYNC_TCP]") +#endif + +#if defined(DEBUG_ESP_TCP_SSL) && !defined(TCP_SSL_DEBUG) +#define TCP_SSL_DEBUG( format, ...) DEBUG_GENERIC_P("[TCP_SSL]", format, ##__VA_ARGS__) +#endif + +#endif //_DEBUG_PRINT_MACROS_H diff --git a/libraries/ESPAsyncTCP/src/ESPAsyncTCP.cpp b/libraries/ESPAsyncTCP/src/ESPAsyncTCP.cpp new file mode 100644 index 0000000..7a9fdc7 --- /dev/null +++ b/libraries/ESPAsyncTCP/src/ESPAsyncTCP.cpp @@ -0,0 +1,1394 @@ +/* + Asynchronous TCP library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +/* +Changes for July 2019 + +The operator "new ..." was changed to "new (std::nothrow) ...", which will +return NULL when the heap is out of memory. Without the change "soft WDT" +was the result, starting with Arduino ESP8266 Core 2.5.0. (Note, RE:"soft +WDT" - the error reporting may improve with core 2.6.) With proir core +versions the library appears to work fine. +ref: https://github.com/esp8266/Arduino/issues/6269#issue-464978944 + +To support newer lwIP versions and buffer models. All references to 1460 +were replaced with TCP_MSS. If TCP_MSS is not defined (exp. 1.4v lwIP) +1460 is assumed. + +The ESPAsyncTCP library should build for Arduino ESP8266 Core releases: +2.3.0, 2.4.1, 2.4.2, 2.5.1, 2.5.2. It may still build with core versions +2.4.0 and 2.5.0. I did not do any regression testing with these, since +they had too many issues and were quickly superseded. + +lwIP tcp_err() callback often resulted in crashes. The problem was a +tcp_err() would come in, while processing a send or receive in the +forground. The tcp_err() callback would be passed down to a client's +registered disconnect CB. A common problem with SyncClient and other +modules as well as some client code was: the freeing of ESPAsyncTCP +AsyncClient objects via disconnect CB handlers while the library was +waiting for an operstion to finished. Attempts to access bad pointers +followed. For SyncClient this commonly occured during a call to delay(). +On return to SyncClient _client was invalid. Also the problem described by +issue #94 also surfaced + +Use of tcp_abort() required some very special handling and was very +challenging to make work without changing client API. ERR_ABRT can only be +used once on a return to lwIP for a given connection and since the +AsyncClient structure was sometimes deleted before returning to lwIP, the +state tracking became tricky. While ugly, a global variable for this +seemed to work; however, I abanded it when I saw a possible +reentrancy/concurrency issue. After several approaches I settled the +problem by creating "class ACErrorTracker" to manage the issue. + + +Additional Async Client considerations: + +The client sketch must always test if the connection is still up at loop() +entry and after the return of any function call, that may have done a +delay() or yield() or any ESPAsyncTCP library family call. For example, +the connection could be lost during a call to _client->write(...). Client +sketches that delete _client as part of their onDisconnect() handler must +be very careful as _client will become invalid after calls to delay(), +yield(), etc. + + + */ +#include "Arduino.h" + +#include "ESPAsyncTCP.h" +extern "C"{ + #include "lwip/opt.h" + #include "lwip/tcp.h" + #include "lwip/inet.h" + #include "lwip/dns.h" + #include "lwip/init.h" +} +#include + +/* + Async Client Error Return Tracker +*/ +// Assumption: callbacks are never called with err == ERR_ABRT; however, +// they may return ERR_ABRT. + +ACErrorTracker::ACErrorTracker(AsyncClient *c): + _client(c) + , _close_error(ERR_OK) + , _errored(EE_OK) +#ifdef DEBUG_MORE + , _error_event_cb(NULL) + , _error_event_cb_arg(NULL) +#endif +{} + +#ifdef DEBUG_MORE +/** + * This is not necessary, but a start at gathering some statistics on + * errored out connections. Used from AsyncServer. + */ +void ACErrorTracker::onErrorEvent(AsNotifyHandler cb, void *arg) { + _error_event_cb = cb; + _error_event_cb_arg = arg; +} +#endif + +void ACErrorTracker::setCloseError(err_t e) { + if (e != ERR_OK) + ASYNC_TCP_DEBUG("setCloseError() to: %s(%ld)\n", _client->errorToString(e), e); + if(_errored == EE_OK) + _close_error = e; +} +/** + * Called mainly by callback routines, called when err is not ERR_OK. + * This prevents the possiblity of aborting an already errored out + * connection. + */ +void ACErrorTracker::setErrored(size_t errorEvent){ + if(EE_OK == _errored) + _errored = errorEvent; +#ifdef DEBUG_MORE + if (_error_event_cb) + _error_event_cb(_error_event_cb_arg, errorEvent); +#endif +} +/** + * Used by callback functions only. Used for proper ERR_ABRT return value + * reporting. ERR_ABRT is only reported/returned once; thereafter ERR_OK + * is always returned. + */ +err_t ACErrorTracker::getCallbackCloseError(void){ + if (EE_OK != _errored) + return ERR_OK; + if (ERR_ABRT == _close_error) + setErrored(EE_ABORTED); + return _close_error; +} + +/* + Async TCP Client +*/ +#if DEBUG_ESP_ASYNC_TCP +static size_t _connectionCount=0; +#endif + +#if ASYNC_TCP_SSL_ENABLED +AsyncClient::AsyncClient(tcp_pcb* pcb, SSL_CTX * ssl_ctx): +#else +AsyncClient::AsyncClient(tcp_pcb* pcb): +#endif + _connect_cb(0) + , _connect_cb_arg(0) + , _discard_cb(0) + , _discard_cb_arg(0) + , _sent_cb(0) + , _sent_cb_arg(0) + , _error_cb(0) + , _error_cb_arg(0) + , _recv_cb(0) + , _recv_cb_arg(0) + , _pb_cb(0) + , _pb_cb_arg(0) + , _timeout_cb(0) + , _timeout_cb_arg(0) + , _poll_cb(0) + , _poll_cb_arg(0) + , _pcb_busy(false) +#if ASYNC_TCP_SSL_ENABLED + , _pcb_secure(false) + , _handshake_done(true) +#endif + , _pcb_sent_at(0) + , _close_pcb(false) + , _ack_pcb(true) + , _tx_unacked_len(0) + , _tx_acked_len(0) + , _tx_unsent_len(0) + , _rx_ack_len(0) + , _rx_last_packet(0) + , _rx_since_timeout(0) + , _ack_timeout(ASYNC_MAX_ACK_TIME) + , _connect_port(0) + , _recv_pbuf_flags(0) + , _errorTracker(NULL) + , prev(NULL) + , next(NULL) +{ + _pcb = pcb; + if(_pcb){ + _rx_last_packet = millis(); + tcp_setprio(_pcb, TCP_PRIO_MIN); + tcp_arg(_pcb, this); + tcp_recv(_pcb, &_s_recv); + tcp_sent(_pcb, &_s_sent); + tcp_err(_pcb, &_s_error); + tcp_poll(_pcb, &_s_poll, 1); +#if ASYNC_TCP_SSL_ENABLED + if(ssl_ctx){ + if(tcp_ssl_new_server(_pcb, ssl_ctx) < 0){ + _close(); + return; + } + tcp_ssl_arg(_pcb, this); + tcp_ssl_data(_pcb, &_s_data); + tcp_ssl_handshake(_pcb, &_s_handshake); + tcp_ssl_err(_pcb, &_s_ssl_error); + + _pcb_secure = true; + _handshake_done = false; + } +#endif + } + + _errorTracker = std::make_shared(this); +#if DEBUG_ESP_ASYNC_TCP + _errorTracker->setConnectionId(++_connectionCount); +#endif +} + +AsyncClient::~AsyncClient(){ + if(_pcb) + _close(); + + _errorTracker->clearClient(); +} + +inline void clearTcpCallbacks(tcp_pcb* pcb){ + tcp_arg(pcb, NULL); + tcp_sent(pcb, NULL); + tcp_recv(pcb, NULL); + tcp_err(pcb, NULL); + tcp_poll(pcb, NULL, 0); +} + +#if ASYNC_TCP_SSL_ENABLED +bool AsyncClient::connect(IPAddress ip, uint16_t port, bool secure){ +#else +bool AsyncClient::connect(IPAddress ip, uint16_t port){ +#endif + if (_pcb) //already connected + return false; + ip_addr_t addr; + addr.addr = ip; +#if LWIP_VERSION_MAJOR == 1 + netif* interface = ip_route(&addr); + if (!interface){ //no route to host + return false; + } +#endif + tcp_pcb* pcb = tcp_new(); + if (!pcb){ //could not allocate pcb + return false; + } + + tcp_setprio(pcb, TCP_PRIO_MIN); +#if ASYNC_TCP_SSL_ENABLED + _pcb_secure = secure; + _handshake_done = !secure; +#endif + tcp_arg(pcb, this); + tcp_err(pcb, &_s_error); + size_t err = tcp_connect(pcb, &addr, port,(tcp_connected_fn)&_s_connected); + return (ERR_OK == err); +} + +#if ASYNC_TCP_SSL_ENABLED +bool AsyncClient::connect(const char* host, uint16_t port, bool secure){ +#else +bool AsyncClient::connect(const char* host, uint16_t port){ +#endif + ip_addr_t addr; + err_t err = dns_gethostbyname(host, &addr, (dns_found_callback)&_s_dns_found, this); + if(err == ERR_OK) { +#if ASYNC_TCP_SSL_ENABLED + return connect(IPAddress(addr.addr), port, secure); +#else + return connect(IPAddress(addr.addr), port); +#endif + } else if(err == ERR_INPROGRESS) { +#if ASYNC_TCP_SSL_ENABLED + _pcb_secure = secure; + _handshake_done = !secure; +#endif + _connect_port = port; + return true; + } + return false; +} + +AsyncClient& AsyncClient::operator=(const AsyncClient& other){ + if (_pcb) { + ASYNC_TCP_DEBUG("operator=[%u]: Abandoned _pcb(0x%" PRIXPTR ") forced close.\n", getConnectionId(), uintptr_t(_pcb)); + _close(); + } + _errorTracker = other._errorTracker; + + // I am confused when "other._pcb" falls out of scope the destructor will + // close it? TODO: Look to see where this is used and how it might work. + _pcb = other._pcb; + if (_pcb) { + _rx_last_packet = millis(); + tcp_setprio(_pcb, TCP_PRIO_MIN); + tcp_arg(_pcb, this); + tcp_recv(_pcb, &_s_recv); + tcp_sent(_pcb, &_s_sent); + tcp_err(_pcb, &_s_error); + tcp_poll(_pcb, &_s_poll, 1); +#if ASYNC_TCP_SSL_ENABLED + if(tcp_ssl_has(_pcb)){ + _pcb_secure = true; + _handshake_done = false; + tcp_ssl_arg(_pcb, this); + tcp_ssl_data(_pcb, &_s_data); + tcp_ssl_handshake(_pcb, &_s_handshake); + tcp_ssl_err(_pcb, &_s_ssl_error); + } else { + _pcb_secure = false; + _handshake_done = true; + } +#endif + } + return *this; +} + +bool AsyncClient::operator==(const AsyncClient &other) { + return (_pcb != NULL && other._pcb != NULL && (_pcb->remote_ip.addr == other._pcb->remote_ip.addr) && (_pcb->remote_port == other._pcb->remote_port)); +} + +void AsyncClient::abort(){ + // Notes: + // 1) _pcb is set to NULL, so we cannot call tcp_abort() more than once. + // 2) setCloseError(ERR_ABRT) is only done here! + // 3) Using this abort() function guarantees only one tcp_abort() call is + // made and only one CB returns with ERR_ABORT. + // 4) After abort() is called from _close(), no callbacks with an err + // parameter will be called. eg. _recv(), _error(), _connected(). + // _close() will reset there CB handlers before calling. + // 5) A callback to _error(), will set _pcb to NULL, thus avoiding the + // of a 2nd call to tcp_abort(). + // 6) Callbacks to _recv() or _connected() with err set, will result in _pcb + // set to NULL. Thus, preventing possible calls later to tcp_abort(). + if(_pcb) { + tcp_abort(_pcb); + _pcb = NULL; + setCloseError(ERR_ABRT); + } + return; +} + +void AsyncClient::close(bool now){ + if(_pcb) + tcp_recved(_pcb, _rx_ack_len); + if(now) + _close(); + else + _close_pcb = true; +} + +void AsyncClient::stop() { + close(false); +} + +bool AsyncClient::free(){ + if(!_pcb) + return true; + if(_pcb->state == 0 || _pcb->state > 4) + return true; + return false; +} + +size_t AsyncClient::write(const char* data) { + if(data == NULL) + return 0; + return write(data, strlen(data)); +} + +size_t AsyncClient::write(const char* data, size_t size, uint8_t apiflags) { + size_t will_send = add(data, size, apiflags); + + if(!will_send || !send()) + return 0; + return will_send; +} + +size_t AsyncClient::add(const char* data, size_t size, uint8_t apiflags) { + if(!_pcb || size == 0 || data == NULL) + return 0; + size_t room = space(); + if(!room) + return 0; +#if ASYNC_TCP_SSL_ENABLED + if(_pcb_secure){ + int sent = tcp_ssl_write(_pcb, (uint8_t*)data, size); + if(sent >= 0){ + _tx_unacked_len += sent; + return sent; + } + _close(); + return 0; + } +#endif + size_t will_send = (room < size) ? room : size; + err_t err = tcp_write(_pcb, data, will_send, apiflags); + if(err != ERR_OK) { + ASYNC_TCP_DEBUG("_add[%u]: tcp_write() returned err: %s(%ld)\n", getConnectionId(), errorToString(err), err); + return 0; + } + _tx_unsent_len += will_send; + return will_send; +} + +bool AsyncClient::send(){ +#if ASYNC_TCP_SSL_ENABLED + if(_pcb_secure) + return true; +#endif + err_t err = tcp_output(_pcb); + if(err == ERR_OK){ + _pcb_busy = true; + _pcb_sent_at = millis(); + _tx_unacked_len += _tx_unsent_len; + _tx_unsent_len = 0; + return true; + } + + ASYNC_TCP_DEBUG("send[%u]: tcp_output() returned err: %s(%ld)", getConnectionId(), errorToString(err), err); + _tx_unsent_len = 0; + return false; +} + +size_t AsyncClient::ack(size_t len){ + if(len > _rx_ack_len) + len = _rx_ack_len; + if(len) + tcp_recved(_pcb, len); + _rx_ack_len -= len; + return len; +} + +// Private Callbacks + +void AsyncClient::_connected(std::shared_ptr& errorTracker, void* pcb, err_t err){ + //(void)err; // LWIP v1.4 appears to always call with ERR_OK + // Documentation for 2.1.0 also says: + // "err - An unused error code, always ERR_OK currently ;-)" + // https://www.nongnu.org/lwip/2_1_x/tcp_8h.html#a939867106bd492caf2d85852fb7f6ae8 + // Based on that wording and emoji lets just handle it now. + // After all, the API does allow for an err != ERR_OK. + if(NULL == pcb || ERR_OK != err) { + ASYNC_TCP_DEBUG("_connected[%u]:%s err: %s(%ld)\n", errorTracker->getConnectionId(), ((NULL == pcb) ? " NULL == pcb!," : ""), errorToString(err), err); + errorTracker->setCloseError(err); + errorTracker->setErrored(EE_CONNECTED_CB); + _pcb = reinterpret_cast(pcb); + if (_pcb) + clearTcpCallbacks(_pcb); + _pcb = NULL; + _error(err); + return; + } + + _pcb = reinterpret_cast(pcb); + if(_pcb){ + _pcb_busy = false; + _rx_last_packet = millis(); + tcp_setprio(_pcb, TCP_PRIO_MIN); + tcp_recv(_pcb, &_s_recv); + tcp_sent(_pcb, &_s_sent); + tcp_poll(_pcb, &_s_poll, 1); +#if ASYNC_TCP_SSL_ENABLED + if(_pcb_secure){ + if(tcp_ssl_new_client(_pcb) < 0){ + _close(); + return; + } + tcp_ssl_arg(_pcb, this); + tcp_ssl_data(_pcb, &_s_data); + tcp_ssl_handshake(_pcb, &_s_handshake); + tcp_ssl_err(_pcb, &_s_ssl_error); + } + } + if(!_pcb_secure && _connect_cb) +#else + } + if(_connect_cb) +#endif + _connect_cb(_connect_cb_arg, this); + return; +} + +void AsyncClient::_close(){ + if(_pcb) { +#if ASYNC_TCP_SSL_ENABLED + if(_pcb_secure){ + tcp_ssl_free(_pcb); + } +#endif + clearTcpCallbacks(_pcb); + err_t err = tcp_close(_pcb); + if(ERR_OK == err) { + setCloseError(err); + } else { + ASYNC_TCP_DEBUG("_close[%u]: abort() called for AsyncClient 0x%" PRIXPTR "\n", getConnectionId(), uintptr_t(this)); + abort(); + } + _pcb = NULL; + if(_discard_cb) + _discard_cb(_discard_cb_arg, this); + } + return; +} + +void AsyncClient::_error(err_t err) { + ASYNC_TCP_DEBUG("_error[%u]:%s err: %s(%ld)\n", getConnectionId(), ((NULL == _pcb) ? " NULL == _pcb!," : ""), errorToString(err), err); + if(_pcb){ +#if ASYNC_TCP_SSL_ENABLED + if(_pcb_secure){ + tcp_ssl_free(_pcb); + } +#endif + // At this callback _pcb is possible already freed. Thus, no calls are + // made to set to NULL other callbacks. + _pcb = NULL; + } + if(_error_cb) + _error_cb(_error_cb_arg, this, err); + if(_discard_cb) + _discard_cb(_discard_cb_arg, this); +} + +#if ASYNC_TCP_SSL_ENABLED +void AsyncClient::_ssl_error(int8_t err){ + if(_error_cb) + _error_cb(_error_cb_arg, this, err+64); +} +#endif + +void AsyncClient::_sent(std::shared_ptr& errorTracker, tcp_pcb* pcb, uint16_t len) { + (void)pcb; +#if ASYNC_TCP_SSL_ENABLED + if (_pcb_secure && !_handshake_done) + return; +#endif + _rx_last_packet = millis(); + _tx_unacked_len -= len; + _tx_acked_len += len; + ASYNC_TCP_DEBUG("_sent[%u]: %4u, unacked=%4u, acked=%4u, space=%4u\n", errorTracker->getConnectionId(), len, _tx_unacked_len, _tx_acked_len, space()); + if(_tx_unacked_len == 0){ + _pcb_busy = false; + errorTracker->setCloseError(ERR_OK); + if(_sent_cb) { + _sent_cb(_sent_cb_arg, this, _tx_acked_len, (millis() - _pcb_sent_at)); + if(!errorTracker->hasClient()) + return; + } + _tx_acked_len = 0; + } + return; +} + +void AsyncClient::_recv(std::shared_ptr& errorTracker, tcp_pcb* pcb, pbuf* pb, err_t err) { + // While lwIP v1.4 appears to always call with ERR_OK, 2.x lwIP may present + // a non-ERR_OK value. + // https://www.nongnu.org/lwip/2_1_x/tcp_8h.html#a780cfac08b02c66948ab94ea974202e8 + if(NULL == pcb || ERR_OK != err){ + ASYNC_TCP_DEBUG("_recv[%u]:%s err: %s(%ld)\n", errorTracker->getConnectionId(), ((NULL == pcb) ? " NULL == pcb!," : ""), errorToString(err), err); + ASYNC_TCP_ASSERT(ERR_ABRT != err); + errorTracker->setCloseError(err); + errorTracker->setErrored(EE_RECV_CB); + _pcb = pcb; + if(_pcb) + clearTcpCallbacks(_pcb); + _pcb = NULL; + // I think we are safe from being called from an interrupt context. + // Best Hint that calling _error() is safe: + // https://www.nongnu.org/lwip/2_1_x/group__lwip__nosys.html + // "Feed incoming packets to netif->input(pbuf, netif) function from + // mainloop, not from interrupt context. You can allocate a Packet buffers + // (PBUF) in interrupt context and put them into a queue which is processed + // from mainloop." + // And the description of "Mainloop Mode" option 2: + // https://www.nongnu.org/lwip/2_1_x/pitfalls.html + // "2) Run lwIP in a mainloop. ... lwIP is ONLY called from mainloop + // callstacks here. The ethernet IRQ has to put received telegrams into a + // queue which is polled in the mainloop. Ensure lwIP is NEVER called from + // an interrupt, ...!" + // Based on these comments I am thinking tcp_recv_fn() is called + // from somebody's mainloop(), which could only have been reached from a + // delay like function or the Arduino sketch loop() function has returned. + // What I don't want is for the client sketch to delete the AsyncClient + // object via _error() while it is in the middle of using it. However, + // the client sketch must always test that the connection is still up + // at loop() entry and after the return of any function call, that may + // have done a delay() or yield(). + _error(err); + return; + } + + if(pb == NULL){ + ASYNC_TCP_DEBUG("_recv[%u]: pb == NULL! Closing... %ld\n", errorTracker->getConnectionId(), err); + _close(); + return; + } + _rx_last_packet = millis(); + errorTracker->setCloseError(ERR_OK); +#if ASYNC_TCP_SSL_ENABLED + if(_pcb_secure){ + ASYNC_TCP_DEBUG("_recv[%u]: %d\n", getConnectionId(), pb->tot_len); + int read_bytes = tcp_ssl_read(pcb, pb); + if(read_bytes < 0){ + if (read_bytes != SSL_CLOSE_NOTIFY) { + ASYNC_TCP_DEBUG("_recv[%u] err: %d\n", getConnectionId(), read_bytes); + _close(); + } + } + return; + } +#endif + while(pb != NULL){ + // IF this callback function returns ERR_OK or ERR_ABRT + // then it is assummed we freed the pbufs. + // https://www.nongnu.org/lwip/2_1_x/group__tcp__raw.html#ga8afd0b316a87a5eeff4726dc95006ed0 + if(!errorTracker->hasClient()){ + while(pb != NULL){ + pbuf *b = pb; + pb = b->next; + b->next = NULL; + pbuf_free(b); + } + return; + } + //we should not ack before we assimilate the data + _ack_pcb = true; + pbuf *b = pb; + pb = b->next; + b->next = NULL; + ASYNC_TCP_DEBUG("_recv[%u]: %d%s\n", errorTracker->getConnectionId(), b->len, (b->flags&PBUF_FLAG_PUSH)?", PBUF_FLAG_PUSH":""); + if(_pb_cb){ + _pb_cb(_pb_cb_arg, this, b); + } else { + if(_recv_cb){ + _recv_pbuf_flags = b->flags; + _recv_cb(_recv_cb_arg, this, b->payload, b->len); + } + if(errorTracker->hasClient()){ + if(!_ack_pcb) + _rx_ack_len += b->len; + else + tcp_recved(pcb, b->len); + } + pbuf_free(b); + } + } + return; +} + +void AsyncClient::_poll(std::shared_ptr& errorTracker, tcp_pcb* pcb){ + (void)pcb; + errorTracker->setCloseError(ERR_OK); + + // Close requested + if(_close_pcb){ + _close_pcb = false; + _close(); + return; + } + uint32_t now = millis(); + + // ACK Timeout + if(_pcb_busy && _ack_timeout && (now - _pcb_sent_at) >= _ack_timeout){ + _pcb_busy = false; + if(_timeout_cb) + _timeout_cb(_timeout_cb_arg, this, (now - _pcb_sent_at)); + return; + } + // RX Timeout + if(_rx_since_timeout && (now - _rx_last_packet) >= (_rx_since_timeout * 1000)){ + _close(); + return; + } +#if ASYNC_TCP_SSL_ENABLED + // SSL Handshake Timeout + if(_pcb_secure && !_handshake_done && (now - _rx_last_packet) >= 2000){ + _close(); + return; + } +#endif + // Everything is fine + if(_poll_cb) + _poll_cb(_poll_cb_arg, this); + return; +} + +#if LWIP_VERSION_MAJOR == 1 +void AsyncClient::_dns_found(struct ip_addr *ipaddr){ +#else +void AsyncClient::_dns_found(const ip_addr *ipaddr){ +#endif + if(ipaddr){ +#if ASYNC_TCP_SSL_ENABLED + connect(IPAddress(ipaddr->addr), _connect_port, _pcb_secure); +#else + connect(IPAddress(ipaddr->addr), _connect_port); +#endif + } else { + if(_error_cb) + _error_cb(_error_cb_arg, this, -55); + if(_discard_cb) + _discard_cb(_discard_cb_arg, this); + } +} + +// lwIP Callbacks +#if LWIP_VERSION_MAJOR == 1 +void AsyncClient::_s_dns_found(const char *name, ip_addr_t *ipaddr, void *arg){ +#else +void AsyncClient::_s_dns_found(const char *name, const ip_addr *ipaddr, void *arg){ +#endif + (void)name; + reinterpret_cast(arg)->_dns_found(ipaddr); +} + +err_t AsyncClient::_s_poll(void *arg, struct tcp_pcb *tpcb) { + AsyncClient *c = reinterpret_cast(arg); + std::shared_ptrerrorTracker = c->getACErrorTracker(); + c->_poll(errorTracker, tpcb); + return errorTracker->getCallbackCloseError(); +} + +err_t AsyncClient::_s_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *pb, err_t err) { + AsyncClient *c = reinterpret_cast(arg); + auto errorTracker = c->getACErrorTracker(); + c->_recv(errorTracker, tpcb, pb, err); + return errorTracker->getCallbackCloseError(); +} + +void AsyncClient::_s_error(void *arg, err_t err) { + AsyncClient *c = reinterpret_cast(arg); + auto errorTracker = c->getACErrorTracker(); + errorTracker->setCloseError(err); + errorTracker->setErrored(EE_ERROR_CB); + c->_error(err); +} + +err_t AsyncClient::_s_sent(void *arg, struct tcp_pcb *tpcb, uint16_t len) { + AsyncClient *c = reinterpret_cast(arg); + auto errorTracker = c->getACErrorTracker(); + c->_sent(errorTracker, tpcb, len); + return errorTracker->getCallbackCloseError(); +} + +err_t AsyncClient::_s_connected(void* arg, void* tpcb, err_t err){ + AsyncClient *c = reinterpret_cast(arg); + auto errorTracker = c->getACErrorTracker(); + c->_connected(errorTracker, tpcb, err); + return errorTracker->getCallbackCloseError(); +} + +#if ASYNC_TCP_SSL_ENABLED +void AsyncClient::_s_data(void *arg, struct tcp_pcb *tcp, uint8_t * data, size_t len){ + AsyncClient *c = reinterpret_cast(arg); + if(c->_recv_cb) + c->_recv_cb(c->_recv_cb_arg, c, data, len); +} + +void AsyncClient::_s_handshake(void *arg, struct tcp_pcb *tcp, SSL *ssl){ + AsyncClient *c = reinterpret_cast(arg); + c->_handshake_done = true; + if(c->_connect_cb) + c->_connect_cb(c->_connect_cb_arg, c); +} + +void AsyncClient::_s_ssl_error(void *arg, struct tcp_pcb *tcp, int8_t err){ + reinterpret_cast(arg)->_ssl_error(err); +} +#endif + +// Operators + +AsyncClient & AsyncClient::operator+=(const AsyncClient &other) { + if(next == NULL){ + next = (AsyncClient*)(&other); + next->prev = this; + } else { + AsyncClient *c = next; + while(c->next != NULL) c = c->next; + c->next =(AsyncClient*)(&other); + c->next->prev = c; + } + return *this; +} + +void AsyncClient::setRxTimeout(uint32_t timeout){ + _rx_since_timeout = timeout; +} + +uint32_t AsyncClient::getRxTimeout(){ + return _rx_since_timeout; +} + +uint32_t AsyncClient::getAckTimeout(){ + return _ack_timeout; +} + +void AsyncClient::setAckTimeout(uint32_t timeout){ + _ack_timeout = timeout; +} + +void AsyncClient::setNoDelay(bool nodelay){ + if(!_pcb) + return; + if(nodelay) + tcp_nagle_disable(_pcb); + else + tcp_nagle_enable(_pcb); +} + +bool AsyncClient::getNoDelay(){ + if(!_pcb) + return false; + return tcp_nagle_disabled(_pcb); +} + +uint16_t AsyncClient::getMss(){ + if(_pcb) + return tcp_mss(_pcb); + return 0; +} + +uint32_t AsyncClient::getRemoteAddress() { + if(!_pcb) + return 0; + return _pcb->remote_ip.addr; +} + +uint16_t AsyncClient::getRemotePort() { + if(!_pcb) + return 0; + return _pcb->remote_port; +} + +uint32_t AsyncClient::getLocalAddress() { + if(!_pcb) + return 0; + return _pcb->local_ip.addr; +} + +uint16_t AsyncClient::getLocalPort() { + if(!_pcb) + return 0; + return _pcb->local_port; +} + +IPAddress AsyncClient::remoteIP() { + return IPAddress(getRemoteAddress()); +} + +uint16_t AsyncClient::remotePort() { + return getRemotePort(); +} + +IPAddress AsyncClient::localIP() { + return IPAddress(getLocalAddress()); +} + +uint16_t AsyncClient::localPort() { + return getLocalPort(); +} + +#if ASYNC_TCP_SSL_ENABLED +SSL * AsyncClient::getSSL(){ + if(_pcb && _pcb_secure){ + return tcp_ssl_get_ssl(_pcb); + } + return NULL; +} +#endif + +uint8_t AsyncClient::state() { + if(!_pcb) + return 0; + return _pcb->state; +} + +bool AsyncClient::connected(){ + if (!_pcb) + return false; +#if ASYNC_TCP_SSL_ENABLED + return _pcb->state == 4 && _handshake_done; +#else + return _pcb->state == 4; +#endif +} + +bool AsyncClient::connecting(){ + if (!_pcb) + return false; + return _pcb->state > 0 && _pcb->state < 4; +} + +bool AsyncClient::disconnecting(){ + if (!_pcb) + return false; + return _pcb->state > 4 && _pcb->state < 10; +} + +bool AsyncClient::disconnected(){ + if (!_pcb) + return true; + return _pcb->state == 0 || _pcb->state == 10; +} + +bool AsyncClient::freeable(){ + if (!_pcb) + return true; + return _pcb->state == 0 || _pcb->state > 4; +} + +bool AsyncClient::canSend(){ + return !_pcb_busy && (space() > 0); +} + + +// Callback Setters + +void AsyncClient::onConnect(AcConnectHandler cb, void* arg){ + _connect_cb = cb; + _connect_cb_arg = arg; +} + +void AsyncClient::onDisconnect(AcConnectHandler cb, void* arg){ + _discard_cb = cb; + _discard_cb_arg = arg; +} + +void AsyncClient::onAck(AcAckHandler cb, void* arg){ + _sent_cb = cb; + _sent_cb_arg = arg; +} + +void AsyncClient::onError(AcErrorHandler cb, void* arg){ + _error_cb = cb; + _error_cb_arg = arg; +} + +void AsyncClient::onData(AcDataHandler cb, void* arg){ + _recv_cb = cb; + _recv_cb_arg = arg; +} + +void AsyncClient::onPacket(AcPacketHandler cb, void* arg){ + _pb_cb = cb; + _pb_cb_arg = arg; +} + +void AsyncClient::onTimeout(AcTimeoutHandler cb, void* arg){ + _timeout_cb = cb; + _timeout_cb_arg = arg; +} + +void AsyncClient::onPoll(AcConnectHandler cb, void* arg){ + _poll_cb = cb; + _poll_cb_arg = arg; +} + + +size_t AsyncClient::space(){ +#if ASYNC_TCP_SSL_ENABLED + if((_pcb != NULL) && (_pcb->state == 4) && _handshake_done){ + uint16_t s = tcp_sndbuf(_pcb); + if(_pcb_secure){ +#ifdef AXTLS_2_0_0_SNDBUF + return tcp_ssl_sndbuf(_pcb); +#else + if(s >= 128) //safe approach + return s - 128; + return 0; +#endif + } + return s; + } +#else // ASYNC_TCP_SSL_ENABLED + if((_pcb != NULL) && (_pcb->state == 4)){ + return tcp_sndbuf(_pcb); + } +#endif // ASYNC_TCP_SSL_ENABLED + return 0; +} + +void AsyncClient::ackPacket(struct pbuf * pb){ + if(!pb){ + return; + } + tcp_recved(_pcb, pb->len); + pbuf_free(pb); +} + +const char * AsyncClient::errorToString(err_t error) { + switch (error) { + case ERR_OK: return "No error, everything OK"; + case ERR_MEM: return "Out of memory error"; + case ERR_BUF: return "Buffer error"; + case ERR_TIMEOUT: return "Timeout"; + case ERR_RTE: return "Routing problem"; + case ERR_INPROGRESS: return "Operation in progress"; + case ERR_VAL: return "Illegal value"; + case ERR_WOULDBLOCK: return "Operation would block"; + case ERR_ABRT: return "Connection aborted"; + case ERR_RST: return "Connection reset"; + case ERR_CLSD: return "Connection closed"; + case ERR_CONN: return "Not connected"; + case ERR_ARG: return "Illegal argument"; + case ERR_USE: return "Address in use"; +#if defined(LWIP_VERSION_MAJOR) && (LWIP_VERSION_MAJOR > 1) + case ERR_ALREADY: return "Already connectioning"; +#endif + case ERR_IF: return "Low-level netif error"; + case ERR_ISCONN: return "Connection already established"; + case -55: return "DNS failed"; + default: return "Unknown error"; + } +} + +const char * AsyncClient::stateToString(){ + switch(state()){ + case 0: return "Closed"; + case 1: return "Listen"; + case 2: return "SYN Sent"; + case 3: return "SYN Received"; + case 4: return "Established"; + case 5: return "FIN Wait 1"; + case 6: return "FIN Wait 2"; + case 7: return "Close Wait"; + case 8: return "Closing"; + case 9: return "Last ACK"; + case 10: return "Time Wait"; + default: return "UNKNOWN"; + } +} + +/* + Async TCP Server +*/ +struct pending_pcb { + tcp_pcb* pcb; + pbuf *pb; + struct pending_pcb * next; +}; + +AsyncServer::AsyncServer(IPAddress addr, uint16_t port) + : _port(port) + , _addr(addr) + , _noDelay(false) + , _pcb(0) + , _connect_cb(0) + , _connect_cb_arg(0) +#if ASYNC_TCP_SSL_ENABLED + , _pending(NULL) + , _ssl_ctx(NULL) + , _file_cb(0) + , _file_cb_arg(0) +#endif +{ +#ifdef DEBUG_MORE + for (size_t i=0; inext; + if(p->pb){ + pbuf_free(p->pb); + } + free(p); + } + } + } +#endif +} + +void AsyncServer::setNoDelay(bool nodelay){ + _noDelay = nodelay; +} + +bool AsyncServer::getNoDelay(){ + return _noDelay; +} + +uint8_t AsyncServer::status(){ + if (!_pcb) + return 0; + return _pcb->state; +} + +err_t AsyncServer::_accept(tcp_pcb* pcb, err_t err){ + //http://savannah.nongnu.org/bugs/?43739 + if(NULL == pcb || ERR_OK != err){ + // https://www.nongnu.org/lwip/2_1_x/tcp_8h.html#a00517abce6856d6c82f0efebdafb734d + // An error code if there has been an error accepting. Only return ERR_ABRT + // if you have called tcp_abort from within the callback function! + // eg. 2.1.0 could call with error on failure to allocate pcb. + ASYNC_TCP_DEBUG("_accept:%s err: %ld\n", ((NULL == pcb) ? " NULL == pcb!," : ""), err); + ASYNC_TCP_ASSERT(ERR_ABRT != err); +#ifdef DEBUG_MORE + incEventCount(EE_ACCEPT_CB); +#endif + return ERR_OK; + } + + if(_connect_cb){ +#if ASYNC_TCP_SSL_ENABLED + if (_noDelay || _ssl_ctx) +#else + if (_noDelay) +#endif + tcp_nagle_disable(pcb); + else + tcp_nagle_enable(pcb); + +#if ASYNC_TCP_SSL_ENABLED + if(_ssl_ctx){ + if(tcp_ssl_has_client() || _pending){ + struct pending_pcb * new_item = (struct pending_pcb*)malloc(sizeof(struct pending_pcb)); + if(!new_item){ + ASYNC_TCP_DEBUG("### malloc new pending failed!\n"); + if(tcp_close(pcb) != ERR_OK){ + tcp_abort(pcb); + return ERR_ABRT; + } + return ERR_OK; + } + ASYNC_TCP_DEBUG("### put to wait: %d\n", _clients_waiting); + new_item->pcb = pcb; + new_item->pb = NULL; + new_item->next = NULL; + tcp_setprio(_pcb, TCP_PRIO_MIN); + tcp_arg(pcb, this); + tcp_poll(pcb, &_s_poll, 1); + tcp_recv(pcb, &_s_recv); + + if(_pending == NULL){ + _pending = new_item; + } else { + struct pending_pcb * p = _pending; + while(p->next != NULL) + p = p->next; + p->next = new_item; + } + } else { + AsyncClient *c = new (std::nothrow) AsyncClient(pcb, _ssl_ctx); + if(c){ + ASYNC_TCP_DEBUG("_accept[%u]: SSL connected\n", c->getConnectionId()); + c->onConnect([this](void * arg, AsyncClient *c){ + _connect_cb(_connect_cb_arg, c); + }, this); + } else { + ASYNC_TCP_DEBUG("_accept[_ssl_ctx]: new AsyncClient() failed, connection aborted!\n"); + if(tcp_close(pcb) != ERR_OK){ + tcp_abort(pcb); + return ERR_ABRT; + } + } + } + return ERR_OK; + } else { + AsyncClient *c = new (std::nothrow) AsyncClient(pcb, NULL); +#else + AsyncClient *c = new (std::nothrow) AsyncClient(pcb); +#endif + + if(c){ + auto errorTracker = c->getACErrorTracker(); +#ifdef DEBUG_MORE + errorTracker->onErrorEvent( + [](void *obj, size_t ee){ ((AsyncServer*)(obj))->incEventCount(ee); }, + this); +#endif + ASYNC_TCP_DEBUG("_accept[%u]: connected\n", errorTracker->getConnectionId()); + _connect_cb(_connect_cb_arg, c); + return errorTracker->getCallbackCloseError(); + } else { + ASYNC_TCP_DEBUG("_accept: new AsyncClient() failed, connection aborted!\n"); + if(tcp_close(pcb) != ERR_OK){ + tcp_abort(pcb); + return ERR_ABRT; + } + } +#if ASYNC_TCP_SSL_ENABLED + } +#endif + } + if(tcp_close(pcb) != ERR_OK){ + tcp_abort(pcb); + return ERR_ABRT; + } + return ERR_OK; +} + +err_t AsyncServer::_s_accept(void *arg, tcp_pcb* pcb, err_t err){ + return reinterpret_cast(arg)->_accept(pcb, err); +} + +#if ASYNC_TCP_SSL_ENABLED +err_t AsyncServer::_poll(tcp_pcb* pcb){ + if(!tcp_ssl_has_client() && _pending){ + struct pending_pcb * p = _pending; + if(p->pcb == pcb){ + _pending = _pending->next; + } else { + while(p->next && p->next->pcb != pcb) p = p->next; + if(!p->next) return 0; + struct pending_pcb * b = p->next; + p->next = b->next; + p = b; + } + ASYNC_TCP_DEBUG("### remove from wait: %d\n", _clients_waiting); + AsyncClient *c = new (std::nothrow) AsyncClient(pcb, _ssl_ctx); + if(c){ + c->onConnect([this](void * arg, AsyncClient *c){ + _connect_cb(_connect_cb_arg, c); + }, this); + if(p->pb) + c->_recv(pcb, p->pb, 0); + } + // Should there be error handling for when "new AsynClient" fails?? + free(p); + } + return ERR_OK; +} + +err_t AsyncServer::_recv(struct tcp_pcb *pcb, struct pbuf *pb, err_t err){ + if(!_pending) + return ERR_OK; + + struct pending_pcb * p; + + if(!pb){ + ASYNC_TCP_DEBUG("### close from wait: %d\n", _clients_waiting); + p = _pending; + if(p->pcb == pcb){ + _pending = _pending->next; + } else { + while(p->next && p->next->pcb != pcb) p = p->next; + if(!p->next) return 0; + struct pending_pcb * b = p->next; + p->next = b->next; + p = b; + } + if(p->pb){ + pbuf_free(p->pb); + } + free(p); + size_t err = tcp_close(pcb); + if (err != ERR_OK) { + tcp_abort(pcb); + return ERR_ABRT; + } + } else { + ASYNC_TCP_DEBUG("### wait _recv: %u %d\n", pb->tot_len, _clients_waiting); + p = _pending; + while(p && p->pcb != pcb) + p = p->next; + if(p){ + if(p->pb){ + pbuf_chain(p->pb, pb); + } else { + p->pb = pb; + } + } + } + return ERR_OK; +} + +int AsyncServer::_cert(const char *filename, uint8_t **buf){ + if(_file_cb){ + return _file_cb(_file_cb_arg, filename, buf); + } + *buf = 0; + return 0; +} + +int AsyncServer::_s_cert(void *arg, const char *filename, uint8_t **buf){ + return reinterpret_cast(arg)->_cert(filename, buf); +} + +err_t AsyncServer::_s_poll(void *arg, struct tcp_pcb *pcb){ + return reinterpret_cast(arg)->_poll(pcb); +} + +err_t AsyncServer::_s_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *pb, err_t err){ + return reinterpret_cast(arg)->_recv(pcb, pb, err); +} +#endif diff --git a/libraries/ESPAsyncTCP/src/ESPAsyncTCP.h b/libraries/ESPAsyncTCP/src/ESPAsyncTCP.h new file mode 100644 index 0000000..2d1f768 --- /dev/null +++ b/libraries/ESPAsyncTCP/src/ESPAsyncTCP.h @@ -0,0 +1,327 @@ +/* + Asynchronous TCP library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ASYNCTCP_H_ +#define ASYNCTCP_H_ + +#include +#include "IPAddress.h" +#include +#include + +extern "C" { + #include "lwip/init.h" + #include "lwip/err.h" + #include "lwip/pbuf.h" +}; + +class AsyncClient; +class AsyncServer; +class ACErrorTracker; + +#define ASYNC_MAX_ACK_TIME 5000 +#define ASYNC_WRITE_FLAG_COPY 0x01 //will allocate new buffer to hold the data while sending (else will hold reference to the data given) +#define ASYNC_WRITE_FLAG_MORE 0x02 //will not send PSH flag, meaning that there should be more data to be sent before the application should react. + +struct tcp_pcb; +struct ip_addr; +#if ASYNC_TCP_SSL_ENABLED +struct SSL_; +typedef struct SSL_ SSL; +struct SSL_CTX_; +typedef struct SSL_CTX_ SSL_CTX; +#endif + +typedef std::function AcConnectHandler; +typedef std::function AcAckHandler; +typedef std::function AcErrorHandler; +typedef std::function AcDataHandler; +typedef std::function AcPacketHandler; +typedef std::function AcTimeoutHandler; +typedef std::function AsNotifyHandler; + +enum error_events { + EE_OK = 0, + EE_ABORTED, // Callback or foreground aborted connections + EE_ERROR_CB, // Stack initiated aborts via error Callbacks. + EE_CONNECTED_CB, + EE_RECV_CB, + EE_ACCEPT_CB, + EE_MAX +}; +// DEBUG_MORE is for gathering more information on which CBs close events are +// occuring and count. +// #define DEBUG_MORE 1 +class ACErrorTracker { + private: + AsyncClient *_client; + err_t _close_error; + int _errored; +#if DEBUG_ESP_ASYNC_TCP + size_t _connectionId; +#endif +#ifdef DEBUG_MORE + AsNotifyHandler _error_event_cb; + void* _error_event_cb_arg; +#endif + + protected: + friend class AsyncClient; + friend class AsyncServer; +#ifdef DEBUG_MORE + void onErrorEvent(AsNotifyHandler cb, void *arg); +#endif +#if DEBUG_ESP_ASYNC_TCP + void setConnectionId(size_t id) { _connectionId=id;} + size_t getConnectionId(void) { return _connectionId;} +#endif + void setCloseError(err_t e); + void setErrored(size_t errorEvent); + err_t getCallbackCloseError(void); + void clearClient(void){ if (_client) _client = NULL;} + + public: + err_t getCloseError(void) const { return _close_error;} + bool hasClient(void) const { return (_client != NULL);} + ACErrorTracker(AsyncClient *c); + ~ACErrorTracker() {} +}; + +class AsyncClient { + protected: + friend class AsyncTCPbuffer; + friend class AsyncServer; + tcp_pcb* _pcb; + AcConnectHandler _connect_cb; + void* _connect_cb_arg; + AcConnectHandler _discard_cb; + void* _discard_cb_arg; + AcAckHandler _sent_cb; + void* _sent_cb_arg; + AcErrorHandler _error_cb; + void* _error_cb_arg; + AcDataHandler _recv_cb; + void* _recv_cb_arg; + AcPacketHandler _pb_cb; + void* _pb_cb_arg; + AcTimeoutHandler _timeout_cb; + void* _timeout_cb_arg; + AcConnectHandler _poll_cb; + void* _poll_cb_arg; + bool _pcb_busy; +#if ASYNC_TCP_SSL_ENABLED + bool _pcb_secure; + bool _handshake_done; +#endif + uint32_t _pcb_sent_at; + bool _close_pcb; + bool _ack_pcb; + uint32_t _tx_unacked_len; + uint32_t _tx_acked_len; + uint32_t _tx_unsent_len; + uint32_t _rx_ack_len; + uint32_t _rx_last_packet; + uint32_t _rx_since_timeout; + uint32_t _ack_timeout; + uint16_t _connect_port; + u8_t _recv_pbuf_flags; + std::shared_ptr _errorTracker; + + void _close(); + void _connected(std::shared_ptr& closeAbort, void* pcb, err_t err); + void _error(err_t err); +#if ASYNC_TCP_SSL_ENABLED + void _ssl_error(int8_t err); +#endif + void _poll(std::shared_ptr& closeAbort, tcp_pcb* pcb); + void _sent(std::shared_ptr& closeAbort, tcp_pcb* pcb, uint16_t len); +#if LWIP_VERSION_MAJOR == 1 + void _dns_found(struct ip_addr *ipaddr); +#else + void _dns_found(const ip_addr *ipaddr); +#endif + static err_t _s_poll(void *arg, struct tcp_pcb *tpcb); + static err_t _s_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *pb, err_t err); + static void _s_error(void *arg, err_t err); + static err_t _s_sent(void *arg, struct tcp_pcb *tpcb, uint16_t len); + static err_t _s_connected(void* arg, void* tpcb, err_t err); +#if LWIP_VERSION_MAJOR == 1 + static void _s_dns_found(const char *name, struct ip_addr *ipaddr, void *arg); +#else + static void _s_dns_found(const char *name, const ip_addr *ipaddr, void *arg); +#endif +#if ASYNC_TCP_SSL_ENABLED + static void _s_data(void *arg, struct tcp_pcb *tcp, uint8_t * data, size_t len); + static void _s_handshake(void *arg, struct tcp_pcb *tcp, SSL *ssl); + static void _s_ssl_error(void *arg, struct tcp_pcb *tcp, int8_t err); +#endif + std::shared_ptr getACErrorTracker(void) const { return _errorTracker; }; + void setCloseError(err_t e) const { _errorTracker->setCloseError(e);} + + public: + AsyncClient* prev; + AsyncClient* next; + +#if ASYNC_TCP_SSL_ENABLED + AsyncClient(tcp_pcb* pcb = 0, SSL_CTX * ssl_ctx = NULL); +#else + AsyncClient(tcp_pcb* pcb = 0); +#endif + ~AsyncClient(); + + AsyncClient & operator=(const AsyncClient &other); + AsyncClient & operator+=(const AsyncClient &other); + + bool operator==(const AsyncClient &other); + + bool operator!=(const AsyncClient &other) { + return !(*this == other); + } +#if ASYNC_TCP_SSL_ENABLED + bool connect(IPAddress ip, uint16_t port, bool secure=false); + bool connect(const char* host, uint16_t port, bool secure=false); +#else + bool connect(IPAddress ip, uint16_t port); + bool connect(const char* host, uint16_t port); +#endif + void close(bool now = false); + void stop(); + void abort(); + bool free(); + + bool canSend();//ack is not pending + size_t space(); + size_t add(const char* data, size_t size, uint8_t apiflags=0);//add for sending + bool send();//send all data added with the method above + size_t ack(size_t len); //ack data that you have not acked using the method below + void ackLater(){ _ack_pcb = false; } //will not ack the current packet. Call from onData + bool isRecvPush(){ return !!(_recv_pbuf_flags & PBUF_FLAG_PUSH); } +#if DEBUG_ESP_ASYNC_TCP + size_t getConnectionId(void) const { return _errorTracker->getConnectionId();} +#endif +#if ASYNC_TCP_SSL_ENABLED + SSL *getSSL(); +#endif + + size_t write(const char* data); + size_t write(const char* data, size_t size, uint8_t apiflags=0); //only when canSend() == true + + uint8_t state(); + bool connecting(); + bool connected(); + bool disconnecting(); + bool disconnected(); + bool freeable();//disconnected or disconnecting + + uint16_t getMss(); + uint32_t getRxTimeout(); + void setRxTimeout(uint32_t timeout);//no RX data timeout for the connection in seconds + uint32_t getAckTimeout(); + void setAckTimeout(uint32_t timeout);//no ACK timeout for the last sent packet in milliseconds + void setNoDelay(bool nodelay); + bool getNoDelay(); + uint32_t getRemoteAddress(); + uint16_t getRemotePort(); + uint32_t getLocalAddress(); + uint16_t getLocalPort(); + + IPAddress remoteIP(); + uint16_t remotePort(); + IPAddress localIP(); + uint16_t localPort(); + + void onConnect(AcConnectHandler cb, void* arg = 0); //on successful connect + void onDisconnect(AcConnectHandler cb, void* arg = 0); //disconnected + void onAck(AcAckHandler cb, void* arg = 0); //ack received + void onError(AcErrorHandler cb, void* arg = 0); //unsuccessful connect or error + void onData(AcDataHandler cb, void* arg = 0); //data received (called if onPacket is not used) + void onPacket(AcPacketHandler cb, void* arg = 0); //data received + void onTimeout(AcTimeoutHandler cb, void* arg = 0); //ack timeout + void onPoll(AcConnectHandler cb, void* arg = 0); //every 125ms when connected + void ackPacket(struct pbuf * pb); + + const char * errorToString(err_t error); + const char * stateToString(); + + void _recv(std::shared_ptr& closeAbort, tcp_pcb* pcb, pbuf* pb, err_t err); + err_t getCloseError(void) const { return _errorTracker->getCloseError();} +}; + +#if ASYNC_TCP_SSL_ENABLED +typedef std::function AcSSlFileHandler; +struct pending_pcb; +#endif + + +class AsyncServer { + protected: + uint16_t _port; + IPAddress _addr; + bool _noDelay; + tcp_pcb* _pcb; + AcConnectHandler _connect_cb; + void* _connect_cb_arg; +#if ASYNC_TCP_SSL_ENABLED + struct pending_pcb * _pending; + SSL_CTX * _ssl_ctx; + AcSSlFileHandler _file_cb; + void* _file_cb_arg; +#endif +#ifdef DEBUG_MORE + int _event_count[EE_MAX]; +#endif + + public: + + AsyncServer(IPAddress addr, uint16_t port); + AsyncServer(uint16_t port); + ~AsyncServer(); + void onClient(AcConnectHandler cb, void* arg); +#if ASYNC_TCP_SSL_ENABLED + void onSslFileRequest(AcSSlFileHandler cb, void* arg); + void beginSecure(const char *cert, const char *private_key_file, const char *password); +#endif + void begin(); + void end(); + void setNoDelay(bool nodelay); + bool getNoDelay(); + uint8_t status(); +#ifdef DEBUG_MORE + int getEventCount(size_t ee) const { return _event_count[ee];} +#endif + protected: + err_t _accept(tcp_pcb* newpcb, err_t err); + static err_t _s_accept(void *arg, tcp_pcb* newpcb, err_t err); +#ifdef DEBUG_MORE + int incEventCount(size_t ee) { return ++_event_count[ee];} +#endif +#if ASYNC_TCP_SSL_ENABLED + int _cert(const char *filename, uint8_t **buf); + err_t _poll(tcp_pcb* pcb); + err_t _recv(tcp_pcb *pcb, struct pbuf *pb, err_t err); + static int _s_cert(void *arg, const char *filename, uint8_t **buf); + static err_t _s_poll(void *arg, struct tcp_pcb *tpcb); + static err_t _s_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *pb, err_t err); +#endif +}; + + +#endif /* ASYNCTCP_H_ */ diff --git a/libraries/ESPAsyncTCP/src/ESPAsyncTCPbuffer.cpp b/libraries/ESPAsyncTCP/src/ESPAsyncTCPbuffer.cpp new file mode 100644 index 0000000..d2261da --- /dev/null +++ b/libraries/ESPAsyncTCP/src/ESPAsyncTCPbuffer.cpp @@ -0,0 +1,555 @@ +/** + * @file ESPAsyncTCPbuffer.cpp + * @date 22.01.2016 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the Asynv TCP for ESP. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + + +#include +#include + +#include "ESPAsyncTCPbuffer.h" + + +AsyncTCPbuffer::AsyncTCPbuffer(AsyncClient* client) { + if(client == NULL) { + DEBUG_ASYNC_TCP("[A-TCP] client is null!!!\n"); + panic(); + } + + _client = client; + _TXbufferWrite = new (std::nothrow) cbuf(TCP_MSS); + _TXbufferRead = _TXbufferWrite; + _RXbuffer = new (std::nothrow) cbuf(100); + _RXmode = ATB_RX_MODE_FREE; + _rxSize = 0; + _rxTerminator = 0x00; + _rxReadBytesPtr = NULL; + _rxReadStringPtr = NULL; + _cbDisconnect = NULL; + + _cbRX = NULL; + _cbDone = NULL; + _attachCallbacks(); +} + +AsyncTCPbuffer::~AsyncTCPbuffer() { + if(_client) { + _client->close(); + } + + if(_RXbuffer) { + delete _RXbuffer; + _RXbuffer = NULL; + } + + if(_TXbufferWrite) { + // will be deleted in _TXbufferRead chain + _TXbufferWrite = NULL; + } + + if(_TXbufferRead) { + cbuf * next = _TXbufferRead->next; + delete _TXbufferRead; + while(next != NULL) { + _TXbufferRead = next; + next = _TXbufferRead->next; + delete _TXbufferRead; + } + _TXbufferRead = NULL; + } +} + +size_t AsyncTCPbuffer::write(String & data) { + return write(data.c_str(), data.length()); +} + +size_t AsyncTCPbuffer::write(uint8_t data) { + return write(&data, 1); +} + +size_t AsyncTCPbuffer::write(const char* data) { + return write((const uint8_t *) data, strlen(data)); +} + +size_t AsyncTCPbuffer::write(const char *data, size_t len) { + return write((const uint8_t *) data, len); +} + +/** + * write data in to buffer and try to send the data + * @param data + * @param len + * @return + */ +size_t AsyncTCPbuffer::write(const uint8_t *data, size_t len) { + if(_TXbufferWrite == NULL || _client == NULL || !_client->connected() || data == NULL || len == 0) { + return 0; + } + + size_t bytesLeft = len; + while(bytesLeft) { + size_t w = _TXbufferWrite->write((const char*) data, bytesLeft); + bytesLeft -= w; + data += w; + _sendBuffer(); + + // add new buffer since we have more data + if(_TXbufferWrite->full() && bytesLeft > 0) { + + // to less ram!!! + if(ESP.getFreeHeap() < 4096) { + DEBUG_ASYNC_TCP("[A-TCP] run out of Heap can not send all Data!\n"); + return (len - bytesLeft); + } + + cbuf * next = new (std::nothrow) cbuf(TCP_MSS); + if(next == NULL) { + DEBUG_ASYNC_TCP("[A-TCP] run out of Heap!\n"); + panic(); + } else { + DEBUG_ASYNC_TCP("[A-TCP] new cbuf\n"); + } + + // add new buffer to chain (current cbuf) + _TXbufferWrite->next = next; + + // move ptr for next data + _TXbufferWrite = next; + } + } + + return len; + +} + +/** + * wait until all data has send out + */ +void AsyncTCPbuffer::flush() { + while(!_TXbufferWrite->empty()) { + while(connected() && !_client->canSend()) { + delay(0); + } + if(!connected()) + return; + _sendBuffer(); + } +} + +void AsyncTCPbuffer::noCallback() { + _RXmode = ATB_RX_MODE_NONE; +} + +void AsyncTCPbuffer::readStringUntil(char terminator, String * str, AsyncTCPbufferDoneCb done) { + if(_client == NULL) { + return; + } + DEBUG_ASYNC_TCP("[A-TCP] readStringUntil terminator: %02X\n", terminator); + _RXmode = ATB_RX_MODE_NONE; + _cbDone = done; + _rxReadStringPtr = str; + _rxTerminator = terminator; + _rxSize = 0; + _RXmode = ATB_RX_MODE_TERMINATOR_STRING; +} + +/* + void AsyncTCPbuffer::readBytesUntil(char terminator, char *buffer, size_t length, AsyncTCPbufferDoneCb done) { + _RXmode = ATB_RX_MODE_NONE; + _cbDone = done; + _rxReadBytesPtr = (uint8_t *) buffer; + _rxTerminator = terminator; + _rxSize = length; + _RXmode = ATB_RX_MODE_TERMINATOR; + _handleRxBuffer(NULL, 0); + } + + void AsyncTCPbuffer::readBytesUntil(char terminator, uint8_t *buffer, size_t length, AsyncTCPbufferDoneCb done) { + readBytesUntil(terminator, (char *) buffer, length, done); + } + */ + +void AsyncTCPbuffer::readBytes(char *buffer, size_t length, AsyncTCPbufferDoneCb done) { + if(_client == NULL) { + return; + } + DEBUG_ASYNC_TCP("[A-TCP] readBytes length: %d\n", length); + _RXmode = ATB_RX_MODE_NONE; + _cbDone = done; + _rxReadBytesPtr = (uint8_t *) buffer; + _rxSize = length; + _RXmode = ATB_RX_MODE_READ_BYTES; +} + +void AsyncTCPbuffer::readBytes(uint8_t *buffer, size_t length, AsyncTCPbufferDoneCb done) { + readBytes((char *) buffer, length, done); +} + +void AsyncTCPbuffer::onData(AsyncTCPbufferDataCb cb) { + if(_client == NULL) { + return; + } + DEBUG_ASYNC_TCP("[A-TCP] onData\n"); + _RXmode = ATB_RX_MODE_NONE; + _cbDone = NULL; + _cbRX = cb; + _RXmode = ATB_RX_MODE_FREE; +} + +void AsyncTCPbuffer::onDisconnect(AsyncTCPbufferDisconnectCb cb) { + _cbDisconnect = cb; +} + +IPAddress AsyncTCPbuffer::remoteIP() { + if(!_client) { + return IPAddress(0U); + } + return _client->remoteIP(); +} + +uint16_t AsyncTCPbuffer::remotePort() { + if(!_client) { + return 0; + } + return _client->remotePort(); +} + +bool AsyncTCPbuffer::connected() { + if(!_client) { + return false; + } + return _client->connected(); +} + +void AsyncTCPbuffer::stop() { + + if(!_client) { + return; + } + _client->stop(); + _client = NULL; + + if(_cbDone) { + switch(_RXmode) { + case ATB_RX_MODE_READ_BYTES: + case ATB_RX_MODE_TERMINATOR: + case ATB_RX_MODE_TERMINATOR_STRING: + _RXmode = ATB_RX_MODE_NONE; + _cbDone(false, NULL); + break; + default: + break; + } + } + _RXmode = ATB_RX_MODE_NONE; +} + +void AsyncTCPbuffer::close() { + stop(); +} + + +///-------------------------------- + +/** + * attachCallbacks to AsyncClient class + */ +void AsyncTCPbuffer::_attachCallbacks() { + if(!_client) { + return; + } + DEBUG_ASYNC_TCP("[A-TCP] attachCallbacks\n"); + + _client->onPoll([](void *obj, AsyncClient* c) { + (void)c; + AsyncTCPbuffer* b = ((AsyncTCPbuffer*)(obj)); + if((b->_TXbufferRead != NULL) && !b->_TXbufferRead->empty()) { + b->_sendBuffer(); + } + // if(!b->_RXbuffer->empty()) { + // b->_handleRxBuffer(NULL, 0); + // } + }, this); + + _client->onAck([](void *obj, AsyncClient* c, size_t len, uint32_t time) { + (void)c; + (void)len; + (void)time; + DEBUG_ASYNC_TCP("[A-TCP] onAck\n"); + ((AsyncTCPbuffer*)(obj))->_sendBuffer(); + }, this); + + _client->onDisconnect([](void *obj, AsyncClient* c) { + DEBUG_ASYNC_TCP("[A-TCP] onDisconnect\n"); + AsyncTCPbuffer* b = ((AsyncTCPbuffer*)(obj)); + b->_client = NULL; + bool del = true; + if(b->_cbDisconnect) { + del = b->_cbDisconnect(b); + } + delete c; + if(del) { + delete b; + } + }, this); + + _client->onData([](void *obj, AsyncClient* c, void *buf, size_t len) { + (void)c; + AsyncTCPbuffer* b = ((AsyncTCPbuffer*)(obj)); + b->_rxData((uint8_t *)buf, len); + }, this); + + _client->onTimeout([](void *obj, AsyncClient* c, uint32_t time){ + (void)obj; + (void)time; + DEBUG_ASYNC_TCP("[A-TCP] onTimeout\n"); + c->close(); + }, this); + + DEBUG_ASYNC_TCP("[A-TCP] attachCallbacks Done.\n"); +} + +/** + * send TX buffer if possible + */ +void AsyncTCPbuffer::_sendBuffer() { + //DEBUG_ASYNC_TCP("[A-TCP] _sendBuffer...\n"); + size_t available = _TXbufferRead->available(); + if(available == 0 || _client == NULL || !_client->connected() || !_client->canSend()) { + return; + } + + while(connected() && (_client->space() > 0) && (_TXbufferRead->available() > 0) && _client->canSend()) { + + available = _TXbufferRead->available(); + + if(available > _client->space()) { + available = _client->space(); + } + + char *out = new (std::nothrow) char[available]; + if(out == NULL) { + DEBUG_ASYNC_TCP("[A-TCP] to less heap, try later.\n"); + return; + } + + // read data from buffer + _TXbufferRead->peek(out, available); + + // send data + size_t send = _client->write((const char*) out, available); + if(send != available) { + DEBUG_ASYNC_TCP("[A-TCP] write failed send: %d available: %d \n", send, available); + if(!connected()) { + DEBUG_ASYNC_TCP("[A-TCP] incomplete transfer, connection lost.\n"); + } + } + + // remove really send data from buffer + _TXbufferRead->remove(send); + + // if buffer is empty and there is a other buffer in chain delete the empty one + if(_TXbufferRead->available() == 0 && _TXbufferRead->next != NULL) { + cbuf * old = _TXbufferRead; + _TXbufferRead = _TXbufferRead->next; + delete old; + DEBUG_ASYNC_TCP("[A-TCP] delete cbuf\n"); + } + + delete out; + } + +} + +/** + * called on incoming data + * @param buf + * @param len + */ +void AsyncTCPbuffer::_rxData(uint8_t *buf, size_t len) { + if(!_client || !_client->connected()) { + DEBUG_ASYNC_TCP("[A-TCP] not connected!\n"); + return; + } + if(!_RXbuffer) { + DEBUG_ASYNC_TCP("[A-TCP] _rxData no _RXbuffer!\n"); + return; + } + DEBUG_ASYNC_TCP("[A-TCP] _rxData len: %d RXmode: %d\n", len, _RXmode); + + size_t handled = 0; + + if(_RXmode != ATB_RX_MODE_NONE) { + handled = _handleRxBuffer((uint8_t *) buf, len); + buf += handled; + len -= handled; + + // handle as much as possible before using the buffer + if(_RXbuffer->empty()) { + while(_RXmode != ATB_RX_MODE_NONE && handled != 0 && len > 0) { + handled = _handleRxBuffer(buf, len); + buf += handled; + len -= handled; + } + } + } + + if(len > 0) { + + if(_RXbuffer->room() < len) { + // to less space + DEBUG_ASYNC_TCP("[A-TCP] _rxData buffer full try resize\n"); + _RXbuffer->resizeAdd((len + _RXbuffer->room())); + + if(_RXbuffer->room() < len) { + DEBUG_ASYNC_TCP("[A-TCP] _rxData buffer to full can only handle %d!!!\n", _RXbuffer->room()); + } + } + + _RXbuffer->write((const char *) (buf), len); + } + + if(!_RXbuffer->empty() && _RXmode != ATB_RX_MODE_NONE) { + // handle as much as possible data in buffer + handled = _handleRxBuffer(NULL, 0); + while(_RXmode != ATB_RX_MODE_NONE && handled != 0) { + handled = _handleRxBuffer(NULL, 0); + } + } + + // clean up ram + if(_RXbuffer->empty() && _RXbuffer->room() != 100) { + _RXbuffer->resize(100); + } + +} + +/** + * + */ +size_t AsyncTCPbuffer::_handleRxBuffer(uint8_t *buf, size_t len) { + if(!_client || !_client->connected() || _RXbuffer == NULL) { + return 0; + } + + DEBUG_ASYNC_TCP("[A-TCP] _handleRxBuffer len: %d RXmode: %d\n", len, _RXmode); + + size_t BufferAvailable = _RXbuffer->available(); + size_t r = 0; + + if(_RXmode == ATB_RX_MODE_NONE) { + return 0; + } else if(_RXmode == ATB_RX_MODE_FREE) { + if(_cbRX == NULL) { + return 0; + } + + if(BufferAvailable > 0) { + uint8_t * b = new (std::nothrow) uint8_t[BufferAvailable]; + if(b == NULL){ + panic(); //TODO: What action should this be ? + } + _RXbuffer->peek((char *) b, BufferAvailable); + r = _cbRX(b, BufferAvailable); + _RXbuffer->remove(r); + } + + if(r == BufferAvailable && buf && (len > 0)) { + return _cbRX(buf, len); + } else { + return 0; + } + + } else if(_RXmode == ATB_RX_MODE_READ_BYTES) { + if(_rxReadBytesPtr == NULL || _cbDone == NULL) { + return 0; + } + + size_t newReadCount = 0; + + if(BufferAvailable) { + r = _RXbuffer->read((char *) _rxReadBytesPtr, _rxSize); + _rxSize -= r; + _rxReadBytesPtr += r; + } + + if(_RXbuffer->empty() && (len > 0) && buf) { + r = len; + if(r > _rxSize) { + r = _rxSize; + } + memcpy(_rxReadBytesPtr, buf, r); + _rxReadBytesPtr += r; + _rxSize -= r; + newReadCount += r; + } + + if(_rxSize == 0) { + _RXmode = ATB_RX_MODE_NONE; + _cbDone(true, NULL); + } + + // add left over bytes to Buffer + return newReadCount; + + } else if(_RXmode == ATB_RX_MODE_TERMINATOR) { + // TODO implement read terminator non string + + } else if(_RXmode == ATB_RX_MODE_TERMINATOR_STRING) { + if(_rxReadStringPtr == NULL || _cbDone == NULL) { + return 0; + } + + // handle Buffer + if(BufferAvailable > 0) { + while(!_RXbuffer->empty()) { + char c = _RXbuffer->read(); + if(c == _rxTerminator || c == 0x00) { + _RXmode = ATB_RX_MODE_NONE; + _cbDone(true, _rxReadStringPtr); + return 0; + } else { + (*_rxReadStringPtr) += c; + } + } + } + + if(_RXbuffer->empty() && (len > 0) && buf) { + size_t newReadCount = 0; + while(newReadCount < len) { + char c = (char) *buf; + buf++; + newReadCount++; + if(c == _rxTerminator || c == 0x00) { + _RXmode = ATB_RX_MODE_NONE; + _cbDone(true, _rxReadStringPtr); + return newReadCount; + } else { + (*_rxReadStringPtr) += c; + } + } + return newReadCount; + } + } + + return 0; +} diff --git a/libraries/ESPAsyncTCP/src/ESPAsyncTCPbuffer.h b/libraries/ESPAsyncTCP/src/ESPAsyncTCPbuffer.h new file mode 100644 index 0000000..08a57c7 --- /dev/null +++ b/libraries/ESPAsyncTCP/src/ESPAsyncTCPbuffer.h @@ -0,0 +1,118 @@ +/** + * @file ESPAsyncTCPbuffer.h + * @date 22.01.2016 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the Asynv TCP for ESP. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef ESPASYNCTCPBUFFER_H_ +#define ESPASYNCTCPBUFFER_H_ + +//#define DEBUG_ASYNC_TCP(...) while(((U0S >> USTXC) & 0x7F) != 0x00); os_printf( __VA_ARGS__ ); while(((U0S >> USTXC) & 0x7F) != 0x00) +//#define DEBUG_ASYNC_TCP ASYNC_TCP_DEBUG +#ifndef DEBUG_ASYNC_TCP +#define DEBUG_ASYNC_TCP(...) +#endif + +#include +#include + +#include "ESPAsyncTCP.h" + + + +typedef enum { + ATB_RX_MODE_NONE, + ATB_RX_MODE_FREE, + ATB_RX_MODE_READ_BYTES, + ATB_RX_MODE_TERMINATOR, + ATB_RX_MODE_TERMINATOR_STRING +} atbRxMode_t; + +class AsyncTCPbuffer: public Print { + + public: + + typedef std::function AsyncTCPbufferDataCb; + typedef std::function AsyncTCPbufferDoneCb; + typedef std::function AsyncTCPbufferDisconnectCb; + + AsyncTCPbuffer(AsyncClient* c); + virtual ~AsyncTCPbuffer(); + + size_t write(String & data); + size_t write(uint8_t data); + size_t write(const char* data); + size_t write(const char *data, size_t len); + size_t write(const uint8_t *data, size_t len); + + void flush(); + + void noCallback(); + + void readStringUntil(char terminator, String * str, AsyncTCPbufferDoneCb done); + + // TODO implement read terminator non string + //void readBytesUntil(char terminator, char *buffer, size_t length, AsyncTCPbufferDoneCb done); + //void readBytesUntil(char terminator, uint8_t *buffer, size_t length, AsyncTCPbufferDoneCb done); + + void readBytes(char *buffer, size_t length, AsyncTCPbufferDoneCb done); + void readBytes(uint8_t *buffer, size_t length, AsyncTCPbufferDoneCb done); + + // TODO implement + // void setTimeout(size_t timeout); + + void onData(AsyncTCPbufferDataCb cb); + void onDisconnect(AsyncTCPbufferDisconnectCb cb); + + IPAddress remoteIP(); + uint16_t remotePort(); + IPAddress localIP(); + uint16_t localPort(); + + bool connected(); + + void stop(); + void close(); + + protected: + AsyncClient* _client; + cbuf * _TXbufferRead; + cbuf * _TXbufferWrite; + cbuf * _RXbuffer; + atbRxMode_t _RXmode; + size_t _rxSize; + char _rxTerminator; + uint8_t * _rxReadBytesPtr; + String * _rxReadStringPtr; + + AsyncTCPbufferDataCb _cbRX; + AsyncTCPbufferDoneCb _cbDone; + AsyncTCPbufferDisconnectCb _cbDisconnect; + + void _attachCallbacks(); + void _sendBuffer(); + void _on_close(); + void _rxData(uint8_t *buf, size_t len); + size_t _handleRxBuffer(uint8_t *buf, size_t len); + +}; + +#endif /* ESPASYNCTCPBUFFER_H_ */ diff --git a/libraries/ESPAsyncTCP/src/SyncClient.cpp b/libraries/ESPAsyncTCP/src/SyncClient.cpp new file mode 100644 index 0000000..8335358 --- /dev/null +++ b/libraries/ESPAsyncTCP/src/SyncClient.cpp @@ -0,0 +1,414 @@ +/* + Asynchronous TCP library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "Arduino.h" +#include "SyncClient.h" +#include "ESPAsyncTCP.h" +#include "cbuf.h" +#include + +#define DEBUG_ESP_SYNC_CLIENT +#if defined(DEBUG_ESP_SYNC_CLIENT) && !defined(SYNC_CLIENT_DEBUG) +#define SYNC_CLIENT_DEBUG( format, ...) DEBUG_GENERIC_P("[SYNC_CLIENT]", format, ##__VA_ARGS__) +#endif +#ifndef SYNC_CLIENT_DEBUG +#define SYNC_CLIENT_DEBUG(...) do { (void)0;} while(false) +#endif + +/* + Without LWIP_NETIF_TX_SINGLE_PBUF, all tcp_writes default to "no copy". + Referenced data must be preserved and free-ed from the specified tcp_sent() + callback. Alternative, tcp_writes need to use the TCP_WRITE_FLAG_COPY + attribute. +*/ +static_assert(LWIP_NETIF_TX_SINGLE_PBUF, "Required, tcp_write() must always copy."); + +SyncClient::SyncClient(size_t txBufLen) + : _client(NULL) + , _tx_buffer(NULL) + , _tx_buffer_size(txBufLen) + , _rx_buffer(NULL) + , _ref(NULL) +{ + ref(); +} + +SyncClient::SyncClient(AsyncClient *client, size_t txBufLen) + : _client(client) + , _tx_buffer(new (std::nothrow) cbuf(txBufLen)) + , _tx_buffer_size(txBufLen) + , _rx_buffer(NULL) + , _ref(NULL) +{ + if(ref() > 0 && _client != NULL) + _attachCallbacks(); +} + +SyncClient::~SyncClient(){ + if (0 == unref()) + _release(); +} + +void SyncClient::_release(){ + if(_client != NULL){ + _client->onData(NULL, NULL); + _client->onAck(NULL, NULL); + _client->onPoll(NULL, NULL); + _client->abort(); + _client = NULL; + } + if(_tx_buffer != NULL){ + cbuf *b = _tx_buffer; + _tx_buffer = NULL; + delete b; + } + while(_rx_buffer != NULL){ + cbuf *b = _rx_buffer; + _rx_buffer = _rx_buffer->next; + delete b; + } +} + +int SyncClient::ref(){ + if(_ref == NULL){ + _ref = new (std::nothrow) int; + if(_ref != NULL) + *_ref = 0; + else + return -1; + } + return (++*_ref); +} + +int SyncClient::unref(){ + int count = -1; + if (_ref != NULL) { + count = --*_ref; + if (0 == count) { + delete _ref; + _ref = NULL; + } + } + return count; +} + +#if ASYNC_TCP_SSL_ENABLED +int SyncClient::_connect(const IPAddress& ip, uint16_t port, bool secure){ +#else +int SyncClient::_connect(const IPAddress& ip, uint16_t port){ +#endif + if(connected()) + return 0; + if(_client != NULL) + delete _client; + + _client = new (std::nothrow) AsyncClient(); + if (_client == NULL) + return 0; + + _client->onConnect([](void *obj, AsyncClient *c){ ((SyncClient*)(obj))->_onConnect(c); }, this); + _attachCallbacks_Disconnect(); +#if ASYNC_TCP_SSL_ENABLED + if(_client->connect(ip, port, secure)){ +#else + if(_client->connect(ip, port)){ +#endif + while(_client != NULL && !_client->connected() && !_client->disconnecting()) + delay(1); + return connected(); + } + return 0; +} + +#if ASYNC_TCP_SSL_ENABLED +int SyncClient::connect(const char *host, uint16_t port, bool secure){ +#else +int SyncClient::connect(const char *host, uint16_t port){ +#endif + if(connected()) + return 0; + if(_client != NULL) + delete _client; + + _client = new (std::nothrow) AsyncClient(); + if (_client == NULL) + return 0; + + _client->onConnect([](void *obj, AsyncClient *c){ ((SyncClient*)(obj))->_onConnect(c); }, this); + _attachCallbacks_Disconnect(); +#if ASYNC_TCP_SSL_ENABLED + if(_client->connect(host, port, secure)){ +#else + if(_client->connect(host, port)){ +#endif + while(_client != NULL && !_client->connected() && !_client->disconnecting()) + delay(1); + return connected(); + } + return 0; +} +//#define SYNCCLIENT_NEW_OPERATOR_EQUAL +#ifdef SYNCCLIENT_NEW_OPERATOR_EQUAL +/* + New behavior for operator= + + Allow for the object to be placed on a queue and transfered to a new container + with buffers still in tact. Avoiding receive data drops. Transfers rx and tx + buffers. Supports return by value. + + Note, this is optional, the old behavior is the default. + +*/ +SyncClient & SyncClient::operator=(const SyncClient &other){ + int *rhsref = other._ref; + ++*rhsref; // Just in case the left and right side are the same object with different containers + if (0 == unref()) + _release(); + _ref = other._ref; + ref(); + --*rhsref; + // Why do I not test _tx_buffer for != NULL and free? + // I allow for the lh target container, to be a copy of an active + // connection. Thus we are just reusing the container. + // The above unref() handles releaseing the previous client of the container. + _tx_buffer_size = other._tx_buffer_size; + _tx_buffer = other._tx_buffer; + _client = other._client; + if (_client != NULL && _tx_buffer == NULL) + _tx_buffer = new (std::nothrow) cbuf(_tx_buffer_size); + + _rx_buffer = other._rx_buffer; + if(_client) + _attachCallbacks(); + return *this; +} +#else // ! SYNCCLIENT_NEW_OPERATOR_EQUAL +// This is the origianl logic with null checks +SyncClient & SyncClient::operator=(const SyncClient &other){ + if(_client != NULL){ + _client->abort(); + _client->free(); + _client = NULL; + } + _tx_buffer_size = other._tx_buffer_size; + if(_tx_buffer != NULL){ + cbuf *b = _tx_buffer; + _tx_buffer = NULL; + delete b; + } + while(_rx_buffer != NULL){ + cbuf *b = _rx_buffer; + _rx_buffer = b->next; + delete b; + } + if(other._client != NULL) + _tx_buffer = new (std::nothrow) cbuf(other._tx_buffer_size); + + _client = other._client; + if(_client) + _attachCallbacks(); + + return *this; +} +#endif + +void SyncClient::setTimeout(uint32_t seconds){ + if(_client != NULL) + _client->setRxTimeout(seconds); +} + +uint8_t SyncClient::status(){ + if(_client == NULL) + return 0; + return _client->state(); +} + +uint8_t SyncClient::connected(){ + return (_client != NULL && _client->connected()); +} + +bool SyncClient::stop(unsigned int maxWaitMs){ + (void)maxWaitMs; + if(_client != NULL) + _client->close(true); + return true; +} + +size_t SyncClient::_sendBuffer(){ + if(_client == NULL || _tx_buffer == NULL) + return 0; + size_t available = _tx_buffer->available(); + if(!connected() || !_client->canSend() || available == 0) + return 0; + size_t sendable = _client->space(); + if(sendable < available) + available= sendable; + char *out = new (std::nothrow) char[available]; + if(out == NULL) + return 0; + + _tx_buffer->read(out, available); + size_t sent = _client->write(out, available); + delete[] out; + return sent; +} + +void SyncClient::_onData(void *data, size_t len){ + _client->ackLater(); + cbuf *b = new (std::nothrow) cbuf(len+1); + if(b != NULL){ + b->write((const char *)data, len); + if(_rx_buffer == NULL) + _rx_buffer = b; + else { + cbuf *p = _rx_buffer; + while(p->next != NULL) + p = p->next; + p->next = b; + } + } else { + // We ran out of memory. This fail causes lost receive data. + // The connection should be closed in a manner that conveys something + // bad/abnormal has happened to the connection. Hence, we abort the + // connection to avoid possible data corruption. + // Note, callbacks maybe called. + _client->abort(); + } +} + +void SyncClient::_onDisconnect(){ + if(_client != NULL){ + _client = NULL; + } + if(_tx_buffer != NULL){ + cbuf *b = _tx_buffer; + _tx_buffer = NULL; + delete b; + } +} + +void SyncClient::_onConnect(AsyncClient *c){ + _client = c; + if(_tx_buffer != NULL){ + cbuf *b = _tx_buffer; + _tx_buffer = NULL; + delete b; + } + _tx_buffer = new (std::nothrow) cbuf(_tx_buffer_size); + _attachCallbacks_AfterConnected(); +} + +void SyncClient::_attachCallbacks(){ + _attachCallbacks_Disconnect(); + _attachCallbacks_AfterConnected(); +} + +void SyncClient::_attachCallbacks_AfterConnected(){ + _client->onAck([](void *obj, AsyncClient* c, size_t len, uint32_t time){ (void)c; (void)len; (void)time; ((SyncClient*)(obj))->_sendBuffer(); }, this); + _client->onData([](void *obj, AsyncClient* c, void *data, size_t len){ (void)c; ((SyncClient*)(obj))->_onData(data, len); }, this); + _client->onTimeout([](void *obj, AsyncClient* c, uint32_t time){ (void)obj; (void)time; c->close(); }, this); +} + +void SyncClient::_attachCallbacks_Disconnect(){ + _client->onDisconnect([](void *obj, AsyncClient* c){ ((SyncClient*)(obj))->_onDisconnect(); delete c; }, this); +} + +size_t SyncClient::write(uint8_t data){ + return write(&data, 1); +} + +size_t SyncClient::write(const uint8_t *data, size_t len){ + if(_tx_buffer == NULL || !connected()){ + return 0; + } + size_t toWrite = 0; + size_t toSend = len; + while(_tx_buffer->room() < toSend){ + toWrite = _tx_buffer->room(); + _tx_buffer->write((const char*)data, toWrite); + while(connected() && !_client->canSend()) + delay(0); + if(!connected()) + return 0; + _sendBuffer(); + toSend -= toWrite; + } + _tx_buffer->write((const char*)(data+(len - toSend)), toSend); + if(connected() && _client->canSend()) + _sendBuffer(); + return len; +} + +int SyncClient::available(){ + if(_rx_buffer == NULL) return 0; + size_t a = 0; + cbuf *b = _rx_buffer; + while(b != NULL){ + a += b->available(); + b = b->next; + } + return a; +} + +int SyncClient::peek(){ + if(_rx_buffer == NULL) return -1; + return _rx_buffer->peek(); +} + +int SyncClient::read(uint8_t *data, size_t len){ + if(_rx_buffer == NULL) return -1; + + size_t readSoFar = 0; + while(_rx_buffer != NULL && (len - readSoFar) >= _rx_buffer->available()){ + cbuf *b = _rx_buffer; + _rx_buffer = _rx_buffer->next; + size_t toRead = b->available(); + readSoFar += b->read((char*)(data+readSoFar), toRead); + if(connected()){ + _client->ack(b->size() - 1); + } + delete b; + } + if(_rx_buffer != NULL && readSoFar < len){ + readSoFar += _rx_buffer->read((char*)(data+readSoFar), (len - readSoFar)); + } + return readSoFar; +} + +int SyncClient::read(){ + uint8_t res = 0; + if(read(&res, 1) != 1) + return -1; + return res; +} + +bool SyncClient::flush(unsigned int maxWaitMs){ + (void)maxWaitMs; + if(_tx_buffer == NULL || !connected()) + return false; + if(_tx_buffer->available()){ + while(connected() && !_client->canSend()) + delay(0); + if(_client == NULL || _tx_buffer == NULL) + return false; + _sendBuffer(); + } + return true; +} diff --git a/libraries/ESPAsyncTCP/src/SyncClient.h b/libraries/ESPAsyncTCP/src/SyncClient.h new file mode 100644 index 0000000..cb568de --- /dev/null +++ b/libraries/ESPAsyncTCP/src/SyncClient.h @@ -0,0 +1,109 @@ +/* + Asynchronous TCP library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef SYNCCLIENT_H_ +#define SYNCCLIENT_H_ + +#include "Client.h" +// Needed for Arduino core releases prior to 2.5.0, because of changes +// made to accommodate Arduino core 2.5.0 +// CONST was 1st defined in Core 2.5.0 in IPAddress.h +#ifndef CONST +#define CONST +#endif +#include +class cbuf; +class AsyncClient; + +class SyncClient: public Client { + private: + AsyncClient *_client; + cbuf *_tx_buffer; + size_t _tx_buffer_size; + cbuf *_rx_buffer; + int *_ref; + + size_t _sendBuffer(); + void _onData(void *data, size_t len); + void _onConnect(AsyncClient *c); + void _onDisconnect(); + void _attachCallbacks(); + void _attachCallbacks_Disconnect(); + void _attachCallbacks_AfterConnected(); + void _release(); + + public: + SyncClient(size_t txBufLen = TCP_MSS); + SyncClient(AsyncClient *client, size_t txBufLen = TCP_MSS); + virtual ~SyncClient(); + + int ref(); + int unref(); + operator bool(){ return connected(); } + SyncClient & operator=(const SyncClient &other); + +#if ASYNC_TCP_SSL_ENABLED + int _connect(const IPAddress& ip, uint16_t port, bool secure); + int connect(CONST IPAddress& ip, uint16_t port, bool secure){ + return _connect(ip, port, secure); + } + int connect(IPAddress ip, uint16_t port, bool secure){ + return _connect(reinterpret_cast(ip), port, secure); + } + int connect(const char *host, uint16_t port, bool secure); + int connect(CONST IPAddress& ip, uint16_t port){ + return _connect(ip, port, false); + } + int connect(IPAddress ip, uint16_t port){ + return _connect(reinterpret_cast(ip), port, false); + } + int connect(const char *host, uint16_t port){ + return connect(host, port, false); + } +#else + int _connect(const IPAddress& ip, uint16_t port); + int connect(CONST IPAddress& ip, uint16_t port){ + return _connect(ip, port); + } + int connect(IPAddress ip, uint16_t port){ + return _connect(reinterpret_cast(ip), port); + } + int connect(const char *host, uint16_t port); +#endif + void setTimeout(uint32_t seconds); + + uint8_t status(); + uint8_t connected(); + + bool stop(unsigned int maxWaitMs); + bool flush(unsigned int maxWaitMs); + void stop() { (void)stop(0);} + void flush() { (void)flush(0);} + size_t write(uint8_t data); + size_t write(const uint8_t *data, size_t len); + + int available(); + int peek(); + int read(); + int read(uint8_t *data, size_t len); +}; + +#endif /* SYNCCLIENT_H_ */ diff --git a/libraries/ESPAsyncTCP/src/async_config.h b/libraries/ESPAsyncTCP/src/async_config.h new file mode 100644 index 0000000..ca6912f --- /dev/null +++ b/libraries/ESPAsyncTCP/src/async_config.h @@ -0,0 +1,38 @@ +#ifndef LIBRARIES_ESPASYNCTCP_SRC_ASYNC_CONFIG_H_ +#define LIBRARIES_ESPASYNCTCP_SRC_ASYNC_CONFIG_H_ + +#ifndef ASYNC_TCP_SSL_ENABLED +#define ASYNC_TCP_SSL_ENABLED 0 +#endif + +#ifndef TCP_MSS +// May have been definded as a -DTCP_MSS option on the compile line or not. +// Arduino core 2.3.0 or earlier does not do the -DTCP_MSS option. +// Later versions may set this option with info from board.txt. +// However, Core 2.4.0 and up board.txt does not define TCP_MSS for lwIP v1.4 +#define TCP_MSS (1460) +#endif + +// #define ASYNC_TCP_DEBUG(...) ets_printf(__VA_ARGS__) +// #define TCP_SSL_DEBUG(...) ets_printf(__VA_ARGS__) +// #define ASYNC_TCP_ASSERT( a ) do{ if(!(a)){ets_printf("ASSERT: %s %u \n", __FILE__, __LINE__);}}while(0) + +// Starting with Arduino Core 2.4.0 and up the define of DEBUG_ESP_PORT +// can be handled through the Arduino IDE Board options instead of here. +// #define DEBUG_ESP_PORT Serial + +// #define DEBUG_ESP_ASYNC_TCP 1 +// #define DEBUG_ESP_TCP_SSL 1 +#include + +#ifndef ASYNC_TCP_ASSERT +#define ASYNC_TCP_ASSERT(...) do { (void)0;} while(false) +#endif +#ifndef ASYNC_TCP_DEBUG +#define ASYNC_TCP_DEBUG(...) do { (void)0;} while(false) +#endif +#ifndef TCP_SSL_DEBUG +#define TCP_SSL_DEBUG(...) do { (void)0;} while(false) +#endif + +#endif /* LIBRARIES_ESPASYNCTCP_SRC_ASYNC_CONFIG_H_ */ diff --git a/libraries/ESPAsyncTCP/src/tcp_axtls.c b/libraries/ESPAsyncTCP/src/tcp_axtls.c new file mode 100644 index 0000000..cdbdf41 --- /dev/null +++ b/libraries/ESPAsyncTCP/src/tcp_axtls.c @@ -0,0 +1,588 @@ +/* + Asynchronous TCP library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +/* + * Compatibility for AxTLS with LWIP raw tcp mode (http://lwip.wikia.com/wiki/Raw/TCP) + * Original Code and Inspiration: Slavey Karadzhov + */ +#include +#if ASYNC_TCP_SSL_ENABLED + +#include "lwip/opt.h" +#include "lwip/tcp.h" +#include "lwip/inet.h" +#include +#include +#include +#include +#include + +uint8_t * default_private_key = NULL; +uint16_t default_private_key_len = 0; + +uint8_t * default_certificate = NULL; +uint16_t default_certificate_len = 0; + +static uint8_t _tcp_ssl_has_client = 0; + +SSL_CTX * tcp_ssl_new_server_ctx(const char *cert, const char *private_key_file, const char *password){ + uint32_t options = SSL_CONNECT_IN_PARTS; + SSL_CTX *ssl_ctx; + + if(private_key_file){ + options |= SSL_NO_DEFAULT_KEY; + } + + if ((ssl_ctx = ssl_ctx_new(options, SSL_DEFAULT_SVR_SESS)) == NULL){ + TCP_SSL_DEBUG("tcp_ssl_new_server_ctx: failed to allocate context\n"); + return NULL; + } + + if (private_key_file){ + int obj_type = SSL_OBJ_RSA_KEY; + if (strstr(private_key_file, ".p8")) + obj_type = SSL_OBJ_PKCS8; + else if (strstr(private_key_file, ".p12")) + obj_type = SSL_OBJ_PKCS12; + + if (ssl_obj_load(ssl_ctx, obj_type, private_key_file, password)){ + TCP_SSL_DEBUG("tcp_ssl_new_server_ctx: load private key '%s' failed\n", private_key_file); + return NULL; + } + } + + if (cert){ + if (ssl_obj_load(ssl_ctx, SSL_OBJ_X509_CERT, cert, NULL)){ + TCP_SSL_DEBUG("tcp_ssl_new_server_ctx: load certificate '%s' failed\n", cert); + return NULL; + } + } + return ssl_ctx; +} + +struct tcp_ssl_pcb { + struct tcp_pcb *tcp; + int fd; + SSL_CTX* ssl_ctx; + SSL *ssl; + uint8_t type; + int handshake; + void * arg; + tcp_ssl_data_cb_t on_data; + tcp_ssl_handshake_cb_t on_handshake; + tcp_ssl_error_cb_t on_error; + int last_wr; + struct pbuf *tcp_pbuf; + int pbuf_offset; + struct tcp_ssl_pcb * next; +}; + +typedef struct tcp_ssl_pcb tcp_ssl_t; + +static tcp_ssl_t * tcp_ssl_array = NULL; +static int tcp_ssl_next_fd = 0; + +uint8_t tcp_ssl_has_client(){ + return _tcp_ssl_has_client; +} + +tcp_ssl_t * tcp_ssl_new(struct tcp_pcb *tcp) { + + if(tcp_ssl_next_fd < 0){ + tcp_ssl_next_fd = 0;//overflow + } + + tcp_ssl_t * new_item = (tcp_ssl_t*)malloc(sizeof(tcp_ssl_t)); + if(!new_item){ + TCP_SSL_DEBUG("tcp_ssl_new: failed to allocate tcp_ssl\n"); + return NULL; + } + + new_item->tcp = tcp; + new_item->handshake = SSL_NOT_OK; + new_item->arg = NULL; + new_item->on_data = NULL; + new_item->on_handshake = NULL; + new_item->on_error = NULL; + new_item->tcp_pbuf = NULL; + new_item->pbuf_offset = 0; + new_item->next = NULL; + new_item->ssl_ctx = NULL; + new_item->ssl = NULL; + new_item->type = TCP_SSL_TYPE_CLIENT; + new_item->fd = tcp_ssl_next_fd++; + + if(tcp_ssl_array == NULL){ + tcp_ssl_array = new_item; + } else { + tcp_ssl_t * item = tcp_ssl_array; + while(item->next != NULL) + item = item->next; + item->next = new_item; + } + + TCP_SSL_DEBUG("tcp_ssl_new: %d\n", new_item->fd); + return new_item; +} + +tcp_ssl_t* tcp_ssl_get(struct tcp_pcb *tcp) { + if(tcp == NULL) { + return NULL; + } + tcp_ssl_t * item = tcp_ssl_array; + while(item && item->tcp != tcp){ + item = item->next; + } + return item; +} + +int tcp_ssl_new_client(struct tcp_pcb *tcp){ + SSL_CTX* ssl_ctx; + tcp_ssl_t * tcp_ssl; + + if(tcp == NULL) { + return -1; + } + + if(tcp_ssl_get(tcp) != NULL){ + TCP_SSL_DEBUG("tcp_ssl_new_client: tcp_ssl already exists\n"); + return -1; + } + + ssl_ctx = ssl_ctx_new(SSL_CONNECT_IN_PARTS | SSL_SERVER_VERIFY_LATER, 1); + if(ssl_ctx == NULL){ + TCP_SSL_DEBUG("tcp_ssl_new_client: failed to allocate ssl context\n"); + return -1; + } + + tcp_ssl = tcp_ssl_new(tcp); + if(tcp_ssl == NULL){ + ssl_ctx_free(ssl_ctx); + return -1; + } + + tcp_ssl->ssl_ctx = ssl_ctx; + + tcp_ssl->ssl = ssl_client_new(ssl_ctx, tcp_ssl->fd, NULL, 0, NULL); + if(tcp_ssl->ssl == NULL){ + TCP_SSL_DEBUG("tcp_ssl_new_client: failed to allocate ssl\n"); + tcp_ssl_free(tcp); + return -1; + } + + return tcp_ssl->fd; +} + +int tcp_ssl_new_server(struct tcp_pcb *tcp, SSL_CTX* ssl_ctx){ + tcp_ssl_t * tcp_ssl; + + if(tcp == NULL) { + return -1; + } + + if(ssl_ctx == NULL){ + return -1; + } + + if(tcp_ssl_get(tcp) != NULL){ + TCP_SSL_DEBUG("tcp_ssl_new_server: tcp_ssl already exists\n"); + return -1; + } + + tcp_ssl = tcp_ssl_new(tcp); + if(tcp_ssl == NULL){ + return -1; + } + + tcp_ssl->type = TCP_SSL_TYPE_SERVER; + tcp_ssl->ssl_ctx = ssl_ctx; + + _tcp_ssl_has_client = 1; + tcp_ssl->ssl = ssl_server_new(ssl_ctx, tcp_ssl->fd); + if(tcp_ssl->ssl == NULL){ + TCP_SSL_DEBUG("tcp_ssl_new_server: failed to allocate ssl\n"); + tcp_ssl_free(tcp); + return -1; + } + + return tcp_ssl->fd; +} + +int tcp_ssl_free(struct tcp_pcb *tcp) { + + if(tcp == NULL) { + return -1; + } + + tcp_ssl_t * item = tcp_ssl_array; + + if(item->tcp == tcp){ + tcp_ssl_array = tcp_ssl_array->next; + if(item->tcp_pbuf != NULL){ + pbuf_free(item->tcp_pbuf); + } + TCP_SSL_DEBUG("tcp_ssl_free: %d\n", item->fd); + if(item->ssl) + ssl_free(item->ssl); + if(item->type == TCP_SSL_TYPE_CLIENT && item->ssl_ctx) + ssl_ctx_free(item->ssl_ctx); + if(item->type == TCP_SSL_TYPE_SERVER) + _tcp_ssl_has_client = 0; + free(item); + return 0; + } + + while(item->next && item->next->tcp != tcp) + item = item->next; + + if(item->next == NULL){ + return ERR_TCP_SSL_INVALID_CLIENTFD_DATA;//item not found + } + + tcp_ssl_t * i = item->next; + item->next = i->next; + if(i->tcp_pbuf != NULL){ + pbuf_free(i->tcp_pbuf); + } + TCP_SSL_DEBUG("tcp_ssl_free: %d\n", i->fd); + if(i->ssl) + ssl_free(i->ssl); + if(i->type == TCP_SSL_TYPE_CLIENT && i->ssl_ctx) + ssl_ctx_free(i->ssl_ctx); + if(i->type == TCP_SSL_TYPE_SERVER) + _tcp_ssl_has_client = 0; + free(i); + return 0; +} + +#ifdef AXTLS_2_0_0_SNDBUF +int tcp_ssl_sndbuf(struct tcp_pcb *tcp){ + int expected; + int available; + int result = -1; + + if(tcp == NULL) { + return result; + } + tcp_ssl_t * tcp_ssl = tcp_ssl_get(tcp); + if(!tcp_ssl){ + TCP_SSL_DEBUG("tcp_ssl_sndbuf: tcp_ssl is NULL\n"); + return result; + } + available = tcp_sndbuf(tcp); + if(!available){ + TCP_SSL_DEBUG("tcp_ssl_sndbuf: tcp_sndbuf is zero\n"); + return 0; + } + result = available; + while((expected = ssl_calculate_write_length(tcp_ssl->ssl, result)) > available){ + result -= (expected - available) + 4; + } + + if(expected > 0){ + //TCP_SSL_DEBUG("tcp_ssl_sndbuf: tcp_sndbuf is %d from %d\n", result, available); + return result; + } + + return 0; +} +#endif + +int tcp_ssl_write(struct tcp_pcb *tcp, uint8_t *data, size_t len) { + if(tcp == NULL) { + return -1; + } + tcp_ssl_t * tcp_ssl = tcp_ssl_get(tcp); + if(!tcp_ssl){ + TCP_SSL_DEBUG("tcp_ssl_write: tcp_ssl is NULL\n"); + return 0; + } + tcp_ssl->last_wr = 0; + +#ifdef AXTLS_2_0_0_SNDBUF + int expected_len = ssl_calculate_write_length(tcp_ssl->ssl, len); + int available_len = tcp_sndbuf(tcp); + if(expected_len < 0 || expected_len > available_len){ + TCP_SSL_DEBUG("tcp_ssl_write: data will not fit! %u < %d(%u)\r\n", available_len, expected_len, len); + return -1; + } +#endif + + int rc = ssl_write(tcp_ssl->ssl, data, len); + + //TCP_SSL_DEBUG("tcp_ssl_write: %u -> %d (%d)\r\n", len, tcp_ssl->last_wr, rc); + + if (rc < 0){ + if(rc != SSL_CLOSE_NOTIFY) { + TCP_SSL_DEBUG("tcp_ssl_write error: %d\r\n", rc); + } + return rc; + } + + return tcp_ssl->last_wr; +} + +/** + * Reads data from the SSL over TCP stream. Returns decrypted data. + * @param tcp_pcb *tcp - pointer to the raw tcp object + * @param pbuf *p - pointer to the buffer with the TCP packet data + * + * @return int + * 0 - when everything is fine but there are no symbols to process yet + * < 0 - when there is an error + * > 0 - the length of the clear text characters that were read + */ +int tcp_ssl_read(struct tcp_pcb *tcp, struct pbuf *p) { + if(tcp == NULL) { + return -1; + } + tcp_ssl_t* fd_data = NULL; + + int read_bytes = 0; + int total_bytes = 0; + uint8_t *read_buf; + + fd_data = tcp_ssl_get(tcp); + if(fd_data == NULL) { + TCP_SSL_DEBUG("tcp_ssl_read: tcp_ssl is NULL\n"); + return ERR_TCP_SSL_INVALID_CLIENTFD_DATA; + } + + if(p == NULL) { + TCP_SSL_DEBUG("tcp_ssl_read:p == NULL\n"); + return ERR_TCP_SSL_INVALID_DATA; + } + + //TCP_SSL_DEBUG("READY TO READ SOME DATA\n"); + + fd_data->tcp_pbuf = p; + fd_data->pbuf_offset = 0; + + do { + read_bytes = ssl_read(fd_data->ssl, &read_buf); + //TCP_SSL_DEBUG("tcp_ssl_ssl_read: %d\n", read_bytes); + if(read_bytes < SSL_OK) { + if(read_bytes != SSL_CLOSE_NOTIFY) { + TCP_SSL_DEBUG("tcp_ssl_read: read error: %d\n", read_bytes); + } + total_bytes = read_bytes; + break; + } else if(read_bytes > 0){ + if(fd_data->on_data){ + fd_data->on_data(fd_data->arg, tcp, read_buf, read_bytes); + } + total_bytes+= read_bytes; + } else { + if(fd_data->handshake != SSL_OK) { + fd_data->handshake = ssl_handshake_status(fd_data->ssl); + if(fd_data->handshake == SSL_OK){ + //TCP_SSL_DEBUG("tcp_ssl_read: handshake OK\n"); + if(fd_data->on_handshake) + fd_data->on_handshake(fd_data->arg, fd_data->tcp, fd_data->ssl); + } else if(fd_data->handshake != SSL_NOT_OK){ + TCP_SSL_DEBUG("tcp_ssl_read: handshake error: %d\n", fd_data->handshake); + if(fd_data->on_error) + fd_data->on_error(fd_data->arg, fd_data->tcp, fd_data->handshake); + return fd_data->handshake; + } + } + } + } while (p->tot_len - fd_data->pbuf_offset > 0); + + tcp_recved(tcp, p->tot_len); + fd_data->tcp_pbuf = NULL; + pbuf_free(p); + + return total_bytes; +} + +SSL * tcp_ssl_get_ssl(struct tcp_pcb *tcp){ + tcp_ssl_t * tcp_ssl = tcp_ssl_get(tcp); + if(tcp_ssl){ + return tcp_ssl->ssl; + } + return NULL; +} + +bool tcp_ssl_has(struct tcp_pcb *tcp){ + return tcp_ssl_get(tcp) != NULL; +} + +int tcp_ssl_is_server(struct tcp_pcb *tcp){ + tcp_ssl_t * tcp_ssl = tcp_ssl_get(tcp); + if(tcp_ssl){ + return tcp_ssl->type; + } + return -1; +} + +void tcp_ssl_arg(struct tcp_pcb *tcp, void * arg){ + tcp_ssl_t * item = tcp_ssl_get(tcp); + if(item) { + item->arg = arg; + } +} + +void tcp_ssl_data(struct tcp_pcb *tcp, tcp_ssl_data_cb_t arg){ + tcp_ssl_t * item = tcp_ssl_get(tcp); + if(item) { + item->on_data = arg; + } +} + +void tcp_ssl_handshake(struct tcp_pcb *tcp, tcp_ssl_handshake_cb_t arg){ + tcp_ssl_t * item = tcp_ssl_get(tcp); + if(item) { + item->on_handshake = arg; + } +} + +void tcp_ssl_err(struct tcp_pcb *tcp, tcp_ssl_error_cb_t arg){ + tcp_ssl_t * item = tcp_ssl_get(tcp); + if(item) { + item->on_error = arg; + } +} + +static tcp_ssl_file_cb_t _tcp_ssl_file_cb = NULL; +static void * _tcp_ssl_file_arg = NULL; + +void tcp_ssl_file(tcp_ssl_file_cb_t cb, void * arg){ + _tcp_ssl_file_cb = cb; + _tcp_ssl_file_arg = arg; +} + +int ax_get_file(const char *filename, uint8_t **buf) { + //TCP_SSL_DEBUG("ax_get_file: %s\n", filename); + if(_tcp_ssl_file_cb){ + return _tcp_ssl_file_cb(_tcp_ssl_file_arg, filename, buf); + } + *buf = 0; + return 0; +} + +tcp_ssl_t* tcp_ssl_get_by_fd(int fd) { + tcp_ssl_t * item = tcp_ssl_array; + while(item && item->fd != fd){ + item = item->next; + } + return item; +} +/* + * The LWIP tcp raw version of the SOCKET_WRITE(A, B, C) + */ +int ax_port_write(int fd, uint8_t *data, uint16_t len) { + tcp_ssl_t *fd_data = NULL; + int tcp_len = 0; + err_t err = ERR_OK; + + //TCP_SSL_DEBUG("ax_port_write: %d, %d\n", fd, len); + + fd_data = tcp_ssl_get_by_fd(fd); + if(fd_data == NULL) { + //TCP_SSL_DEBUG("ax_port_write: tcp_ssl[%d] is NULL\n", fd); + return ERR_MEM; + } + + if (data == NULL || len == 0) { + return 0; + } + + if (tcp_sndbuf(fd_data->tcp) < len) { + tcp_len = tcp_sndbuf(fd_data->tcp); + if(tcp_len == 0) { + TCP_SSL_DEBUG("ax_port_write: tcp_sndbuf is zero: %d\n", len); + return ERR_MEM; + } + } else { + tcp_len = len; + } + + if (tcp_len > 2 * fd_data->tcp->mss) { + tcp_len = 2 * fd_data->tcp->mss; + } + + err = tcp_write(fd_data->tcp, data, tcp_len, TCP_WRITE_FLAG_COPY); + if(err < ERR_OK) { + if (err == ERR_MEM) { + TCP_SSL_DEBUG("ax_port_write: No memory %d (%d)\n", tcp_len, len); + return err; + } + TCP_SSL_DEBUG("ax_port_write: tcp_write error: %d\n", err); + return err; + } else if (err == ERR_OK) { + //TCP_SSL_DEBUG("ax_port_write: tcp_output: %d / %d\n", tcp_len, len); + err = tcp_output(fd_data->tcp); + if(err != ERR_OK) { + TCP_SSL_DEBUG("ax_port_write: tcp_output err: %d\n", err); + return err; + } + } + + fd_data->last_wr += tcp_len; + + return tcp_len; +} + +/* + * The LWIP tcp raw version of the SOCKET_READ(A, B, C) + */ +int ax_port_read(int fd, uint8_t *data, int len) { + tcp_ssl_t *fd_data = NULL; + uint8_t *read_buf = NULL; + uint8_t *pread_buf = NULL; + u16_t recv_len = 0; + + //TCP_SSL_DEBUG("ax_port_read: %d, %d\n", fd, len); + + fd_data = tcp_ssl_get_by_fd(fd); + if (fd_data == NULL) { + TCP_SSL_DEBUG("ax_port_read: tcp_ssl[%d] is NULL\n", fd); + return ERR_TCP_SSL_INVALID_CLIENTFD_DATA; + } + + if(fd_data->tcp_pbuf == NULL || fd_data->tcp_pbuf->tot_len == 0) { + return 0; + } + + read_buf =(uint8_t*)calloc(fd_data->tcp_pbuf->len + 1, sizeof(uint8_t)); + pread_buf = read_buf; + if (pread_buf != NULL){ + recv_len = pbuf_copy_partial(fd_data->tcp_pbuf, read_buf, len, fd_data->pbuf_offset); + fd_data->pbuf_offset += recv_len; + } + + if (recv_len != 0) { + memcpy(data, read_buf, recv_len); + } + + if(len < recv_len) { + TCP_SSL_DEBUG("ax_port_read: got %d bytes more than expected\n", recv_len - len); + } + + free(pread_buf); + pread_buf = NULL; + + return recv_len; +} + +void ax_wdt_feed() {} + +#endif diff --git a/libraries/ESPAsyncTCP/src/tcp_axtls.h b/libraries/ESPAsyncTCP/src/tcp_axtls.h new file mode 100644 index 0000000..118e36f --- /dev/null +++ b/libraries/ESPAsyncTCP/src/tcp_axtls.h @@ -0,0 +1,98 @@ +/* + Asynchronous TCP library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +/* + * Compatibility for AxTLS with LWIP raw tcp mode (http://lwip.wikia.com/wiki/Raw/TCP) + * Original Code and Inspiration: Slavey Karadzhov + */ + +#ifndef LWIPR_COMPAT_H +#define LWIPR_COMPAT_H + +#include + +#if ASYNC_TCP_SSL_ENABLED + +#include "lwipopts.h" +/* + * All those functions will run only if LWIP tcp raw mode is used + */ +#if LWIP_RAW==1 + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "include/ssl.h" + +#define ERR_TCP_SSL_INVALID_SSL -101 +#define ERR_TCP_SSL_INVALID_TCP -102 +#define ERR_TCP_SSL_INVALID_CLIENTFD -103 +#define ERR_TCP_SSL_INVALID_CLIENTFD_DATA -104 +#define ERR_TCP_SSL_INVALID_DATA -105 + +#define TCP_SSL_TYPE_CLIENT 0 +#define TCP_SSL_TYPE_SERVER 1 + +#define tcp_ssl_ssl_write(A, B, C) tcp_ssl_write(A, B, C) +#define tcp_ssl_ssl_read(A, B) tcp_ssl_read(A, B) + +typedef void (* tcp_ssl_data_cb_t)(void *arg, struct tcp_pcb *tcp, uint8_t * data, size_t len); +typedef void (* tcp_ssl_handshake_cb_t)(void *arg, struct tcp_pcb *tcp, SSL *ssl); +typedef void (* tcp_ssl_error_cb_t)(void *arg, struct tcp_pcb *tcp, int8_t error); +typedef int (* tcp_ssl_file_cb_t)(void *arg, const char *filename, uint8_t **buf); + +uint8_t tcp_ssl_has_client(); + +int tcp_ssl_new_client(struct tcp_pcb *tcp); + +SSL_CTX * tcp_ssl_new_server_ctx(const char *cert, const char *private_key_file, const char *password); +int tcp_ssl_new_server(struct tcp_pcb *tcp, SSL_CTX* ssl_ctx); +int tcp_ssl_is_server(struct tcp_pcb *tcp); + +int tcp_ssl_free(struct tcp_pcb *tcp); +int tcp_ssl_read(struct tcp_pcb *tcp, struct pbuf *p); + +#ifdef AXTLS_2_0_0_SNDBUF +int tcp_ssl_sndbuf(struct tcp_pcb *tcp); +#endif + +int tcp_ssl_write(struct tcp_pcb *tcp, uint8_t *data, size_t len); + +void tcp_ssl_file(tcp_ssl_file_cb_t cb, void * arg); + +void tcp_ssl_arg(struct tcp_pcb *tcp, void * arg); +void tcp_ssl_data(struct tcp_pcb *tcp, tcp_ssl_data_cb_t arg); +void tcp_ssl_handshake(struct tcp_pcb *tcp, tcp_ssl_handshake_cb_t arg); +void tcp_ssl_err(struct tcp_pcb *tcp, tcp_ssl_error_cb_t arg); + +SSL * tcp_ssl_get_ssl(struct tcp_pcb *tcp); +bool tcp_ssl_has(struct tcp_pcb *tcp); + +#ifdef __cplusplus +} +#endif + +#endif /* LWIP_RAW==1 */ + +#endif /* ASYNC_TCP_SSL_ENABLED */ + +#endif /* LWIPR_COMPAT_H */ diff --git a/libraries/ESPAsyncTCP/ssl/gen_server_cert.sh b/libraries/ESPAsyncTCP/ssl/gen_server_cert.sh new file mode 100644 index 0000000..fd749ed --- /dev/null +++ b/libraries/ESPAsyncTCP/ssl/gen_server_cert.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +cat > ca_cert.conf << EOF +[ req ] +distinguished_name = req_distinguished_name +prompt = no + +[ req_distinguished_name ] + O = Espressif Systems +EOF + +openssl genrsa -out axTLS.ca_key.pem 2048 +openssl req -new -config ./ca_cert.conf -key axTLS.ca_key.pem -out axTLS.ca_x509.req +openssl x509 -req -sha1 -days 5000 -signkey axTLS.ca_key.pem -CAkey axTLS.ca_key.pem -in axTLS.ca_x509.req -out axTLS.ca_x509.pem + +cat > certs.conf << EOF +[ req ] +distinguished_name = req_distinguished_name +prompt = no + +[ req_distinguished_name ] + O = axTLS on ESP8266 + CN = esp8266.local +EOF + +openssl genrsa -out axTLS.key_1024.pem 1024 +openssl req -new -config ./certs.conf -key axTLS.key_1024.pem -out axTLS.x509_1024.req +openssl x509 -req -sha1 -CAcreateserial -days 5000 -CA axTLS.ca_x509.pem -CAkey axTLS.ca_key.pem -in axTLS.x509_1024.req -out axTLS.x509_1024.pem + +openssl rsa -outform DER -in axTLS.key_1024.pem -out axTLS.key_1024 +openssl x509 -outform DER -in axTLS.x509_1024.pem -out axTLS.x509_1024.cer + +cat axTLS.key_1024 > server.key +cat axTLS.x509_1024.cer > server.cer + +rm axTLS.* ca_cert.conf certs.conf diff --git a/libraries/ESPAsyncTCP/ssl/server.cer b/libraries/ESPAsyncTCP/ssl/server.cer new file mode 100644 index 0000000..b5e5f24 Binary files /dev/null and b/libraries/ESPAsyncTCP/ssl/server.cer differ diff --git a/libraries/ESPAsyncTCP/ssl/server.key b/libraries/ESPAsyncTCP/ssl/server.key new file mode 100644 index 0000000..1b7095f Binary files /dev/null and b/libraries/ESPAsyncTCP/ssl/server.key differ diff --git a/libraries/ESPAsyncTCP/travis/build-pio.sh b/libraries/ESPAsyncTCP/travis/build-pio.sh new file mode 100644 index 0000000..dc918d7 --- /dev/null +++ b/libraries/ESPAsyncTCP/travis/build-pio.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +echo -e "travis_fold:start:install_pio" +pip install -U platformio +if [ $? -ne 0 ]; then exit 1; fi +echo -e "travis_fold:end:install_pio" + +echo -e "travis_fold:start:install_lib" +python -m platformio lib --storage-dir $TRAVIS_BUILD_DIR install +if [ $? -ne 0 ]; then exit 1; fi +echo -e "travis_fold:end:install_lib" + +echo -e "travis_fold:start:test_pio" +for EXAMPLE in $TRAVIS_BUILD_DIR/examples/*/*.ino; do + python -m platformio ci $EXAMPLE -l '.' -b esp12e + if [ $? -ne 0 ]; then exit 1; fi +done +echo -e "travis_fold:end:test_pio" + +echo -e "travis_fold:start:install_json" +python -m platformio lib -g install https://github.com/bblanchon/ArduinoJson.git +if [ $? -ne 0 ]; then exit 1; fi +echo -e "travis_fold:end:install_json" + +cd $HOME/ +echo -e "travis_fold:start:install_web_server" +git clone https://github.com/me-no-dev/ESPAsyncWebServer +if [ $? -ne 0 ]; then exit 1; fi +python -m platformio lib --storage-dir $HOME/ESPAsyncWebServer install +if [ $? -ne 0 ]; then exit 1; fi +echo -e "travis_fold:end:install_web_server" + +echo -e "travis_fold:start:test_web_server" +for EXAMPLE in $HOME/ESPAsyncWebServer/examples/*/*.ino; do + python -m platformio ci $EXAMPLE -l $TRAVIS_BUILD_DIR -l $HOME/ESPAsyncWebServer -b esp12e + if [ $? -ne 0 ]; then exit 1; fi +done +echo -e "travis_fold:end:test_web_server" diff --git a/libraries/ESPAsyncTCP/travis/build.sh b/libraries/ESPAsyncTCP/travis/build.sh new file mode 100644 index 0000000..329b6cc --- /dev/null +++ b/libraries/ESPAsyncTCP/travis/build.sh @@ -0,0 +1,204 @@ +#!/bin/bash + +CHUNK_INDEX=$1 +CHUNKS_CNT=$2 +if [ "$#" -lt 2 ]; then + echo "Building all sketches" + CHUNK_INDEX=0 + CHUNKS_CNT=1 +fi +if [ "$CHUNKS_CNT" -le 0 ]; then + echo "Chunks count must be positive number" + exit 1 +fi +if [ "$CHUNK_INDEX" -ge "$CHUNKS_CNT" ]; then + echo "Chunk index must be less than chunks count" + exit 1 +fi + +echo -e "travis_fold:start:prep_arduino_ide" +# Install Arduino IDE +wget -O arduino.tar.xz https://www.arduino.cc/download.php?f=/arduino-nightly-linux64.tar.xz +tar xf arduino.tar.xz +mv arduino-nightly $HOME/arduino_ide +mkdir -p $HOME/Arduino/libraries +mkdir -p $HOME/Arduino/hardware +echo -e "travis_fold:end:prep_arduino_ide" + +echo -e "travis_fold:start:sketch_test_env_prepare" +cd $HOME/Arduino/libraries +cp -rf $TRAVIS_BUILD_DIR ESPAsyncTCP +PLATFORM_EXAMPLES=$TRAVIS_BUILD_DIR/examples + +cd $HOME/Arduino/libraries +git clone https://github.com/me-no-dev/ESPAsyncWebServer +git clone https://github.com/bblanchon/ArduinoJson +LIB_EXAMPLES=$HOME/Arduino/libraries/ESPAsyncWebServer/examples + +cd $HOME/Arduino/hardware +mkdir esp8266com +cd esp8266com +git clone https://github.com/esp8266/Arduino.git esp8266 +cd esp8266 +git submodule update --init --recursive +cd tools +python get.py +PLATFORM_FQBN="esp8266com:esp8266:generic:xtal=80,FlashFreq=40,FlashMode=qio,baud=921600,eesz=4M1M,ip=lm2f,ResetMethod=nodemcu" +PLATFORM_SIZE_BIN=$HOME/Arduino/hardware/esp8266com/esp8266/tools/xtensa-lx106-elf/bin/xtensa-lx106-elf-size +echo -e "travis_fold:end:sketch_test_env_prepare" + +cd $TRAVIS_BUILD_DIR + +ARDUINO_IDE_PATH=$HOME/arduino_ide +ARDUINO_USR_PATH=$HOME/Arduino +ARDUINO_BUILD_DIR=$HOME/build.tmp +ARDUINO_CACHE_DIR=$HOME/cache.tmp +ARDUINO_BUILD_CMD="$ARDUINO_IDE_PATH/arduino-builder -compile -logger=human -core-api-version=10810 -hardware \"$ARDUINO_IDE_PATH/hardware\" -hardware \"$ARDUINO_USR_PATH/hardware\" -tools \"$ARDUINO_IDE_PATH/tools-builder\" -built-in-libraries \"$ARDUINO_IDE_PATH/libraries\" -libraries \"$ARDUINO_USR_PATH/libraries\" -fqbn=$PLATFORM_FQBN -warnings=\"all\" -build-cache \"$ARDUINO_CACHE_DIR\" -build-path \"$ARDUINO_BUILD_DIR\" -verbose" + +function print_size_info() +{ + elf_file=$1 + + if [ -z "$elf_file" ]; then + printf "sketch data rodata bss text irom0.text dram flash\n" + return 0 + fi + + elf_name=$(basename $elf_file) + sketch_name="${elf_name%.*}" + declare -A segments + while read -a tokens; do + seg=${tokens[0]} + seg=${seg//./} + size=${tokens[1]} + addr=${tokens[2]} + if [ "$addr" -eq "$addr" -a "$addr" -ne "0" ] 2>/dev/null; then + segments[$seg]=$size + fi + done < <($PLATFORM_SIZE_BIN --format=sysv $elf_file) + + total_ram=$((${segments[data]} + ${segments[rodata]} + ${segments[bss]})) + total_flash=$((${segments[data]} + ${segments[rodata]} + ${segments[text]} + ${segments[irom0text]})) + printf "%-28s %-8d %-8d %-8d %-8d %-8d %-8d %-8d\n" $sketch_name ${segments[data]} ${segments[rodata]} ${segments[bss]} ${segments[text]} ${segments[irom0text]} $total_ram $total_flash + return 0 +} + +function build_sketch() +{ + local sketch=$1 + echo -e "\n------------ Building $sketch ------------\n"; + rm -rf $ARDUINO_BUILD_DIR/* + time ($ARDUINO_BUILD_CMD $sketch >build.log) + local result=$? + if [ $result -ne 0 ]; then + echo "Build failed ($1)" + echo "Build log:" + cat build.log + return $result + fi + rm build.log + return 0 +} + +function count_sketches() +{ + local path=$1 + local sketches=$(find $path -name *.ino) + local sketchnum=0 + for sketch in $sketches; do + local sketchdir=$(dirname $sketch) + local sketchdirname=$(basename $sketchdir) + local sketchname=$(basename $sketch) + if [[ "${sketchdirname}.ino" != "$sketchname" ]]; then + continue + fi + echo $sketch >> sketches.txt + sketchnum=$(($sketchnum + 1)) + done + return $sketchnum +} + +function build_sketches() +{ + mkdir -p $ARDUINO_BUILD_DIR + mkdir -p $ARDUINO_CACHE_DIR + mkdir -p $ARDUINO_USR_PATH/libraries + mkdir -p $ARDUINO_USR_PATH/hardware + + local chunk_idex=$1 + local chunks_num=$2 + rm -rf sketches.txt + count_sketches $PLATFORM_EXAMPLES + local sketchcount=$? + count_sketches $LIB_EXAMPLES + local libsketchcount=$? + sketchcount=$(($sketchcount + $libsketchcount)) + local sketches=$(cat sketches.txt) + + local chunk_size=$(( $sketchcount / $chunks_num )) + local all_chunks=$(( $chunks_num * $chunk_size )) + if [ "$all_chunks" -lt "$sketchcount" ]; then + chunk_size=$(( $chunk_size + 1 )) + fi + + local start_index=$(( $chunk_idex * $chunk_size )) + if [ "$sketchcount" -le "$start_index" ]; then + echo "Skipping job" + return 0 + fi + + local end_index=$(( $(( $chunk_idex + 1 )) * $chunk_size )) + if [ "$end_index" -gt "$sketchcount" ]; then + end_index=$sketchcount + fi + + local start_num=$(( $start_index + 1 )) + #echo -e "Sketches: \n$sketches\n" + echo "Found $sketchcount Sketches"; + echo "Chunk Count : $chunks_num" + echo "Chunk Size : $chunk_size" + echo "Start Sketch: $start_num" + echo "End Sketch : $end_index" + + local sketchnum=0 + print_size_info >size.log + for sketch in $sketches; do + local sketchdir=$(dirname $sketch) + local sketchdirname=$(basename $sketchdir) + local sketchname=$(basename $sketch) + if [[ "${sketchdirname}.ino" != "$sketchname" ]]; then + #echo "Skipping $sketch, beacause it is not the main sketch file"; + continue + fi; + if [[ -f "$sketchdir/.test.skip" ]]; then + #echo "Skipping $sketch marked"; + continue + fi + sketchnum=$(($sketchnum + 1)) + if [ "$sketchnum" -le "$start_index" ]; then + #echo "Skipping $sketch index low" + continue + fi + if [ "$sketchnum" -gt "$end_index" ]; then + #echo "Skipping $sketch index high" + continue + fi + build_sketch $sketch + local result=$? + if [ $result -ne 0 ]; then + return $result + fi + print_size_info $ARDUINO_BUILD_DIR/*.elf >>size.log + done + return 0 +} + +echo -e "travis_fold:start:test_arduino_ide" +# Build Examples +build_sketches $CHUNK_INDEX $CHUNKS_CNT +if [ $? -ne 0 ]; then exit 1; fi +echo -e "travis_fold:end:test_arduino_ide" + +echo -e "travis_fold:start:size_report" +cat size.log +echo -e "travis_fold:end:size_report" diff --git a/libraries/async-mqtt-client/.editorconfig b/libraries/async-mqtt-client/.editorconfig new file mode 100644 index 0000000..3dccc6f --- /dev/null +++ b/libraries/async-mqtt-client/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[keywords.txt] +indent_style = tab diff --git a/libraries/async-mqtt-client/.gitignore b/libraries/async-mqtt-client/.gitignore new file mode 100644 index 0000000..7d3a24e --- /dev/null +++ b/libraries/async-mqtt-client/.gitignore @@ -0,0 +1 @@ +/config.json diff --git a/libraries/async-mqtt-client/.travis.yml b/libraries/async-mqtt-client/.travis.yml new file mode 100644 index 0000000..8ea2eb9 --- /dev/null +++ b/libraries/async-mqtt-client/.travis.yml @@ -0,0 +1,20 @@ +language: python +python: + - "2.7" + +cache: + directories: + - "~/.platformio" + +env: + - PLATFORMIO_CI_SRC=examples/FullyFeatured-ESP8266 PLATFORMIO_CI_EXTRA_ARGS="--board=esp01 --board=nodemcuv2" + - PLATFORMIO_CI_SRC=examples/FullyFeatured-ESP32 PLATFORMIO_CI_EXTRA_ARGS="--board=lolin32" + - CPPLINT=true + +install: + - pip install -U https://github.com/platformio/platformio-core/archive/develop.zip + - pip install -U cpplint + - platformio lib -g install file://. + +script: + - if [[ "$CPPLINT" ]]; then make cpplint; else platformio ci $PLATFORMIO_CI_EXTRA_ARGS; fi diff --git a/libraries/async-mqtt-client/LICENSE b/libraries/async-mqtt-client/LICENSE new file mode 100644 index 0000000..a6183c6 --- /dev/null +++ b/libraries/async-mqtt-client/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Marvin Roger + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/libraries/async-mqtt-client/Makefile b/libraries/async-mqtt-client/Makefile new file mode 100644 index 0000000..43f99dc --- /dev/null +++ b/libraries/async-mqtt-client/Makefile @@ -0,0 +1,3 @@ +cpplint: + cpplint --repository=. --recursive --filter=-whitespace/line_length,-legal/copyright,-runtime/printf,-build/include,-build/namespace ./src +.PHONY: cpplint diff --git a/libraries/async-mqtt-client/README.md b/libraries/async-mqtt-client/README.md new file mode 100644 index 0000000..af66bee --- /dev/null +++ b/libraries/async-mqtt-client/README.md @@ -0,0 +1,18 @@ +Async MQTT client for ESP8266 and ESP32 +============================= + +[![Build Status](https://img.shields.io/travis/marvinroger/async-mqtt-client/master.svg?style=flat-square)](https://travis-ci.org/marvinroger/async-mqtt-client) + +An Arduino for ESP8266 and ESP32 asynchronous [MQTT](http://mqtt.org/) client implementation, built on [me-no-dev/ESPAsyncTCP (ESP8266)](https://github.com/me-no-dev/ESPAsyncTCP) | [me-no-dev/AsyncTCP (ESP32)](https://github.com/me-no-dev/AsyncTCP) . +## Features + +* Compliant with the 3.1.1 version of the protocol +* Fully asynchronous +* Subscribe at QoS 0, 1 and 2 +* Publish at QoS 0, 1 and 2 +* SSL/TLS support +* Available in the [PlatformIO registry](http://platformio.org/lib/show/346/AsyncMqttClient) + +## Requirements, installation and usage + +The project is documented in the [/docs folder](docs). diff --git a/libraries/async-mqtt-client/async-mqtt-client.cppcheck b/libraries/async-mqtt-client/async-mqtt-client.cppcheck new file mode 100644 index 0000000..da20086 --- /dev/null +++ b/libraries/async-mqtt-client/async-mqtt-client.cppcheck @@ -0,0 +1,6 @@ + + + + + + diff --git a/libraries/async-mqtt-client/docs/1.-Getting-started.md b/libraries/async-mqtt-client/docs/1.-Getting-started.md new file mode 100644 index 0000000..2319b59 --- /dev/null +++ b/libraries/async-mqtt-client/docs/1.-Getting-started.md @@ -0,0 +1,26 @@ +# Getting started + +To use AsyncMqttClient, you need: + +* An ESP8266 +* The Arduino IDE for ESP8266 (version 2.2.0 minimum) +* Basic knowledge of the Arduino environment (upload a sketch, import libraries, ...) + +## Installing AsyncMqttClient + +There are two ways to install AsyncMqttClient. + +### 1a. For the Arduino IDE + +1. Download the [corresponding release](https://github.com/marvinroger/async-mqtt-client/releases/latest) +2. Load the `.zip` with **Sketch → Include Library → Add .ZIP Library** + +AsyncMqttClient has 1 dependency: [ESPAsyncTCP](https://github.com/me-no-dev/ESPAsyncTCP). Download the [.zip](https://github.com/me-no-dev/ESPAsyncTCP/archive/master.zip) and install it with the same method as above. + +## Fully-featured sketch + +See [examples/FullyFeatured-ESP8266.ino](../examples/FullyFeatured-ESP8266/FullyFeatured-ESP8266.ino) + +**Very important: As a rule of thumb, never use blocking functions in the callbacks (don't use `delay()` or `yield()`).** Otherwise, you may very probably experience unexpected behaviors. + +You can go to the [API reference](2.-API-reference.md). diff --git a/libraries/async-mqtt-client/docs/2.-API-reference.md b/libraries/async-mqtt-client/docs/2.-API-reference.md new file mode 100644 index 0000000..d992184 --- /dev/null +++ b/libraries/async-mqtt-client/docs/2.-API-reference.md @@ -0,0 +1,160 @@ +# API reference + +#### AsyncMqttClient() + +Instantiate a new AsyncMqttClient object. + +### Configuration + +#### AsyncMqttClient& setKeepAlive(uint16_t `keepAlive`) + +Set the keep alive. Defaults to 15 seconds. + +* **`keepAlive`**: Keep alive in seconds + +#### AsyncMqttClient& setClientId(const char\* `clientId`) + +Set the client ID. Defaults to `esp8266`. + +* **`clientId`**: Client ID + +#### AsyncMqttClient& setCleanSession(bool `cleanSession`) + +Whether or not to set the CleanSession flag. Defaults to `true`. + +* **`cleanSession`**: clean session wanted or not + +#### AsyncMqttClient& setMaxTopicLength(uint16_t `maxTopicLength`) + +Set the maximum allowed topic length to receive. If an MQTT packet is received +with a topic longer than this maximum, the packet will be ignored. Defaults to `128`. + +* **`maxTopicLength`**: Maximum allowed topic length to receive + +#### AsyncMqttClient& setCredentials(const char\* `username`, const char\* `password` = nullptr) + +Set the username/password. Defaults to non-auth. + +* **`username`**: Username +* **`password`**: Password + +#### AsyncMqttClient& setWill(const char\* `topic`, uint8_t `qos`, bool `retain`, const char\* `payload` = nullptr, size_t `length` = 0) + +Set the Last Will Testament. Defaults to none. + +* **`topic`**: Topic of the LWT +* **`qos`**: QoS of the LWT +* **`retain`**: Retain flag of the LWT +* **`payload`**: Payload of the LWT. If unset, the payload will be empty +* **`length`**: Payload length. If unset or set to 0, the payload will be considered as a string and its size will be calculated using `strlen(payload)` + +#### AsyncMqttClient& setServer(IPAddress `ip`, uint16_t `port`) + +Set the server. + +* **`ip`**: IP of the server +* **`port`**: Port of the server + +#### AsyncMqttClient& setServer(const char\* `host`, uint16_t `port`) + +Set the server. + +* **`host`**: Host of the server +* **`port`**: Port of the server + +#### AsyncMqttClient& setSecure(bool `secure`) + +Whether or not to use SSL. Defaults to `false`. + +* **`secure`**: SSL wanted or not. + +#### AsyncMqttClient& addServerFingerprint(const uint8_t\* `fingerprint`) + +Adds an acceptable server fingerprint (SHA1). This may be called multiple times to permit any one of the specified fingerprints. By default, if no fingerprint is added, any fingerprint is accepted. + +* **`fingerprint`**: Fingerprint to add + +### Events handlers + +#### AsyncMqttClient& onConnect(AsyncMqttClientInternals::OnConnectUserCallback `callback`) + +Add a connect event handler. + +* **`callback`**: Function to call + +#### AsyncMqttClient& onDisconnect(AsyncMqttClientInternals::OnDisconnectUserCallback `callback`) + +Add a disconnect event handler. + +* **`callback`**: Function to call + +#### AsyncMqttClient& onSubscribe(AsyncMqttClientInternals::OnSubscribeUserCallback `callback`) + +Add a subscribe acknowledged event handler. + +* **`callback`**: Function to call + +#### AsyncMqttClient& onUnsubscribe(AsyncMqttClientInternals::OnUnsubscribeUserCallback `callback`) + +Add an unsubscribe acknowledged event handler. + +* **`callback`**: Function to call + +#### AsyncMqttClient& onMessage(AsyncMqttClientInternals::OnMessageUserCallback `callback`) + +Add a publish received event handler. + +* **`callback`**: Function to call + +#### AsyncMqttClient& onPublish(AsyncMqttClientInternals::OnPublishUserCallback `callback`) + +Add a publish acknowledged event handler. + +* **`callback`**: Function to call + +### Operation functions + +#### bool connected() + +Return if the client is currently connected to the broker or not. + +#### void connect() + +Connect to the server. + +#### void disconnect(bool `force` = false) + +Disconnect from the server. + +* **`force`**: Whether to force the disconnection. Defaults to `false` (clean disconnection). + +#### uint16_t subscribe(const char\* `topic`, uint8_t `qos`) + +Subscribe to the given topic at the given QoS. + +Return the packet ID or 0 if failed. + +* **`topic`**: Topic +* **`qos`**: QoS + +#### uint16_t unsubscribe(const char\* `topic`) + +Unsubscribe from the given topic. + +Return the packet ID or 0 if failed. + +* **`topic`**: Topic + +#### uint16_t publish(const char\* `topic`, uint8_t `qos`, bool `retain`, const char\* `payload` = nullptr, size_t `length` = 0, bool dup = false, uint16_t message_id = 0) + +Publish a packet. + +Return the packet ID (or 1 if QoS 0) or 0 if failed. + +* **`topic`**: Topic +* **`qos`**: QoS +* **`retain`**: Retain flag +* **`payload`**: Payload. If unset, the payload will be empty +* **`length`**: Payload length. If unset or set to 0, the payload will be considered as a string and its size will be calculated using `strlen(payload)` +* **`dup`**: Duplicate flag. If set or set to 1, the payload will be flagged as a duplicate +* **`message_id`**: The message ID. If unset or set to 0, the message ID will be automtaically assigned. Use this with the DUP flag to identify which message is being duplicated diff --git a/libraries/async-mqtt-client/docs/3.-Memory-management.md b/libraries/async-mqtt-client/docs/3.-Memory-management.md new file mode 100644 index 0000000..ad27d19 --- /dev/null +++ b/libraries/async-mqtt-client/docs/3.-Memory-management.md @@ -0,0 +1,7 @@ +# Memory management + +AsyncMqttClient does not use an internal buffer, it uses the raw TCP buffer. + +The max receive size is about 1460 bytes per call to your onMessage callback. But the amount of data you can receive is unlimited, as if you receive, say, a 300kB payload (such as an OTA payload), then your `onMessage` callback will be called about 200 times, with the according len, index and total parameters. Keep in mind the library will call your `onMessage` callbacks with the same topic buffer, so if you change the buffer on one call, the buffer will remain changed on subsequent calls. + +You can send data as long as you stay below the available TCP window (which is about 3-4kB on the ESP8266). The data is indeed held in memory by the async TCP code until ACK is received. If the TCP window was sufficient to send your packet, the `publish` method will return a packet ID indicating the packet was sent. Otherwise, a `0` will be returned, and it's your responsability to resend the packet with `publish`. diff --git a/libraries/async-mqtt-client/docs/4.-Limitations-and-known-issues.md b/libraries/async-mqtt-client/docs/4.-Limitations-and-known-issues.md new file mode 100644 index 0000000..c85b0d3 --- /dev/null +++ b/libraries/async-mqtt-client/docs/4.-Limitations-and-known-issues.md @@ -0,0 +1,19 @@ +# Limitations and known issues + +* When the CleanSession is set to `false`, the implementation is not spec compliant. The following is not honored: + +> Must be kept in memory: +* All messages in a QoS 1 or 2 flow, which are not confirmed by the broker +* All received QoS 2 messages, which are not yet confirmed to the broker + +This means retransmission is not honored in case of a failure. + +* You cannot send payload larger that what can fit on RAM. + +## SSL limitations + +* SSL requires use of esp8266/Arduino 2.4.0, which is not yet released (platform = espressif8266_stage in PlatformIO). +* SSL requires the build flag -DASYNC_TCP_SSL_ENABLED=1 +* SSL only supports fingerprints for server validation. +* If you do not specify one or more acceptable server fingerprints, the SSL connection will be vulnerable to man-in-the-middle attacks. +* Some server certificate signature algorithms do not work. SHA1, SHA224, SHA256, and MD5 are working. SHA384, and SHA512 will cause a crash. diff --git a/libraries/async-mqtt-client/docs/5.-Troubleshooting.md b/libraries/async-mqtt-client/docs/5.-Troubleshooting.md new file mode 100644 index 0000000..a59672b --- /dev/null +++ b/libraries/async-mqtt-client/docs/5.-Troubleshooting.md @@ -0,0 +1,3 @@ +# Troubleshooting + +To be completed when issues arise. diff --git a/libraries/async-mqtt-client/docs/README.md b/libraries/async-mqtt-client/docs/README.md new file mode 100644 index 0000000..d15e1a5 --- /dev/null +++ b/libraries/async-mqtt-client/docs/README.md @@ -0,0 +1,4 @@ +AsyncMqttClient documentation +============================= + +See [index.md](index.md) to view it locally, or http://marvinroger.viewdocs.io/async-mqtt-client/ to view it online. diff --git a/libraries/async-mqtt-client/docs/index.md b/libraries/async-mqtt-client/docs/index.md new file mode 100644 index 0000000..5b74ed4 --- /dev/null +++ b/libraries/async-mqtt-client/docs/index.md @@ -0,0 +1,11 @@ +Welcome to the AsyncMqttClient for ESP8266 docs. + +**

This documentation is only valid for the AsyncMqttClient version in this repo/directory

** + +----- + +#### 1. [Getting started](1.-Getting-started.md) +#### 2. [API reference](2.-API-reference.md) +#### 3. [Memory management](3.-Memory-management.md) +#### 4. [Limitations and known issues](4.-Limitations-and-known-issues.md) +#### 5. [Troubleshooting](5.-Troubleshooting.md) diff --git a/libraries/async-mqtt-client/examples/FullyFeatured-ESP32/FullyFeatured-ESP32.ino b/libraries/async-mqtt-client/examples/FullyFeatured-ESP32/FullyFeatured-ESP32.ino new file mode 100644 index 0000000..c564640 --- /dev/null +++ b/libraries/async-mqtt-client/examples/FullyFeatured-ESP32/FullyFeatured-ESP32.ino @@ -0,0 +1,135 @@ +/* +This example uses FreeRTOS softwaretimers as there is no built-in Ticker library +*/ + + +#include +extern "C" { + #include "freertos/FreeRTOS.h" + #include "freertos/timers.h" +} +#include + +#define WIFI_SSID "yourSSID" +#define WIFI_PASSWORD "yourpass" + +#define MQTT_HOST IPAddress(192, 168, 1, 10) +#define MQTT_PORT 1883 + +AsyncMqttClient mqttClient; +TimerHandle_t mqttReconnectTimer; +TimerHandle_t wifiReconnectTimer; + +void connectToWifi() { + Serial.println("Connecting to Wi-Fi..."); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); +} + +void connectToMqtt() { + Serial.println("Connecting to MQTT..."); + mqttClient.connect(); +} + +void WiFiEvent(WiFiEvent_t event) { + Serial.printf("[WiFi-event] event: %d\n", event); + switch(event) { + case SYSTEM_EVENT_STA_GOT_IP: + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + connectToMqtt(); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + Serial.println("WiFi lost connection"); + xTimerStop(mqttReconnectTimer, 0); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi + xTimerStart(wifiReconnectTimer, 0); + break; + } +} + +void onMqttConnect(bool sessionPresent) { + Serial.println("Connected to MQTT."); + Serial.print("Session present: "); + Serial.println(sessionPresent); + uint16_t packetIdSub = mqttClient.subscribe("test/lol", 2); + Serial.print("Subscribing at QoS 2, packetId: "); + Serial.println(packetIdSub); + mqttClient.publish("test/lol", 0, true, "test 1"); + Serial.println("Publishing at QoS 0"); + uint16_t packetIdPub1 = mqttClient.publish("test/lol", 1, true, "test 2"); + Serial.print("Publishing at QoS 1, packetId: "); + Serial.println(packetIdPub1); + uint16_t packetIdPub2 = mqttClient.publish("test/lol", 2, true, "test 3"); + Serial.print("Publishing at QoS 2, packetId: "); + Serial.println(packetIdPub2); +} + +void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { + Serial.println("Disconnected from MQTT."); + + if (WiFi.isConnected()) { + xTimerStart(mqttReconnectTimer, 0); + } +} + +void onMqttSubscribe(uint16_t packetId, uint8_t qos) { + Serial.println("Subscribe acknowledged."); + Serial.print(" packetId: "); + Serial.println(packetId); + Serial.print(" qos: "); + Serial.println(qos); +} + +void onMqttUnsubscribe(uint16_t packetId) { + Serial.println("Unsubscribe acknowledged."); + Serial.print(" packetId: "); + Serial.println(packetId); +} + +void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { + Serial.println("Publish received."); + Serial.print(" topic: "); + Serial.println(topic); + Serial.print(" qos: "); + Serial.println(properties.qos); + Serial.print(" dup: "); + Serial.println(properties.dup); + Serial.print(" retain: "); + Serial.println(properties.retain); + Serial.print(" len: "); + Serial.println(len); + Serial.print(" index: "); + Serial.println(index); + Serial.print(" total: "); + Serial.println(total); +} + +void onMqttPublish(uint16_t packetId) { + Serial.println("Publish acknowledged."); + Serial.print(" packetId: "); + Serial.println(packetId); +} + +void setup() { + Serial.begin(115200); + Serial.println(); + Serial.println(); + + mqttReconnectTimer = xTimerCreate("mqttTimer", pdMS_TO_TICKS(2000), pdFALSE, (void*)0, reinterpret_cast(connectToMqtt)); + wifiReconnectTimer = xTimerCreate("wifiTimer", pdMS_TO_TICKS(2000), pdFALSE, (void*)0, reinterpret_cast(connectToWifi)); + + WiFi.onEvent(WiFiEvent); + + mqttClient.onConnect(onMqttConnect); + mqttClient.onDisconnect(onMqttDisconnect); + mqttClient.onSubscribe(onMqttSubscribe); + mqttClient.onUnsubscribe(onMqttUnsubscribe); + mqttClient.onMessage(onMqttMessage); + mqttClient.onPublish(onMqttPublish); + mqttClient.setServer(MQTT_HOST, MQTT_PORT); + + connectToWifi(); +} + +void loop() { +} diff --git a/libraries/async-mqtt-client/examples/FullyFeatured-ESP8266/FullyFeatured-ESP8266.ino b/libraries/async-mqtt-client/examples/FullyFeatured-ESP8266/FullyFeatured-ESP8266.ino new file mode 100644 index 0000000..82f981d --- /dev/null +++ b/libraries/async-mqtt-client/examples/FullyFeatured-ESP8266/FullyFeatured-ESP8266.ino @@ -0,0 +1,122 @@ +#include +#include +#include + +#define WIFI_SSID "My_Wi-Fi" +#define WIFI_PASSWORD "my-awesome-password" + +#define MQTT_HOST IPAddress(192, 168, 1, 10) +#define MQTT_PORT 1883 + +AsyncMqttClient mqttClient; +Ticker mqttReconnectTimer; + +WiFiEventHandler wifiConnectHandler; +WiFiEventHandler wifiDisconnectHandler; +Ticker wifiReconnectTimer; + +void connectToWifi() { + Serial.println("Connecting to Wi-Fi..."); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); +} + +void onWifiConnect(const WiFiEventStationModeGotIP& event) { + Serial.println("Connected to Wi-Fi."); + connectToMqtt(); +} + +void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) { + Serial.println("Disconnected from Wi-Fi."); + mqttReconnectTimer.detach(); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi + wifiReconnectTimer.once(2, connectToWifi); +} + +void connectToMqtt() { + Serial.println("Connecting to MQTT..."); + mqttClient.connect(); +} + +void onMqttConnect(bool sessionPresent) { + Serial.println("Connected to MQTT."); + Serial.print("Session present: "); + Serial.println(sessionPresent); + uint16_t packetIdSub = mqttClient.subscribe("test/lol", 2); + Serial.print("Subscribing at QoS 2, packetId: "); + Serial.println(packetIdSub); + mqttClient.publish("test/lol", 0, true, "test 1"); + Serial.println("Publishing at QoS 0"); + uint16_t packetIdPub1 = mqttClient.publish("test/lol", 1, true, "test 2"); + Serial.print("Publishing at QoS 1, packetId: "); + Serial.println(packetIdPub1); + uint16_t packetIdPub2 = mqttClient.publish("test/lol", 2, true, "test 3"); + Serial.print("Publishing at QoS 2, packetId: "); + Serial.println(packetIdPub2); +} + +void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { + Serial.println("Disconnected from MQTT."); + + if (WiFi.isConnected()) { + mqttReconnectTimer.once(2, connectToMqtt); + } +} + +void onMqttSubscribe(uint16_t packetId, uint8_t qos) { + Serial.println("Subscribe acknowledged."); + Serial.print(" packetId: "); + Serial.println(packetId); + Serial.print(" qos: "); + Serial.println(qos); +} + +void onMqttUnsubscribe(uint16_t packetId) { + Serial.println("Unsubscribe acknowledged."); + Serial.print(" packetId: "); + Serial.println(packetId); +} + +void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { + Serial.println("Publish received."); + Serial.print(" topic: "); + Serial.println(topic); + Serial.print(" qos: "); + Serial.println(properties.qos); + Serial.print(" dup: "); + Serial.println(properties.dup); + Serial.print(" retain: "); + Serial.println(properties.retain); + Serial.print(" len: "); + Serial.println(len); + Serial.print(" index: "); + Serial.println(index); + Serial.print(" total: "); + Serial.println(total); +} + +void onMqttPublish(uint16_t packetId) { + Serial.println("Publish acknowledged."); + Serial.print(" packetId: "); + Serial.println(packetId); +} + +void setup() { + Serial.begin(115200); + Serial.println(); + Serial.println(); + + wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect); + wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect); + + mqttClient.onConnect(onMqttConnect); + mqttClient.onDisconnect(onMqttDisconnect); + mqttClient.onSubscribe(onMqttSubscribe); + mqttClient.onUnsubscribe(onMqttUnsubscribe); + mqttClient.onMessage(onMqttMessage); + mqttClient.onPublish(onMqttPublish); + mqttClient.setServer(MQTT_HOST, MQTT_PORT); + + connectToWifi(); +} + +void loop() { +} diff --git a/libraries/async-mqtt-client/examples/FullyFeaturedSSL/platformio.ini b/libraries/async-mqtt-client/examples/FullyFeaturedSSL/platformio.ini new file mode 100644 index 0000000..8fc8816 --- /dev/null +++ b/libraries/async-mqtt-client/examples/FullyFeaturedSSL/platformio.ini @@ -0,0 +1,24 @@ +# +# Example PlatformIO configuration file for SSL and non-SSL builds. +# +# Before you will be able to build the SSL version of this project, you will +# need to explicitly install the espressif8266_stage platform. +# +# To perform this installation, refer to step 1 of: +# http://docs.platformio.org/en/latest/platforms/espressif8266.html#using-arduino-framework-with-staging-version + +[platformio] +env_default = ssl + +[env:ssl] +platform = espressif8266_stage +framework = arduino +board = esp01_1m +build_flags = -DASYNC_TCP_SSL_ENABLED=1 +lib_deps = AsyncMqttClient + +[env:nossl] +platform = espressif8266 +framework = arduino +board = esp01_1m +lib_deps = AsyncMqttClient diff --git a/libraries/async-mqtt-client/examples/FullyFeaturedSSL/src/main.cpp b/libraries/async-mqtt-client/examples/FullyFeaturedSSL/src/main.cpp new file mode 100644 index 0000000..cd2cb8b --- /dev/null +++ b/libraries/async-mqtt-client/examples/FullyFeaturedSSL/src/main.cpp @@ -0,0 +1,145 @@ + +// Example project which can be built with SSL enabled or disabled. +// The espressif8266_stage platform must be installed. +// Refer to platformio.ini for the build configuration and platform installation. + +#include +#include +#include +#include + +#define WIFI_SSID "My_Wi-Fi" +#define WIFI_PASSWORD "my-awesome-password" + +#define MQTT_HOST IPAddress(192, 168, 1, 10) + +#if ASYNC_TCP_SSL_ENABLED +#define MQTT_SECURE true +#define MQTT_SERVER_FINGERPRINT {0x7e, 0x36, 0x22, 0x01, 0xf9, 0x7e, 0x99, 0x2f, 0xc5, 0xdb, 0x3d, 0xbe, 0xac, 0x48, 0x67, 0x5b, 0x5d, 0x47, 0x94, 0xd2} +#define MQTT_PORT 8883 +#else +#define MQTT_PORT 1883 +#endif + +AsyncMqttClient mqttClient; +Ticker mqttReconnectTimer; + +WiFiEventHandler wifiConnectHandler; +WiFiEventHandler wifiDisconnectHandler; +Ticker wifiReconnectTimer; + +void connectToWifi() { + Serial.println("Connecting to Wi-Fi..."); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); +} + +void connectToMqtt() { + Serial.println("Connecting to MQTT..."); + mqttClient.connect(); +} + +void onWifiConnect(const WiFiEventStationModeGotIP& event) { + Serial.println("Connected to Wi-Fi."); + connectToMqtt(); +} + +void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) { + Serial.println("Disconnected from Wi-Fi."); + mqttReconnectTimer.detach(); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi + wifiReconnectTimer.once(2, connectToWifi); +} + +void onMqttConnect(bool sessionPresent) { + Serial.println("Connected to MQTT."); + Serial.print("Session present: "); + Serial.println(sessionPresent); + uint16_t packetIdSub = mqttClient.subscribe("test/lol", 2); + Serial.print("Subscribing at QoS 2, packetId: "); + Serial.println(packetIdSub); + mqttClient.publish("test/lol", 0, true, "test 1"); + Serial.println("Publishing at QoS 0"); + uint16_t packetIdPub1 = mqttClient.publish("test/lol", 1, true, "test 2"); + Serial.print("Publishing at QoS 1, packetId: "); + Serial.println(packetIdPub1); + uint16_t packetIdPub2 = mqttClient.publish("test/lol", 2, true, "test 3"); + Serial.print("Publishing at QoS 2, packetId: "); + Serial.println(packetIdPub2); +} + +void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { + Serial.println("Disconnected from MQTT."); + + if (reason == AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT) { + Serial.println("Bad server fingerprint."); + } + + if (WiFi.isConnected()) { + mqttReconnectTimer.once(2, connectToMqtt); + } +} + +void onMqttSubscribe(uint16_t packetId, uint8_t qos) { + Serial.println("Subscribe acknowledged."); + Serial.print(" packetId: "); + Serial.println(packetId); + Serial.print(" qos: "); + Serial.println(qos); +} + +void onMqttUnsubscribe(uint16_t packetId) { + Serial.println("Unsubscribe acknowledged."); + Serial.print(" packetId: "); + Serial.println(packetId); +} + +void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { + Serial.println("Publish received."); + Serial.print(" topic: "); + Serial.println(topic); + Serial.print(" qos: "); + Serial.println(properties.qos); + Serial.print(" dup: "); + Serial.println(properties.dup); + Serial.print(" retain: "); + Serial.println(properties.retain); + Serial.print(" len: "); + Serial.println(len); + Serial.print(" index: "); + Serial.println(index); + Serial.print(" total: "); + Serial.println(total); +} + +void onMqttPublish(uint16_t packetId) { + Serial.println("Publish acknowledged."); + Serial.print(" packetId: "); + Serial.println(packetId); +} + +void setup() { + Serial.begin(115200); + Serial.println(); + Serial.println(); + + wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect); + wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect); + + mqttClient.onConnect(onMqttConnect); + mqttClient.onDisconnect(onMqttDisconnect); + mqttClient.onSubscribe(onMqttSubscribe); + mqttClient.onUnsubscribe(onMqttUnsubscribe); + mqttClient.onMessage(onMqttMessage); + mqttClient.onPublish(onMqttPublish); + mqttClient.setServer(MQTT_HOST, MQTT_PORT); +#if ASYNC_TCP_SSL_ENABLED + mqttClient.setSecure(MQTT_SECURE); + if (MQTT_SECURE) { + mqttClient.addServerFingerprint((const uint8_t[])MQTT_SERVER_FINGERPRINT); + } +#endif + + connectToWifi(); +} + +void loop() { +} diff --git a/libraries/async-mqtt-client/keywords.txt b/libraries/async-mqtt-client/keywords.txt new file mode 100644 index 0000000..29d74d4 --- /dev/null +++ b/libraries/async-mqtt-client/keywords.txt @@ -0,0 +1,47 @@ +####################################### +# Datatypes (KEYWORD1) +####################################### + +AsyncMqttClient KEYWORD1 +AsyncMqttClientDisconnectReason KEYWORD1 +AsyncMqttClientMessageProperties KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +setKeepAlive KEYWORD2 +setClientId KEYWORD2 +setCleanSession KEYWORD2 +setMaxTopicLength KEYWORD2 +setCredentials KEYWORD2 +setWill KEYWORD2 +setServer KEYWORD2 +setSecure KEYWORD2 +addServerFingerprint KEYWORD2 + +onConnect KEYWORD2 +onDisconnect KEYWORD2 +onSubscribe KEYWORD2 +onUnsubscribe KEYWORD2 +onMessage KEYWORD2 +onPublish KEYWORD2 + +connected KEYWORD2 +connect KEYWORD2 +disconnect KEYWORD2 +subscribe KEYWORD2 +unsubscribe KEYWORD2 +publish KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +TCP_DISCONNECTED LITERAL1 + +MQTT_UNACCEPTABLE_PROTOCOL_VERSION LITERAL1 +MQTT_IDENTIFIER_REJECTED LITERAL1 +MQTT_SERVER_UNAVAILABLE LITERAL1 +MQTT_MALFORMED_CREDENTIALS LITERAL1 +MQTT_NOT_AUTHORIZED LITERAL1 diff --git a/libraries/async-mqtt-client/library.json b/libraries/async-mqtt-client/library.json new file mode 100644 index 0000000..113c4f0 --- /dev/null +++ b/libraries/async-mqtt-client/library.json @@ -0,0 +1,30 @@ +{ + "name": "AsyncMqttClient", + "keywords": "iot, home, automation, async, mqtt, client, esp8266", + "description": "An Arduino for ESP8266 / ESP32 asynchronous MQTT client implementation", + "authors": + { + "name": "Marvin ROGER", + "url": "https://www.marvinroger.fr" + }, + "repository": + { + "type": "git", + "url": "https://github.com/marvinroger/async-mqtt-client.git" + }, + "version": "0.8.2", + "frameworks": "arduino", + "platforms": ["espressif8266", "espressif32"], + "dependencies": [ + { + "name": "ESPAsyncTCP", + "version": "^1.1.0", + "platforms": "espressif8266" + }, + { + "name": "AsyncTCP", + "version": "^1.0.0", + "platforms": "espressif32" + } + ] +} diff --git a/libraries/async-mqtt-client/library.properties b/libraries/async-mqtt-client/library.properties new file mode 100644 index 0000000..e2e6037 --- /dev/null +++ b/libraries/async-mqtt-client/library.properties @@ -0,0 +1,9 @@ +name=AsyncMqttClient +version=0.8.2 +author=Marvin ROGER +maintainer=Marvin ROGER +sentence=An Arduino for ESP8266 and ESP32 asynchronous MQTT client implementation +paragraph=Like this project? Please star it on GitHub! +category=Communication +url=https://github.com/marvinroger/async-mqtt-client +architectures=esp8266,esp32 diff --git a/libraries/async-mqtt-client/scripts/get-fingerprint/get-fingerprint.py b/libraries/async-mqtt-client/scripts/get-fingerprint/get-fingerprint.py new file mode 100644 index 0000000..ec78d1b --- /dev/null +++ b/libraries/async-mqtt-client/scripts/get-fingerprint/get-fingerprint.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +import argparse +import ssl +import hashlib + +parser = argparse.ArgumentParser(description='Compute SSL/TLS fingerprints.') +parser.add_argument('--host', required=True) +parser.add_argument('--port', default=8883) + +args = parser.parse_args() +print(args.host) + +cert_pem = ssl.get_server_certificate((args.host, args.port)) +cert_der = ssl.PEM_cert_to_DER_cert(cert_pem) + +md5 = hashlib.md5(cert_der).hexdigest() +sha1 = hashlib.sha1(cert_der).hexdigest() +sha256 = hashlib.sha256(cert_der).hexdigest() +print("MD5: " + md5) +print("SHA1: " + sha1) +print("SHA256: " + sha256) + +print("\nSHA1 as array initializer:") +print("const uint8_t fingerprint[] = {0x" + ", 0x".join([sha1[i:i+2] for i in range(0, len(sha1), 2)]) + "};") + +print("\nSHA1 as function call:") +print("mqttClient.addServerFingerprint((const uint8_t[]){0x" + ", 0x".join([sha1[i:i+2] for i in range(0, len(sha1), 2)]) + "});") diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient.cpp b/libraries/async-mqtt-client/src/AsyncMqttClient.cpp new file mode 100644 index 0000000..f62e1ef --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient.cpp @@ -0,0 +1,877 @@ +#include "AsyncMqttClient.hpp" + +AsyncMqttClient::AsyncMqttClient() +: _connected(false) +, _connectPacketNotEnoughSpace(false) +, _disconnectFlagged(false) +, _tlsBadFingerprint(false) +, _lastClientActivity(0) +, _lastServerActivity(0) +, _lastPingRequestTime(0) +, _host(nullptr) +, _useIp(false) +#if ASYNC_TCP_SSL_ENABLED +, _secure(false) +#endif +, _port(0) +, _keepAlive(15) +, _cleanSession(true) +, _clientId(nullptr) +, _username(nullptr) +, _password(nullptr) +, _willTopic(nullptr) +, _willPayload(nullptr) +, _willPayloadLength(0) +, _willQos(0) +, _willRetain(false) +, _parsingInformation { .bufferState = AsyncMqttClientInternals::BufferState::NONE } +, _currentParsedPacket(nullptr) +, _remainingLengthBufferPosition(0) +, _nextPacketId(1) { + _client.onConnect([](void* obj, AsyncClient* c) { (static_cast(obj))->_onConnect(c); }, this); + _client.onDisconnect([](void* obj, AsyncClient* c) { (static_cast(obj))->_onDisconnect(c); }, this); + _client.onError([](void* obj, AsyncClient* c, int8_t error) { (static_cast(obj))->_onError(c, error); }, this); + _client.onTimeout([](void* obj, AsyncClient* c, uint32_t time) { (static_cast(obj))->_onTimeout(c, time); }, this); + _client.onAck([](void* obj, AsyncClient* c, size_t len, uint32_t time) { (static_cast(obj))->_onAck(c, len, time); }, this); + _client.onData([](void* obj, AsyncClient* c, void* data, size_t len) { (static_cast(obj))->_onData(c, static_cast(data), len); }, this); + _client.onPoll([](void* obj, AsyncClient* c) { (static_cast(obj))->_onPoll(c); }, this); + +#ifdef ESP32 + sprintf(_generatedClientId, "esp32%06x", ESP.getEfuseMac()); + _xSemaphore = xSemaphoreCreateMutex(); +#elif defined(ESP8266) + sprintf(_generatedClientId, "esp8266%06x", ESP.getChipId()); +#endif + _clientId = _generatedClientId; + + setMaxTopicLength(128); +} + +AsyncMqttClient::~AsyncMqttClient() { + delete _currentParsedPacket; + delete[] _parsingInformation.topicBuffer; +#ifdef ESP32 + vSemaphoreDelete(_xSemaphore); +#endif +} + +AsyncMqttClient& AsyncMqttClient::setKeepAlive(uint16_t keepAlive) { + _keepAlive = keepAlive; + return *this; +} + +AsyncMqttClient& AsyncMqttClient::setClientId(const char* clientId) { + _clientId = clientId; + return *this; +} + +AsyncMqttClient& AsyncMqttClient::setCleanSession(bool cleanSession) { + _cleanSession = cleanSession; + return *this; +} + +AsyncMqttClient& AsyncMqttClient::setMaxTopicLength(uint16_t maxTopicLength) { + _parsingInformation.maxTopicLength = maxTopicLength; + delete[] _parsingInformation.topicBuffer; + _parsingInformation.topicBuffer = new char[maxTopicLength + 1]; + return *this; +} + +AsyncMqttClient& AsyncMqttClient::setCredentials(const char* username, const char* password) { + _username = username; + _password = password; + return *this; +} + +AsyncMqttClient& AsyncMqttClient::setWill(const char* topic, uint8_t qos, bool retain, const char* payload, size_t length) { + _willTopic = topic; + _willQos = qos; + _willRetain = retain; + _willPayload = payload; + _willPayloadLength = length; + return *this; +} + +AsyncMqttClient& AsyncMqttClient::setServer(IPAddress ip, uint16_t port) { + _useIp = true; + _ip = ip; + _port = port; + return *this; +} + +AsyncMqttClient& AsyncMqttClient::setServer(const char* host, uint16_t port) { + _useIp = false; + _host = host; + _port = port; + return *this; +} + +#if ASYNC_TCP_SSL_ENABLED +AsyncMqttClient& AsyncMqttClient::setSecure(bool secure) { + _secure = secure; + return *this; +} + +AsyncMqttClient& AsyncMqttClient::addServerFingerprint(const uint8_t* fingerprint) { + std::array newFingerprint; + memcpy(newFingerprint.data(), fingerprint, SHA1_SIZE); + _secureServerFingerprints.push_back(newFingerprint); + return *this; +} +#endif + +AsyncMqttClient& AsyncMqttClient::onConnect(AsyncMqttClientInternals::OnConnectUserCallback callback) { + _onConnectUserCallbacks.push_back(callback); + return *this; +} + +AsyncMqttClient& AsyncMqttClient::onDisconnect(AsyncMqttClientInternals::OnDisconnectUserCallback callback) { + _onDisconnectUserCallbacks.push_back(callback); + return *this; +} + +AsyncMqttClient& AsyncMqttClient::onSubscribe(AsyncMqttClientInternals::OnSubscribeUserCallback callback) { + _onSubscribeUserCallbacks.push_back(callback); + return *this; +} + +AsyncMqttClient& AsyncMqttClient::onUnsubscribe(AsyncMqttClientInternals::OnUnsubscribeUserCallback callback) { + _onUnsubscribeUserCallbacks.push_back(callback); + return *this; +} + +AsyncMqttClient& AsyncMqttClient::onMessage(AsyncMqttClientInternals::OnMessageUserCallback callback) { + _onMessageUserCallbacks.push_back(callback); + return *this; +} + +AsyncMqttClient& AsyncMqttClient::onPublish(AsyncMqttClientInternals::OnPublishUserCallback callback) { + _onPublishUserCallbacks.push_back(callback); + return *this; +} + +void AsyncMqttClient::_freeCurrentParsedPacket() { + delete _currentParsedPacket; + _currentParsedPacket = nullptr; +} + +void AsyncMqttClient::_clear() { + _lastPingRequestTime = 0; + _connected = false; + _disconnectFlagged = false; + _connectPacketNotEnoughSpace = false; + _tlsBadFingerprint = false; + _freeCurrentParsedPacket(); + + _pendingPubRels.clear(); + _pendingPubRels.shrink_to_fit(); + + _toSendAcks.clear(); + _toSendAcks.shrink_to_fit(); + + _nextPacketId = 1; + _parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::NONE; +} + +/* TCP */ +void AsyncMqttClient::_onConnect(AsyncClient* client) { + (void)client; + +#if ASYNC_TCP_SSL_ENABLED + if (_secure && _secureServerFingerprints.size() > 0) { + SSL* clientSsl = _client.getSSL(); + + bool sslFoundFingerprint = false; + for (std::array fingerprint : _secureServerFingerprints) { + if (ssl_match_fingerprint(clientSsl, fingerprint.data()) == SSL_OK) { + sslFoundFingerprint = true; + break; + } + } + + if (!sslFoundFingerprint) { + _tlsBadFingerprint = true; + _client.close(true); + return; + } + } +#endif + + char fixedHeader[5]; + fixedHeader[0] = AsyncMqttClientInternals::PacketType.CONNECT; + fixedHeader[0] = fixedHeader[0] << 4; + fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.CONNECT_RESERVED; + + uint16_t protocolNameLength = 4; + char protocolNameLengthBytes[2]; + protocolNameLengthBytes[0] = protocolNameLength >> 8; + protocolNameLengthBytes[1] = protocolNameLength & 0xFF; + + char protocolLevel[1]; + protocolLevel[0] = 0x04; + + char connectFlags[1]; + connectFlags[0] = 0; + if (_cleanSession) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.CLEAN_SESSION; + if (_username != nullptr) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.USERNAME; + if (_password != nullptr) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.PASSWORD; + if (_willTopic != nullptr) { + connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL; + if (_willRetain) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_RETAIN; + switch (_willQos) { + case 0: + connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_QOS0; + break; + case 1: + connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_QOS1; + break; + case 2: + connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_QOS2; + break; + } + } + + char keepAliveBytes[2]; + keepAliveBytes[0] = _keepAlive >> 8; + keepAliveBytes[1] = _keepAlive & 0xFF; + + uint16_t clientIdLength = strlen(_clientId); + char clientIdLengthBytes[2]; + clientIdLengthBytes[0] = clientIdLength >> 8; + clientIdLengthBytes[1] = clientIdLength & 0xFF; + + // Optional fields + uint16_t willTopicLength = 0; + char willTopicLengthBytes[2]; + uint16_t willPayloadLength = _willPayloadLength; + char willPayloadLengthBytes[2]; + if (_willTopic != nullptr) { + willTopicLength = strlen(_willTopic); + willTopicLengthBytes[0] = willTopicLength >> 8; + willTopicLengthBytes[1] = willTopicLength & 0xFF; + + if (_willPayload != nullptr && willPayloadLength == 0) willPayloadLength = strlen(_willPayload); + + willPayloadLengthBytes[0] = willPayloadLength >> 8; + willPayloadLengthBytes[1] = willPayloadLength & 0xFF; + } + + uint16_t usernameLength = 0; + char usernameLengthBytes[2]; + if (_username != nullptr) { + usernameLength = strlen(_username); + usernameLengthBytes[0] = usernameLength >> 8; + usernameLengthBytes[1] = usernameLength & 0xFF; + } + + uint16_t passwordLength = 0; + char passwordLengthBytes[2]; + if (_password != nullptr) { + passwordLength = strlen(_password); + passwordLengthBytes[0] = passwordLength >> 8; + passwordLengthBytes[1] = passwordLength & 0xFF; + } + + uint32_t remainingLength = 2 + protocolNameLength + 1 + 1 + 2 + 2 + clientIdLength; // always present + if (_willTopic != nullptr) remainingLength += 2 + willTopicLength + 2 + willPayloadLength; + if (_username != nullptr) remainingLength += 2 + usernameLength; + if (_password != nullptr) remainingLength += 2 + passwordLength; + uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(remainingLength, fixedHeader + 1); + + uint32_t neededSpace = 1 + remainingLengthLength; + neededSpace += 2; + neededSpace += protocolNameLength; + neededSpace += 1; + neededSpace += 1; + neededSpace += 2; + neededSpace += 2; + neededSpace += clientIdLength; + if (_willTopic != nullptr) { + neededSpace += 2; + neededSpace += willTopicLength; + + neededSpace += 2; + if (_willPayload != nullptr) neededSpace += willPayloadLength; + } + if (_username != nullptr) { + neededSpace += 2; + neededSpace += usernameLength; + } + if (_password != nullptr) { + neededSpace += 2; + neededSpace += passwordLength; + } + + SEMAPHORE_TAKE(); + if (_client.space() < neededSpace) { + _connectPacketNotEnoughSpace = true; + _client.close(true); + SEMAPHORE_GIVE(); + return; + } + + _client.add(fixedHeader, 1 + remainingLengthLength); + _client.add(protocolNameLengthBytes, 2); + _client.add("MQTT", protocolNameLength); + _client.add(protocolLevel, 1); + _client.add(connectFlags, 1); + _client.add(keepAliveBytes, 2); + _client.add(clientIdLengthBytes, 2); + _client.add(_clientId, clientIdLength); + if (_willTopic != nullptr) { + _client.add(willTopicLengthBytes, 2); + _client.add(_willTopic, willTopicLength); + + _client.add(willPayloadLengthBytes, 2); + if (_willPayload != nullptr) _client.add(_willPayload, willPayloadLength); + } + if (_username != nullptr) { + _client.add(usernameLengthBytes, 2); + _client.add(_username, usernameLength); + } + if (_password != nullptr) { + _client.add(passwordLengthBytes, 2); + _client.add(_password, passwordLength); + } + _client.send(); + _lastClientActivity = millis(); + SEMAPHORE_GIVE(); +} + +void AsyncMqttClient::_onDisconnect(AsyncClient* client) { + (void)client; + if (!_disconnectFlagged) { + AsyncMqttClientDisconnectReason reason; + + if (_connectPacketNotEnoughSpace) { + reason = AsyncMqttClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE; + } else if (_tlsBadFingerprint) { + reason = AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT; + } else { + reason = AsyncMqttClientDisconnectReason::TCP_DISCONNECTED; + } + for (auto callback : _onDisconnectUserCallbacks) callback(reason); + } + _clear(); +} + +void AsyncMqttClient::_onError(AsyncClient* client, int8_t error) { + (void)client; + (void)error; + // _onDisconnect called anyway +} + +void AsyncMqttClient::_onTimeout(AsyncClient* client, uint32_t time) { + (void)client; + (void)time; + // disconnection will be handled by ping/pong management +} + +void AsyncMqttClient::_onAck(AsyncClient* client, size_t len, uint32_t time) { + (void)client; + (void)len; + (void)time; +} + +void AsyncMqttClient::_onData(AsyncClient* client, char* data, size_t len) { + (void)client; + size_t currentBytePosition = 0; + char currentByte; + do { + switch (_parsingInformation.bufferState) { + case AsyncMqttClientInternals::BufferState::NONE: + currentByte = data[currentBytePosition++]; + _parsingInformation.packetType = currentByte >> 4; + _parsingInformation.packetFlags = (currentByte << 4) >> 4; + _parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::REMAINING_LENGTH; + _lastServerActivity = millis(); + switch (_parsingInformation.packetType) { + case AsyncMqttClientInternals::PacketType.CONNACK: + _currentParsedPacket = new AsyncMqttClientInternals::ConnAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onConnAck, this, std::placeholders::_1, std::placeholders::_2)); + break; + case AsyncMqttClientInternals::PacketType.PINGRESP: + _currentParsedPacket = new AsyncMqttClientInternals::PingRespPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPingResp, this)); + break; + case AsyncMqttClientInternals::PacketType.SUBACK: + _currentParsedPacket = new AsyncMqttClientInternals::SubAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onSubAck, this, std::placeholders::_1, std::placeholders::_2)); + break; + case AsyncMqttClientInternals::PacketType.UNSUBACK: + _currentParsedPacket = new AsyncMqttClientInternals::UnsubAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onUnsubAck, this, std::placeholders::_1)); + break; + case AsyncMqttClientInternals::PacketType.PUBLISH: + _currentParsedPacket = new AsyncMqttClientInternals::PublishPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6, std::placeholders::_7, std::placeholders::_8, std::placeholders::_9), std::bind(&AsyncMqttClient::_onPublish, this, std::placeholders::_1, std::placeholders::_2)); + break; + case AsyncMqttClientInternals::PacketType.PUBREL: + _currentParsedPacket = new AsyncMqttClientInternals::PubRelPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubRel, this, std::placeholders::_1)); + break; + case AsyncMqttClientInternals::PacketType.PUBACK: + _currentParsedPacket = new AsyncMqttClientInternals::PubAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubAck, this, std::placeholders::_1)); + break; + case AsyncMqttClientInternals::PacketType.PUBREC: + _currentParsedPacket = new AsyncMqttClientInternals::PubRecPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubRec, this, std::placeholders::_1)); + break; + case AsyncMqttClientInternals::PacketType.PUBCOMP: + _currentParsedPacket = new AsyncMqttClientInternals::PubCompPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubComp, this, std::placeholders::_1)); + break; + default: + break; + } + break; + case AsyncMqttClientInternals::BufferState::REMAINING_LENGTH: + currentByte = data[currentBytePosition++]; + _remainingLengthBuffer[_remainingLengthBufferPosition++] = currentByte; + if (currentByte >> 7 == 0) { + _parsingInformation.remainingLength = AsyncMqttClientInternals::Helpers::decodeRemainingLength(_remainingLengthBuffer); + _remainingLengthBufferPosition = 0; + if (_parsingInformation.remainingLength > 0) { + _parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::VARIABLE_HEADER; + } else { + // PINGRESP is a special case where it has no variable header, so the packet ends right here + _parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::NONE; + _onPingResp(); + } + } + break; + case AsyncMqttClientInternals::BufferState::VARIABLE_HEADER: + _currentParsedPacket->parseVariableHeader(data, len, ¤tBytePosition); + break; + case AsyncMqttClientInternals::BufferState::PAYLOAD: + _currentParsedPacket->parsePayload(data, len, ¤tBytePosition); + break; + default: + currentBytePosition = len; + } + } while (currentBytePosition != len); +} + +void AsyncMqttClient::_onPoll(AsyncClient* client) { + if (!_connected) return; + + // if there is too much time the client has sent a ping request without a response, disconnect client to avoid half open connections + if (_lastPingRequestTime != 0 && (millis() - _lastPingRequestTime) >= (_keepAlive * 1000 * 2)) { + disconnect(); + return; + // send ping to ensure the server will receive at least one message inside keepalive window + } else if (_lastPingRequestTime == 0 && (millis() - _lastClientActivity) >= (_keepAlive * 1000 * 0.7)) { + _sendPing(); + + // send ping to verify if the server is still there (ensure this is not a half connection) + } else if (_connected && _lastPingRequestTime == 0 && (millis() - _lastServerActivity) >= (_keepAlive * 1000 * 0.7)) { + _sendPing(); + } + + // handle to send ack packets + + _sendAcks(); + + // handle disconnect + + if (_disconnectFlagged) { + _sendDisconnect(); + } +} + +/* MQTT */ +void AsyncMqttClient::_onPingResp() { + _freeCurrentParsedPacket(); + _lastPingRequestTime = 0; +} + +void AsyncMqttClient::_onConnAck(bool sessionPresent, uint8_t connectReturnCode) { + (void)sessionPresent; + _freeCurrentParsedPacket(); + + if (connectReturnCode == 0) { + _connected = true; + for (auto callback : _onConnectUserCallbacks) callback(sessionPresent); + } else { + for (auto callback : _onDisconnectUserCallbacks) callback(static_cast(connectReturnCode)); + _disconnectFlagged = true; + } +} + +void AsyncMqttClient::_onSubAck(uint16_t packetId, char status) { + _freeCurrentParsedPacket(); + + for (auto callback : _onSubscribeUserCallbacks) callback(packetId, status); +} + +void AsyncMqttClient::_onUnsubAck(uint16_t packetId) { + _freeCurrentParsedPacket(); + + for (auto callback : _onUnsubscribeUserCallbacks) callback(packetId); +} + +void AsyncMqttClient::_onMessage(char* topic, char* payload, uint8_t qos, bool dup, bool retain, size_t len, size_t index, size_t total, uint16_t packetId) { + bool notifyPublish = true; + + if (qos == 2) { + for (AsyncMqttClientInternals::PendingPubRel pendingPubRel : _pendingPubRels) { + if (pendingPubRel.packetId == packetId) { + notifyPublish = false; + break; + } + } + } + + if (notifyPublish) { + AsyncMqttClientMessageProperties properties; + properties.qos = qos; + properties.dup = dup; + properties.retain = retain; + + for (auto callback : _onMessageUserCallbacks) callback(topic, payload, properties, len, index, total); + } +} + +void AsyncMqttClient::_onPublish(uint16_t packetId, uint8_t qos) { + AsyncMqttClientInternals::PendingAck pendingAck; + + if (qos == 1) { + pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBACK; + pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBACK_RESERVED; + pendingAck.packetId = packetId; + _toSendAcks.push_back(pendingAck); + } else if (qos == 2) { + pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBREC; + pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBREC_RESERVED; + pendingAck.packetId = packetId; + _toSendAcks.push_back(pendingAck); + + bool pubRelAwaiting = false; + for (AsyncMqttClientInternals::PendingPubRel pendingPubRel : _pendingPubRels) { + if (pendingPubRel.packetId == packetId) { + pubRelAwaiting = true; + break; + } + } + + if (!pubRelAwaiting) { + AsyncMqttClientInternals::PendingPubRel pendingPubRel; + pendingPubRel.packetId = packetId; + _pendingPubRels.push_back(pendingPubRel); + } + + _sendAcks(); + } + + _freeCurrentParsedPacket(); +} + +void AsyncMqttClient::_onPubRel(uint16_t packetId) { + _freeCurrentParsedPacket(); + + AsyncMqttClientInternals::PendingAck pendingAck; + pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBCOMP; + pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBCOMP_RESERVED; + pendingAck.packetId = packetId; + _toSendAcks.push_back(pendingAck); + + for (size_t i = 0; i < _pendingPubRels.size(); i++) { + if (_pendingPubRels[i].packetId == packetId) { + _pendingPubRels.erase(_pendingPubRels.begin() + i); + _pendingPubRels.shrink_to_fit(); + } + } + + _sendAcks(); +} + +void AsyncMqttClient::_onPubAck(uint16_t packetId) { + _freeCurrentParsedPacket(); + + for (auto callback : _onPublishUserCallbacks) callback(packetId); +} + +void AsyncMqttClient::_onPubRec(uint16_t packetId) { + _freeCurrentParsedPacket(); + + AsyncMqttClientInternals::PendingAck pendingAck; + pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBREL; + pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBREL_RESERVED; + pendingAck.packetId = packetId; + _toSendAcks.push_back(pendingAck); + + _sendAcks(); +} + +void AsyncMqttClient::_onPubComp(uint16_t packetId) { + _freeCurrentParsedPacket(); + + for (auto callback : _onPublishUserCallbacks) callback(packetId); +} + +bool AsyncMqttClient::_sendPing() { + char fixedHeader[2]; + fixedHeader[0] = AsyncMqttClientInternals::PacketType.PINGREQ; + fixedHeader[0] = fixedHeader[0] << 4; + fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.PINGREQ_RESERVED; + fixedHeader[1] = 0; + + size_t neededSpace = 2; + + SEMAPHORE_TAKE(false); + if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return false; } + + _client.add(fixedHeader, 2); + _client.send(); + _lastClientActivity = millis(); + _lastPingRequestTime = millis(); + + SEMAPHORE_GIVE(); + return true; +} + +void AsyncMqttClient::_sendAcks() { + uint8_t neededAckSpace = 2 + 2; + + SEMAPHORE_TAKE(); + for (size_t i = 0; i < _toSendAcks.size(); i++) { + if (_client.space() < neededAckSpace) break; + + AsyncMqttClientInternals::PendingAck pendingAck = _toSendAcks[i]; + + char fixedHeader[2]; + fixedHeader[0] = pendingAck.packetType; + fixedHeader[0] = fixedHeader[0] << 4; + fixedHeader[0] = fixedHeader[0] | pendingAck.headerFlag; + fixedHeader[1] = 2; + + char packetIdBytes[2]; + packetIdBytes[0] = pendingAck.packetId >> 8; + packetIdBytes[1] = pendingAck.packetId & 0xFF; + + _client.add(fixedHeader, 2); + _client.add(packetIdBytes, 2); + _client.send(); + + _toSendAcks.erase(_toSendAcks.begin() + i); + _toSendAcks.shrink_to_fit(); + + _lastClientActivity = millis(); + } + SEMAPHORE_GIVE(); +} + +bool AsyncMqttClient::_sendDisconnect() { + if (!_connected) return true; + + const uint8_t neededSpace = 2; + + SEMAPHORE_TAKE(false); + + if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return false; } + + char fixedHeader[2]; + fixedHeader[0] = AsyncMqttClientInternals::PacketType.DISCONNECT; + fixedHeader[0] = fixedHeader[0] << 4; + fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.DISCONNECT_RESERVED; + fixedHeader[1] = 0; + + _client.add(fixedHeader, 2); + _client.send(); + _client.close(true); + + _disconnectFlagged = false; + + SEMAPHORE_GIVE(); + return true; +} + +uint16_t AsyncMqttClient::_getNextPacketId() { + uint16_t nextPacketId = _nextPacketId; + + if (_nextPacketId == 65535) _nextPacketId = 0; // 0 is forbidden + _nextPacketId++; + + return nextPacketId; +} + +bool AsyncMqttClient::connected() const { + return _connected; +} + +void AsyncMqttClient::connect() { + if (_connected) return; + +#if ASYNC_TCP_SSL_ENABLED + if (_useIp) { + _client.connect(_ip, _port, _secure); + } else { + _client.connect(_host, _port, _secure); + } +#else + if (_useIp) { + _client.connect(_ip, _port); + } else { + _client.connect(_host, _port); + } +#endif +} + +void AsyncMqttClient::disconnect(bool force) { + if (!_connected) return; + + if (force) { + _client.close(true); + } else { + _disconnectFlagged = true; + _sendDisconnect(); + } +} + +uint16_t AsyncMqttClient::subscribe(const char* topic, uint8_t qos) { + if (!_connected) return 0; + + char fixedHeader[5]; + fixedHeader[0] = AsyncMqttClientInternals::PacketType.SUBSCRIBE; + fixedHeader[0] = fixedHeader[0] << 4; + fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.SUBSCRIBE_RESERVED; + + uint16_t topicLength = strlen(topic); + char topicLengthBytes[2]; + topicLengthBytes[0] = topicLength >> 8; + topicLengthBytes[1] = topicLength & 0xFF; + + char qosByte[1]; + qosByte[0] = qos; + + uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(2 + 2 + topicLength + 1, fixedHeader + 1); + + size_t neededSpace = 0; + neededSpace += 1 + remainingLengthLength; + neededSpace += 2; + neededSpace += 2; + neededSpace += topicLength; + neededSpace += 1; + + SEMAPHORE_TAKE(0); + if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return 0; } + + uint16_t packetId = _getNextPacketId(); + char packetIdBytes[2]; + packetIdBytes[0] = packetId >> 8; + packetIdBytes[1] = packetId & 0xFF; + + _client.add(fixedHeader, 1 + remainingLengthLength); + _client.add(packetIdBytes, 2); + _client.add(topicLengthBytes, 2); + _client.add(topic, topicLength); + _client.add(qosByte, 1); + _client.send(); + _lastClientActivity = millis(); + + SEMAPHORE_GIVE(); + return packetId; +} + +uint16_t AsyncMqttClient::unsubscribe(const char* topic) { + if (!_connected) return 0; + + char fixedHeader[5]; + fixedHeader[0] = AsyncMqttClientInternals::PacketType.UNSUBSCRIBE; + fixedHeader[0] = fixedHeader[0] << 4; + fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.UNSUBSCRIBE_RESERVED; + + uint16_t topicLength = strlen(topic); + char topicLengthBytes[2]; + topicLengthBytes[0] = topicLength >> 8; + topicLengthBytes[1] = topicLength & 0xFF; + + uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(2 + 2 + topicLength, fixedHeader + 1); + + size_t neededSpace = 0; + neededSpace += 1 + remainingLengthLength; + neededSpace += 2; + neededSpace += 2; + neededSpace += topicLength; + + SEMAPHORE_TAKE(0); + if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return 0; } + + uint16_t packetId = _getNextPacketId(); + char packetIdBytes[2]; + packetIdBytes[0] = packetId >> 8; + packetIdBytes[1] = packetId & 0xFF; + + _client.add(fixedHeader, 1 + remainingLengthLength); + _client.add(packetIdBytes, 2); + _client.add(topicLengthBytes, 2); + _client.add(topic, topicLength); + _client.send(); + _lastClientActivity = millis(); + + SEMAPHORE_GIVE(); + return packetId; +} + +uint16_t AsyncMqttClient::publish(const char* topic, uint8_t qos, bool retain, const char* payload, size_t length, bool dup, uint16_t message_id) { + if (!_connected) return 0; + + char fixedHeader[5]; + fixedHeader[0] = AsyncMqttClientInternals::PacketType.PUBLISH; + fixedHeader[0] = fixedHeader[0] << 4; + if (dup) fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_DUP; + if (retain) fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_RETAIN; + switch (qos) { + case 0: + fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_QOS0; + break; + case 1: + fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_QOS1; + break; + case 2: + fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_QOS2; + break; + } + + uint16_t topicLength = strlen(topic); + char topicLengthBytes[2]; + topicLengthBytes[0] = topicLength >> 8; + topicLengthBytes[1] = topicLength & 0xFF; + + uint32_t payloadLength = length; + if (payload != nullptr && payloadLength == 0) payloadLength = strlen(payload); + + uint32_t remainingLength = 2 + topicLength + payloadLength; + if (qos != 0) remainingLength += 2; + uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(remainingLength, fixedHeader + 1); + + size_t neededSpace = 0; + neededSpace += 1 + remainingLengthLength; + neededSpace += 2; + neededSpace += topicLength; + if (qos != 0) neededSpace += 2; + if (payload != nullptr) neededSpace += payloadLength; + + SEMAPHORE_TAKE(0); + if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return 0; } + + uint16_t packetId = 0; + char packetIdBytes[2]; + if (qos != 0) { + if (dup && message_id > 0) { + packetId = message_id; + } else { + packetId = _getNextPacketId(); + } + + packetIdBytes[0] = packetId >> 8; + packetIdBytes[1] = packetId & 0xFF; + } + + _client.add(fixedHeader, 1 + remainingLengthLength); + _client.add(topicLengthBytes, 2); + _client.add(topic, topicLength); + if (qos != 0) _client.add(packetIdBytes, 2); + if (payload != nullptr) _client.add(payload, payloadLength); + _client.send(); + _lastClientActivity = millis(); + + SEMAPHORE_GIVE(); + if (qos != 0) { + return packetId; + } else { + return 1; + } +} diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient.h b/libraries/async-mqtt-client/src/AsyncMqttClient.h new file mode 100644 index 0000000..23d3055 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient.h @@ -0,0 +1,6 @@ +#ifndef SRC_ASYNCMQTTCLIENT_H_ +#define SRC_ASYNCMQTTCLIENT_H_ + +#include "AsyncMqttClient.hpp" + +#endif // SRC_ASYNCMQTTCLIENT_H_ diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient.hpp b/libraries/async-mqtt-client/src/AsyncMqttClient.hpp new file mode 100644 index 0000000..af8332b --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient.hpp @@ -0,0 +1,166 @@ +#pragma once + +#include +#include + +#include "Arduino.h" + +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#else +#error Platform not supported +#endif + +#if ASYNC_TCP_SSL_ENABLED +#include +#define SHA1_SIZE 20 +#endif + +#include "AsyncMqttClient/Flags.hpp" +#include "AsyncMqttClient/ParsingInformation.hpp" +#include "AsyncMqttClient/MessageProperties.hpp" +#include "AsyncMqttClient/Helpers.hpp" +#include "AsyncMqttClient/Callbacks.hpp" +#include "AsyncMqttClient/DisconnectReasons.hpp" +#include "AsyncMqttClient/Storage.hpp" + +#include "AsyncMqttClient/Packets/Packet.hpp" +#include "AsyncMqttClient/Packets/ConnAckPacket.hpp" +#include "AsyncMqttClient/Packets/PingRespPacket.hpp" +#include "AsyncMqttClient/Packets/SubAckPacket.hpp" +#include "AsyncMqttClient/Packets/UnsubAckPacket.hpp" +#include "AsyncMqttClient/Packets/PublishPacket.hpp" +#include "AsyncMqttClient/Packets/PubRelPacket.hpp" +#include "AsyncMqttClient/Packets/PubAckPacket.hpp" +#include "AsyncMqttClient/Packets/PubRecPacket.hpp" +#include "AsyncMqttClient/Packets/PubCompPacket.hpp" + +#if ESP32 +#define SEMAPHORE_TAKE(X) if (xSemaphoreTake(_xSemaphore, 1000 / portTICK_PERIOD_MS) != pdTRUE) { return X; } // Waits max 1000ms +#define SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore); +#elif defined(ESP8266) +#define SEMAPHORE_TAKE(X) void() +#define SEMAPHORE_GIVE() void() +#endif + +class AsyncMqttClient { + public: + AsyncMqttClient(); + ~AsyncMqttClient(); + + AsyncMqttClient& setKeepAlive(uint16_t keepAlive); + AsyncMqttClient& setClientId(const char* clientId); + AsyncMqttClient& setCleanSession(bool cleanSession); + AsyncMqttClient& setMaxTopicLength(uint16_t maxTopicLength); + AsyncMqttClient& setCredentials(const char* username, const char* password = nullptr); + AsyncMqttClient& setWill(const char* topic, uint8_t qos, bool retain, const char* payload = nullptr, size_t length = 0); + AsyncMqttClient& setServer(IPAddress ip, uint16_t port); + AsyncMqttClient& setServer(const char* host, uint16_t port); +#if ASYNC_TCP_SSL_ENABLED + AsyncMqttClient& setSecure(bool secure); + AsyncMqttClient& addServerFingerprint(const uint8_t* fingerprint); +#endif + + AsyncMqttClient& onConnect(AsyncMqttClientInternals::OnConnectUserCallback callback); + AsyncMqttClient& onDisconnect(AsyncMqttClientInternals::OnDisconnectUserCallback callback); + AsyncMqttClient& onSubscribe(AsyncMqttClientInternals::OnSubscribeUserCallback callback); + AsyncMqttClient& onUnsubscribe(AsyncMqttClientInternals::OnUnsubscribeUserCallback callback); + AsyncMqttClient& onMessage(AsyncMqttClientInternals::OnMessageUserCallback callback); + AsyncMqttClient& onPublish(AsyncMqttClientInternals::OnPublishUserCallback callback); + + bool connected() const; + void connect(); + void disconnect(bool force = false); + uint16_t subscribe(const char* topic, uint8_t qos); + uint16_t unsubscribe(const char* topic); + uint16_t publish(const char* topic, uint8_t qos, bool retain, const char* payload = nullptr, size_t length = 0, bool dup = false, uint16_t message_id = 0); + + private: + AsyncClient _client; + + bool _connected; + bool _connectPacketNotEnoughSpace; + bool _disconnectFlagged; + bool _tlsBadFingerprint; + uint32_t _lastClientActivity; + uint32_t _lastServerActivity; + uint32_t _lastPingRequestTime; + + char _generatedClientId[13 + 1]; // esp8266abc123 + IPAddress _ip; + const char* _host; + bool _useIp; +#if ASYNC_TCP_SSL_ENABLED + bool _secure; +#endif + uint16_t _port; + uint16_t _keepAlive; + bool _cleanSession; + const char* _clientId; + const char* _username; + const char* _password; + const char* _willTopic; + const char* _willPayload; + uint16_t _willPayloadLength; + uint8_t _willQos; + bool _willRetain; + +#if ASYNC_TCP_SSL_ENABLED + std::vector> _secureServerFingerprints; +#endif + + std::vector _onConnectUserCallbacks; + std::vector _onDisconnectUserCallbacks; + std::vector _onSubscribeUserCallbacks; + std::vector _onUnsubscribeUserCallbacks; + std::vector _onMessageUserCallbacks; + std::vector _onPublishUserCallbacks; + + AsyncMqttClientInternals::ParsingInformation _parsingInformation; + AsyncMqttClientInternals::Packet* _currentParsedPacket; + uint8_t _remainingLengthBufferPosition; + char _remainingLengthBuffer[4]; + + uint16_t _nextPacketId; + + std::vector _pendingPubRels; + + std::vector _toSendAcks; + +#ifdef ESP32 + SemaphoreHandle_t _xSemaphore = nullptr; +#endif + + void _clear(); + void _freeCurrentParsedPacket(); + + // TCP + void _onConnect(AsyncClient* client); + void _onDisconnect(AsyncClient* client); + static void _onError(AsyncClient* client, int8_t error); + void _onTimeout(AsyncClient* client, uint32_t time); + static void _onAck(AsyncClient* client, size_t len, uint32_t time); + void _onData(AsyncClient* client, char* data, size_t len); + void _onPoll(AsyncClient* client); + + // MQTT + void _onPingResp(); + void _onConnAck(bool sessionPresent, uint8_t connectReturnCode); + void _onSubAck(uint16_t packetId, char status); + void _onUnsubAck(uint16_t packetId); + void _onMessage(char* topic, char* payload, uint8_t qos, bool dup, bool retain, size_t len, size_t index, size_t total, uint16_t packetId); + void _onPublish(uint16_t packetId, uint8_t qos); + void _onPubRel(uint16_t packetId); + void _onPubAck(uint16_t packetId); + void _onPubRec(uint16_t packetId); + void _onPubComp(uint16_t packetId); + + bool _sendPing(); + void _sendAcks(); + bool _sendDisconnect(); + + uint16_t _getNextPacketId(); +}; diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Callbacks.hpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Callbacks.hpp new file mode 100644 index 0000000..7c3d63d --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Callbacks.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "DisconnectReasons.hpp" +#include "MessageProperties.hpp" + +namespace AsyncMqttClientInternals { +// user callbacks +typedef std::function OnConnectUserCallback; +typedef std::function OnDisconnectUserCallback; +typedef std::function OnSubscribeUserCallback; +typedef std::function OnUnsubscribeUserCallback; +typedef std::function OnMessageUserCallback; +typedef std::function OnPublishUserCallback; + +// internal callbacks +typedef std::function OnConnAckInternalCallback; +typedef std::function OnPingRespInternalCallback; +typedef std::function OnSubAckInternalCallback; +typedef std::function OnUnsubAckInternalCallback; +typedef std::function OnMessageInternalCallback; +typedef std::function OnPublishInternalCallback; +typedef std::function OnPubRelInternalCallback; +typedef std::function OnPubAckInternalCallback; +typedef std::function OnPubRecInternalCallback; +typedef std::function OnPubCompInternalCallback; +} // namespace AsyncMqttClientInternals diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/DisconnectReasons.hpp b/libraries/async-mqtt-client/src/AsyncMqttClient/DisconnectReasons.hpp new file mode 100644 index 0000000..f4cbda8 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/DisconnectReasons.hpp @@ -0,0 +1,15 @@ +#pragma once + +enum class AsyncMqttClientDisconnectReason : int8_t { + TCP_DISCONNECTED = 0, + + MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 1, + MQTT_IDENTIFIER_REJECTED = 2, + MQTT_SERVER_UNAVAILABLE = 3, + MQTT_MALFORMED_CREDENTIALS = 4, + MQTT_NOT_AUTHORIZED = 5, + + ESP8266_NOT_ENOUGH_SPACE = 6, + + TLS_BAD_FINGERPRINT = 7 +}; diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Flags.hpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Flags.hpp new file mode 100644 index 0000000..a1fb3e3 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Flags.hpp @@ -0,0 +1,57 @@ +#pragma once + +namespace AsyncMqttClientInternals { +constexpr struct { + const uint8_t RESERVED = 0; + const uint8_t CONNECT = 1; + const uint8_t CONNACK = 2; + const uint8_t PUBLISH = 3; + const uint8_t PUBACK = 4; + const uint8_t PUBREC = 5; + const uint8_t PUBREL = 6; + const uint8_t PUBCOMP = 7; + const uint8_t SUBSCRIBE = 8; + const uint8_t SUBACK = 9; + const uint8_t UNSUBSCRIBE = 10; + const uint8_t UNSUBACK = 11; + const uint8_t PINGREQ = 12; + const uint8_t PINGRESP = 13; + const uint8_t DISCONNECT = 14; + const uint8_t RESERVED2 = 1; +} PacketType; + +constexpr struct { + const uint8_t CONNECT_RESERVED = 0x00; + const uint8_t CONNACK_RESERVED = 0x00; + const uint8_t PUBLISH_DUP = 0x08; + const uint8_t PUBLISH_QOS0 = 0x00; + const uint8_t PUBLISH_QOS1 = 0x02; + const uint8_t PUBLISH_QOS2 = 0x04; + const uint8_t PUBLISH_QOSRESERVED = 0x06; + const uint8_t PUBLISH_RETAIN = 0x01; + const uint8_t PUBACK_RESERVED = 0x00; + const uint8_t PUBREC_RESERVED = 0x00; + const uint8_t PUBREL_RESERVED = 0x02; + const uint8_t PUBCOMP_RESERVED = 0x00; + const uint8_t SUBSCRIBE_RESERVED = 0x02; + const uint8_t SUBACK_RESERVED = 0x00; + const uint8_t UNSUBSCRIBE_RESERVED = 0x02; + const uint8_t UNSUBACK_RESERVED = 0x00; + const uint8_t PINGREQ_RESERVED = 0x00; + const uint8_t PINGRESP_RESERVED = 0x00; + const uint8_t DISCONNECT_RESERVED = 0x00; + const uint8_t RESERVED2_RESERVED = 0x00; +} HeaderFlag; + +constexpr struct { + const uint8_t USERNAME = 0x80; + const uint8_t PASSWORD = 0x40; + const uint8_t WILL_RETAIN = 0x20; + const uint8_t WILL_QOS0 = 0x00; + const uint8_t WILL_QOS1 = 0x08; + const uint8_t WILL_QOS2 = 0x10; + const uint8_t WILL = 0x04; + const uint8_t CLEAN_SESSION = 0x02; + const uint8_t RESERVED = 0x00; +} ConnectFlag; +} // namespace AsyncMqttClientInternals diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Helpers.hpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Helpers.hpp new file mode 100644 index 0000000..5737c02 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Helpers.hpp @@ -0,0 +1,38 @@ +#pragma once + +namespace AsyncMqttClientInternals { +class Helpers { + public: + static uint32_t decodeRemainingLength(char* bytes) { + uint32_t multiplier = 1; + uint32_t value = 0; + uint8_t currentByte = 0; + uint8_t encodedByte; + do { + encodedByte = bytes[currentByte++]; + value += (encodedByte & 127) * multiplier; + multiplier *= 128; + } while ((encodedByte & 128) != 0); + + return value; + } + + static uint8_t encodeRemainingLength(uint32_t remainingLength, char* destination) { + uint8_t currentByte = 0; + uint8_t bytesNeeded = 0; + + do { + uint8_t encodedByte = remainingLength % 128; + remainingLength /= 128; + if (remainingLength > 0) { + encodedByte = encodedByte | 128; + } + + destination[currentByte++] = encodedByte; + bytesNeeded++; + } while (remainingLength > 0); + + return bytesNeeded; + } +}; +} // namespace AsyncMqttClientInternals diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/MessageProperties.hpp b/libraries/async-mqtt-client/src/AsyncMqttClient/MessageProperties.hpp new file mode 100644 index 0000000..c04b596 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/MessageProperties.hpp @@ -0,0 +1,7 @@ +#pragma once + +struct AsyncMqttClientMessageProperties { + uint8_t qos; + bool dup; + bool retain; +}; diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/ConnAckPacket.cpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/ConnAckPacket.cpp new file mode 100644 index 0000000..f9091c2 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/ConnAckPacket.cpp @@ -0,0 +1,30 @@ +#include "ConnAckPacket.hpp" + +using AsyncMqttClientInternals::ConnAckPacket; + +ConnAckPacket::ConnAckPacket(ParsingInformation* parsingInformation, OnConnAckInternalCallback callback) +: _parsingInformation(parsingInformation) +, _callback(callback) +, _bytePosition(0) +, _sessionPresent(false) +, _connectReturnCode(0) { +} + +ConnAckPacket::~ConnAckPacket() { +} + +void ConnAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { + char currentByte = data[(*currentBytePosition)++]; + if (_bytePosition++ == 0) { + _sessionPresent = (currentByte << 7) >> 7; + } else { + _connectReturnCode = currentByte; + _parsingInformation->bufferState = BufferState::NONE; + _callback(_sessionPresent, _connectReturnCode); + } +} + +void ConnAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { + (void)data; + (void)currentBytePosition; +} diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/ConnAckPacket.hpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/ConnAckPacket.hpp new file mode 100644 index 0000000..8be9ab1 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/ConnAckPacket.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "Arduino.h" +#include "Packet.hpp" +#include "../ParsingInformation.hpp" +#include "../Callbacks.hpp" + +namespace AsyncMqttClientInternals { +class ConnAckPacket : public Packet { + public: + explicit ConnAckPacket(ParsingInformation* parsingInformation, OnConnAckInternalCallback callback); + ~ConnAckPacket(); + + void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); + void parsePayload(char* data, size_t len, size_t* currentBytePosition); + + private: + ParsingInformation* _parsingInformation; + OnConnAckInternalCallback _callback; + + uint8_t _bytePosition; + bool _sessionPresent; + uint8_t _connectReturnCode; +}; +} // namespace AsyncMqttClientInternals diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/Packet.hpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/Packet.hpp new file mode 100644 index 0000000..9552cf0 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/Packet.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace AsyncMqttClientInternals { +class Packet { + public: + virtual ~Packet() {} + + virtual void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) = 0; + virtual void parsePayload(char* data, size_t len, size_t* currentBytePosition) = 0; +}; +} // namespace AsyncMqttClientInternals diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PingRespPacket.cpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PingRespPacket.cpp new file mode 100644 index 0000000..2a939aa --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PingRespPacket.cpp @@ -0,0 +1,21 @@ +#include "PingRespPacket.hpp" + +using AsyncMqttClientInternals::PingRespPacket; + +PingRespPacket::PingRespPacket(ParsingInformation* parsingInformation, OnPingRespInternalCallback callback) +: _parsingInformation(parsingInformation) +, _callback(callback) { +} + +PingRespPacket::~PingRespPacket() { +} + +void PingRespPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { + (void)data; + (void)currentBytePosition; +} + +void PingRespPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { + (void)data; + (void)currentBytePosition; +} diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PingRespPacket.hpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PingRespPacket.hpp new file mode 100644 index 0000000..043a730 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PingRespPacket.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "Arduino.h" +#include "Packet.hpp" +#include "../ParsingInformation.hpp" +#include "../Callbacks.hpp" + +namespace AsyncMqttClientInternals { +class PingRespPacket : public Packet { + public: + explicit PingRespPacket(ParsingInformation* parsingInformation, OnPingRespInternalCallback callback); + ~PingRespPacket(); + + void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); + void parsePayload(char* data, size_t len, size_t* currentBytePosition); + + private: + ParsingInformation* _parsingInformation; + OnPingRespInternalCallback _callback; +}; +} // namespace AsyncMqttClientInternals diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubAckPacket.cpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubAckPacket.cpp new file mode 100644 index 0000000..efa5fa4 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubAckPacket.cpp @@ -0,0 +1,30 @@ +#include "PubAckPacket.hpp" + +using AsyncMqttClientInternals::PubAckPacket; + +PubAckPacket::PubAckPacket(ParsingInformation* parsingInformation, OnPubAckInternalCallback callback) +: _parsingInformation(parsingInformation) +, _callback(callback) +, _bytePosition(0) +, _packetIdMsb(0) +, _packetId(0) { +} + +PubAckPacket::~PubAckPacket() { +} + +void PubAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { + char currentByte = data[(*currentBytePosition)++]; + if (_bytePosition++ == 0) { + _packetIdMsb = currentByte; + } else { + _packetId = currentByte | _packetIdMsb << 8; + _parsingInformation->bufferState = BufferState::NONE; + _callback(_packetId); + } +} + +void PubAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { + (void)data; + (void)currentBytePosition; +} diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubAckPacket.hpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubAckPacket.hpp new file mode 100644 index 0000000..bd00142 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubAckPacket.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "Arduino.h" +#include "Packet.hpp" +#include "../ParsingInformation.hpp" +#include "../Callbacks.hpp" + +namespace AsyncMqttClientInternals { +class PubAckPacket : public Packet { + public: + explicit PubAckPacket(ParsingInformation* parsingInformation, OnPubAckInternalCallback callback); + ~PubAckPacket(); + + void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); + void parsePayload(char* data, size_t len, size_t* currentBytePosition); + + private: + ParsingInformation* _parsingInformation; + OnPubAckInternalCallback _callback; + + uint8_t _bytePosition; + char _packetIdMsb; + uint16_t _packetId; +}; +} // namespace AsyncMqttClientInternals diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubCompPacket.cpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubCompPacket.cpp new file mode 100644 index 0000000..2b3e00d --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubCompPacket.cpp @@ -0,0 +1,30 @@ +#include "PubCompPacket.hpp" + +using AsyncMqttClientInternals::PubCompPacket; + +PubCompPacket::PubCompPacket(ParsingInformation* parsingInformation, OnPubCompInternalCallback callback) +: _parsingInformation(parsingInformation) +, _callback(callback) +, _bytePosition(0) +, _packetIdMsb(0) +, _packetId(0) { +} + +PubCompPacket::~PubCompPacket() { +} + +void PubCompPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { + char currentByte = data[(*currentBytePosition)++]; + if (_bytePosition++ == 0) { + _packetIdMsb = currentByte; + } else { + _packetId = currentByte | _packetIdMsb << 8; + _parsingInformation->bufferState = BufferState::NONE; + _callback(_packetId); + } +} + +void PubCompPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { + (void)data; + (void)currentBytePosition; +} diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubCompPacket.hpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubCompPacket.hpp new file mode 100644 index 0000000..17c1db4 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubCompPacket.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "Arduino.h" +#include "Packet.hpp" +#include "../ParsingInformation.hpp" +#include "../Callbacks.hpp" + +namespace AsyncMqttClientInternals { +class PubCompPacket : public Packet { + public: + explicit PubCompPacket(ParsingInformation* parsingInformation, OnPubCompInternalCallback callback); + ~PubCompPacket(); + + void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); + void parsePayload(char* data, size_t len, size_t* currentBytePosition); + + private: + ParsingInformation* _parsingInformation; + OnPubCompInternalCallback _callback; + + uint8_t _bytePosition; + char _packetIdMsb; + uint16_t _packetId; +}; +} // namespace AsyncMqttClientInternals diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubRecPacket.cpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubRecPacket.cpp new file mode 100644 index 0000000..ec535c6 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubRecPacket.cpp @@ -0,0 +1,30 @@ +#include "PubRecPacket.hpp" + +using AsyncMqttClientInternals::PubRecPacket; + +PubRecPacket::PubRecPacket(ParsingInformation* parsingInformation, OnPubRecInternalCallback callback) +: _parsingInformation(parsingInformation) +, _callback(callback) +, _bytePosition(0) +, _packetIdMsb(0) +, _packetId(0) { +} + +PubRecPacket::~PubRecPacket() { +} + +void PubRecPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { + char currentByte = data[(*currentBytePosition)++]; + if (_bytePosition++ == 0) { + _packetIdMsb = currentByte; + } else { + _packetId = currentByte | _packetIdMsb << 8; + _parsingInformation->bufferState = BufferState::NONE; + _callback(_packetId); + } +} + +void PubRecPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { + (void)data; + (void)currentBytePosition; +} diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubRecPacket.hpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubRecPacket.hpp new file mode 100644 index 0000000..910130a --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubRecPacket.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "Arduino.h" +#include "Packet.hpp" +#include "../ParsingInformation.hpp" +#include "../Callbacks.hpp" + +namespace AsyncMqttClientInternals { +class PubRecPacket : public Packet { + public: + explicit PubRecPacket(ParsingInformation* parsingInformation, OnPubRecInternalCallback callback); + ~PubRecPacket(); + + void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); + void parsePayload(char* data, size_t len, size_t* currentBytePosition); + + private: + ParsingInformation* _parsingInformation; + OnPubRecInternalCallback _callback; + + uint8_t _bytePosition; + char _packetIdMsb; + uint16_t _packetId; +}; +} // namespace AsyncMqttClientInternals diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubRelPacket.cpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubRelPacket.cpp new file mode 100644 index 0000000..2d5abde --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubRelPacket.cpp @@ -0,0 +1,30 @@ +#include "PubRelPacket.hpp" + +using AsyncMqttClientInternals::PubRelPacket; + +PubRelPacket::PubRelPacket(ParsingInformation* parsingInformation, OnPubRelInternalCallback callback) +: _parsingInformation(parsingInformation) +, _callback(callback) +, _bytePosition(0) +, _packetIdMsb(0) +, _packetId(0) { +} + +PubRelPacket::~PubRelPacket() { +} + +void PubRelPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { + char currentByte = data[(*currentBytePosition)++]; + if (_bytePosition++ == 0) { + _packetIdMsb = currentByte; + } else { + _packetId = currentByte | _packetIdMsb << 8; + _parsingInformation->bufferState = BufferState::NONE; + _callback(_packetId); + } +} + +void PubRelPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { + (void)data; + (void)currentBytePosition; +} diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubRelPacket.hpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubRelPacket.hpp new file mode 100644 index 0000000..edea3d5 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PubRelPacket.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "Arduino.h" +#include "Packet.hpp" +#include "../ParsingInformation.hpp" +#include "../Callbacks.hpp" + +namespace AsyncMqttClientInternals { +class PubRelPacket : public Packet { + public: + explicit PubRelPacket(ParsingInformation* parsingInformation, OnPubRelInternalCallback callback); + ~PubRelPacket(); + + void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); + void parsePayload(char* data, size_t len, size_t* currentBytePosition); + + private: + ParsingInformation* _parsingInformation; + OnPubRelInternalCallback _callback; + + uint8_t _bytePosition; + char _packetIdMsb; + uint16_t _packetId; +}; +} // namespace AsyncMqttClientInternals diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PublishPacket.cpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PublishPacket.cpp new file mode 100644 index 0000000..dab30a1 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PublishPacket.cpp @@ -0,0 +1,91 @@ +#include "PublishPacket.hpp" + +using AsyncMqttClientInternals::PublishPacket; + +PublishPacket::PublishPacket(ParsingInformation* parsingInformation, OnMessageInternalCallback dataCallback, OnPublishInternalCallback completeCallback) +: _parsingInformation(parsingInformation) +, _dataCallback(dataCallback) +, _completeCallback(completeCallback) +, _dup(false) +, _qos(0) +, _retain(0) +, _bytePosition(0) +, _topicLengthMsb(0) +, _topicLength(0) +, _ignore(false) +, _packetIdMsb(0) +, _packetId(0) +, _payloadLength(0) +, _payloadBytesRead(0) { + _dup = _parsingInformation->packetFlags & HeaderFlag.PUBLISH_DUP; + _retain = _parsingInformation->packetFlags & HeaderFlag.PUBLISH_RETAIN; + char qosMasked = _parsingInformation->packetFlags & 0x06; + switch (qosMasked) { + case HeaderFlag.PUBLISH_QOS0: + _qos = 0; + break; + case HeaderFlag.PUBLISH_QOS1: + _qos = 1; + break; + case HeaderFlag.PUBLISH_QOS2: + _qos = 2; + break; + } +} + +PublishPacket::~PublishPacket() { +} + +void PublishPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { + char currentByte = data[(*currentBytePosition)++]; + if (_bytePosition == 0) { + _topicLengthMsb = currentByte; + } else if (_bytePosition == 1) { + _topicLength = currentByte | _topicLengthMsb << 8; + if (_topicLength > _parsingInformation->maxTopicLength) { + _ignore = true; + } else { + _parsingInformation->topicBuffer[_topicLength] = '\0'; + } + } else if (_bytePosition >= 2 && _bytePosition < 2 + _topicLength) { + // Starting from here, _ignore might be true + if (!_ignore) _parsingInformation->topicBuffer[_bytePosition - 2] = currentByte; + if (_bytePosition == 2 + _topicLength - 1 && _qos == 0) { + _preparePayloadHandling(_parsingInformation->remainingLength - (_bytePosition + 1)); + return; + } + } else if (_bytePosition == 2 + _topicLength) { + _packetIdMsb = currentByte; + } else { + _packetId = currentByte | _packetIdMsb << 8; + _preparePayloadHandling(_parsingInformation->remainingLength - (_bytePosition + 1)); + } + _bytePosition++; +} + +void PublishPacket::_preparePayloadHandling(uint32_t payloadLength) { + _payloadLength = payloadLength; + if (payloadLength == 0) { + _parsingInformation->bufferState = BufferState::NONE; + if (!_ignore) { + _dataCallback(_parsingInformation->topicBuffer, nullptr, _qos, _dup, _retain, 0, 0, 0, _packetId); + _completeCallback(_packetId, _qos); + } + } else { + _parsingInformation->bufferState = BufferState::PAYLOAD; + } +} + +void PublishPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { + size_t remainToRead = len - (*currentBytePosition); + if (_payloadBytesRead + remainToRead > _payloadLength) remainToRead = _payloadLength - _payloadBytesRead; + + if (!_ignore) _dataCallback(_parsingInformation->topicBuffer, data + (*currentBytePosition), _qos, _dup, _retain, remainToRead, _payloadBytesRead, _payloadLength, _packetId); + _payloadBytesRead += remainToRead; + (*currentBytePosition) += remainToRead; + + if (_payloadBytesRead == _payloadLength) { + _parsingInformation->bufferState = BufferState::NONE; + if (!_ignore) _completeCallback(_packetId, _qos); + } +} diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PublishPacket.hpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PublishPacket.hpp new file mode 100644 index 0000000..9dc19f0 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/PublishPacket.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "Arduino.h" +#include "Packet.hpp" +#include "../Flags.hpp" +#include "../ParsingInformation.hpp" +#include "../Callbacks.hpp" + +namespace AsyncMqttClientInternals { +class PublishPacket : public Packet { + public: + explicit PublishPacket(ParsingInformation* parsingInformation, OnMessageInternalCallback dataCallback, OnPublishInternalCallback completeCallback); + ~PublishPacket(); + + void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); + void parsePayload(char* data, size_t len, size_t* currentBytePosition); + + private: + ParsingInformation* _parsingInformation; + OnMessageInternalCallback _dataCallback; + OnPublishInternalCallback _completeCallback; + + void _preparePayloadHandling(uint32_t payloadLength); + + bool _dup; + uint8_t _qos; + bool _retain; + + uint8_t _bytePosition; + char _topicLengthMsb; + uint16_t _topicLength; + bool _ignore; + char _packetIdMsb; + uint16_t _packetId; + uint32_t _payloadLength; + uint32_t _payloadBytesRead; +}; +} // namespace AsyncMqttClientInternals diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/SubAckPacket.cpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/SubAckPacket.cpp new file mode 100644 index 0000000..ab89965 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/SubAckPacket.cpp @@ -0,0 +1,46 @@ +#include "SubAckPacket.hpp" + +using AsyncMqttClientInternals::SubAckPacket; + +SubAckPacket::SubAckPacket(ParsingInformation* parsingInformation, OnSubAckInternalCallback callback) +: _parsingInformation(parsingInformation) +, _callback(callback) +, _bytePosition(0) +, _packetIdMsb(0) +, _packetId(0) { +} + +SubAckPacket::~SubAckPacket() { +} + +void SubAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { + char currentByte = data[(*currentBytePosition)++]; + if (_bytePosition++ == 0) { + _packetIdMsb = currentByte; + } else { + _packetId = currentByte | _packetIdMsb << 8; + _parsingInformation->bufferState = BufferState::PAYLOAD; + } +} + +void SubAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { + char status = data[(*currentBytePosition)++]; + + /* switch (status) { + case 0: + Serial.println("Success QoS 0"); + break; + case 1: + Serial.println("Success QoS 1"); + break; + case 2: + Serial.println("Success QoS 2"); + break; + case 0x80: + Serial.println("Failure"); + break; + } */ + + _parsingInformation->bufferState = BufferState::NONE; + _callback(_packetId, status); +} diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/SubAckPacket.hpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/SubAckPacket.hpp new file mode 100644 index 0000000..011b800 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/SubAckPacket.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "Arduino.h" +#include "Packet.hpp" +#include "../ParsingInformation.hpp" +#include "../Callbacks.hpp" + +namespace AsyncMqttClientInternals { +class SubAckPacket : public Packet { + public: + explicit SubAckPacket(ParsingInformation* parsingInformation, OnSubAckInternalCallback callback); + ~SubAckPacket(); + + void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); + void parsePayload(char* data, size_t len, size_t* currentBytePosition); + + private: + ParsingInformation* _parsingInformation; + OnSubAckInternalCallback _callback; + + uint8_t _bytePosition; + char _packetIdMsb; + uint16_t _packetId; +}; +} // namespace AsyncMqttClientInternals diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/UnsubAckPacket.cpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/UnsubAckPacket.cpp new file mode 100644 index 0000000..a44943d --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/UnsubAckPacket.cpp @@ -0,0 +1,30 @@ +#include "UnsubAckPacket.hpp" + +using AsyncMqttClientInternals::UnsubAckPacket; + +UnsubAckPacket::UnsubAckPacket(ParsingInformation* parsingInformation, OnUnsubAckInternalCallback callback) +: _parsingInformation(parsingInformation) +, _callback(callback) +, _bytePosition(0) +, _packetIdMsb(0) +, _packetId(0) { +} + +UnsubAckPacket::~UnsubAckPacket() { +} + +void UnsubAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { + char currentByte = data[(*currentBytePosition)++]; + if (_bytePosition++ == 0) { + _packetIdMsb = currentByte; + } else { + _packetId = currentByte | _packetIdMsb << 8; + _parsingInformation->bufferState = BufferState::NONE; + _callback(_packetId); + } +} + +void UnsubAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { + (void)data; + (void)currentBytePosition; +} diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/UnsubAckPacket.hpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/UnsubAckPacket.hpp new file mode 100644 index 0000000..ab5b9c5 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Packets/UnsubAckPacket.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "Arduino.h" +#include "Packet.hpp" +#include "../ParsingInformation.hpp" +#include "../Callbacks.hpp" + +namespace AsyncMqttClientInternals { +class UnsubAckPacket : public Packet { + public: + explicit UnsubAckPacket(ParsingInformation* parsingInformation, OnUnsubAckInternalCallback callback); + ~UnsubAckPacket(); + + void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); + void parsePayload(char* data, size_t len, size_t* currentBytePosition); + + private: + ParsingInformation* _parsingInformation; + OnUnsubAckInternalCallback _callback; + + uint8_t _bytePosition; + char _packetIdMsb; + uint16_t _packetId; +}; +} // namespace AsyncMqttClientInternals diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/ParsingInformation.hpp b/libraries/async-mqtt-client/src/AsyncMqttClient/ParsingInformation.hpp new file mode 100644 index 0000000..2d46f27 --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/ParsingInformation.hpp @@ -0,0 +1,21 @@ +#pragma once + +namespace AsyncMqttClientInternals { +enum class BufferState : uint8_t { + NONE = 0, + REMAINING_LENGTH = 2, + VARIABLE_HEADER = 3, + PAYLOAD = 4 +}; + +struct ParsingInformation { + BufferState bufferState; + + uint16_t maxTopicLength; + char* topicBuffer; + + uint8_t packetType; + uint16_t packetFlags; + uint32_t remainingLength; +}; +} // namespace AsyncMqttClientInternals diff --git a/libraries/async-mqtt-client/src/AsyncMqttClient/Storage.hpp b/libraries/async-mqtt-client/src/AsyncMqttClient/Storage.hpp new file mode 100644 index 0000000..725307b --- /dev/null +++ b/libraries/async-mqtt-client/src/AsyncMqttClient/Storage.hpp @@ -0,0 +1,13 @@ +#pragma once + +namespace AsyncMqttClientInternals { +struct PendingPubRel { + uint16_t packetId; +}; + +struct PendingAck { + uint8_t packetType; + uint8_t headerFlag; + uint16_t packetId; +}; +} // namespace AsyncMqttClientInternals