From 9156eb14772aa813a6918682a8513f1c466383de Mon Sep 17 00:00:00 2001 From: Max Prokhorov Date: Mon, 12 Aug 2019 23:16:06 +0300 Subject: [PATCH] Experimental support of HTTPUpdate for OTA (#1751) - add httpupdate ota impelementation - move async ota into a separate module - support bearssl & axtls through wificlientsecure - allow to disable arduino ide ota module --- code/espurna/config/dependencies.h | 5 + code/espurna/config/general.h | 88 +++++- code/espurna/config/hardware.h | 1 + code/espurna/config/progmem.h | 26 ++ code/espurna/config/prototypes.h | 37 ++- code/espurna/config/types.h | 23 +- code/espurna/espurna.ino | 7 +- code/espurna/libs/URL.h | 68 ++++ code/espurna/ota.ino | 298 ------------------ code/espurna/ota_arduinoota.ino | 101 ++++++ code/espurna/ota_asynctcp.ino | 227 +++++++++++++ code/espurna/ota_httpupdate.ino | 252 +++++++++++++++ code/espurna/static/digicert_evroot_pem.h | 42 +++ .../static/letsencrypt_isrgrootx1_pem.h | 46 +++ code/espurna/terminal.ino | 23 +- code/espurna/utils.ino | 9 +- code/espurna/web.ino | 6 +- 17 files changed, 948 insertions(+), 311 deletions(-) create mode 100644 code/espurna/libs/URL.h delete mode 100644 code/espurna/ota.ino create mode 100644 code/espurna/ota_arduinoota.ino create mode 100644 code/espurna/ota_asynctcp.ino create mode 100644 code/espurna/ota_httpupdate.ino create mode 100644 code/espurna/static/digicert_evroot_pem.h create mode 100644 code/espurna/static/letsencrypt_isrgrootx1_pem.h diff --git a/code/espurna/config/dependencies.h b/code/espurna/config/dependencies.h index 281a2437..1eb49f4a 100644 --- a/code/espurna/config/dependencies.h +++ b/code/espurna/config/dependencies.h @@ -71,3 +71,8 @@ #undef NTP_SUPPORT #define NTP_SUPPORT 1 // Scheduler needs NTP #endif + +#if (SECURE_CLIENT == SECURE_CLIENT_BEARSSL) +#undef OTA_CLIENT_HTTPUPDATE_2_3_0_COMPATIBLE +#define OTA_CLIENT_HTTPUPDATE_2_3_0_COMPATIBLE 0 // Use new HTTPUpdate API with BearSSL +#endif diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index e54008f0..0f331a0d 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -682,19 +682,99 @@ #define SPIFFS_SUPPORT 0 // Do not add support for SPIFFS by default #endif +// ----------------------------------------------------------------------------- +// SSL Client ** EXPERIMENTAL ** +// ----------------------------------------------------------------------------- + +#ifndef SECURE_CLIENT +#define SECURE_CLIENT SECURE_CLIENT_NONE // What variant of WiFiClient to use + // SECURE_CLIENT_NONE - No secure client support (default) + // SECURE_CLIENT_AXTLS - axTLS client secure support (All Core versions, ONLY TLS 1.1) + // SECURE_CLIENT_BEARSSL - BearSSL client secure support (starting with 2.5.0, TLS 1.2) + // + // axTLS marked for derecation since Arduino Core 2.4.2 and **will** be removed in the future +#endif + +// Security check that is performed when the connection is established: +// SECURE_CLIENT_CHECK_CA - Use Trust Anchor / Root Certificate +// Supported only by the SECURE_CLIENT_BEARSSL +// (See respective ..._SECURE_CLIENT_INCLUDE_CA options per-module) +// SECURE_CLIENT_CHECK_FINGERPRINT - Check certificate fingerprint +// SECURE_CLIENT_CHECK_NONE - Allow insecure connections + +#ifndef SECURE_CLIENT_CHECK + +#if SECURE_CLIENT == SECURE_CLIENT_BEARSSL +#define SECURE_CLIENT_CHECK SECURE_CLIENT_CHECK_CA + +#else +#define SECURE_CLIENT_CHECK SECURE_CLIENT_CHECK_FINGERPRINT + +#endif + + +#endif // SECURE_CLIENT_CHECK + +// Support Maximum Fragment Length Negotiation TLS extension +// "...negotiate a smaller maximum fragment length due to memory limitations or bandwidth limitations." +// - https://arduino-esp8266.readthedocs.io/en/latest/esp8266wifi/bearssl-client-secure-class.html#mfln-or-maximum-fragment-length-negotiation-saving-ram +// - https://tools.ietf.org/html/rfc6066#section-4 +#ifndef SECURE_CLIENT_MFLN +#define SECURE_CLIENT_MFLN 0 // The only possible values are: 512, 1024, 2048 and 4096 + // Set to 0 to disable (default) +#endif + // ----------------------------------------------------------------------------- // OTA // ----------------------------------------------------------------------------- #ifndef OTA_PORT -#define OTA_PORT 8266 // OTA port +#define OTA_PORT 8266 // Port for ArduinoOTA #endif #ifndef OTA_MQTT_SUPPORT -#define OTA_MQTT_SUPPORT 0 // No support by default +#define OTA_MQTT_SUPPORT 0 // Listen for HTTP(s) URLs at '/ota'. Depends on OTA_CLIENT +#endif + +#ifndef OTA_ARDUINOOTA_SUPPORT +#define OTA_ARDUINOOTA_SUPPORT 1 // Support ArduinoOTA by default (4.2Kb) + // Implicitly depends on ESP8266mDNS library, thus increasing firmware size +#endif + +#ifndef OTA_CLIENT +#define OTA_CLIENT OTA_CLIENT_ASYNCTCP // Terminal / MQTT OTA support + // OTA_CLIENT_ASYNCTCP (ESPAsyncTCP library) + // OTA_CLIENT_HTTPUPDATE (Arduino Core library) +#endif + +#ifndef OTA_CLIENT_HTTPUPDATE_2_3_0_COMPATIBLE +#define OTA_CLIENT_HTTPUPDATE_2_3_0_COMPATIBLE 1 // Use old HTTPUpdate API by default +#endif + +#define OTA_GITHUB_FP "CA:06:F5:6B:25:8B:7A:0D:4F:2B:05:47:09:39:47:86:51:15:19:84" + +#ifndef OTA_FINGERPRINT +#define OTA_FINGERPRINT OTA_GITHUB_FP +#endif + +#ifndef OTA_SECURE_CLIENT_CHECK +#define OTA_SECURE_CLIENT_CHECK SECURE_CLIENT_CHECK +#endif + +#ifndef OTA_SECURE_CLIENT_MFLN +#define OTA_SECURE_CLIENT_MFLN SECURE_CLIENT_MFLN +#endif + +#ifndef OTA_SECURE_CLIENT_INCLUDE_CA +#define OTA_SECURE_CLIENT_INCLUDE_CA 0 // Use user-provided CA. Only PROGMEM PEM option is supported. + // TODO: eventually should be replaced with pre-parsed structs, read directly from flash + // (ref: https://github.com/earlephilhower/bearssl-esp8266/pull/14) + // + // When enabled, current implementation includes "static/ota_secure_client_ca.h" with + // const char _ota_client_http_update_ca[] PROGMEM = "...PEM data..."; + // By default, using DigiCert root in "static/digicert_evroot_pem.h" (for https://github.com) #endif -#define OTA_GITHUB_FP "D7:9F:07:61:10:B3:92:93:E3:49:AC:89:84:5B:03:80:C1:9E:2F:8B" // ----------------------------------------------------------------------------- // NOFUSS @@ -1330,6 +1410,8 @@ #endif // Enable RCSwitch support +// Originally implemented for SONOFF BASIC +// https://tinkerman.cat/adding-rf-to-a-non-rf-itead-sonoff/ // Also possible to use with SONOFF RF BRIDGE, thanks to @wildwiz // https://github.com/xoseperez/espurna/wiki/Hardware-Itead-Sonoff-RF-Bridge---Direct-Hack #ifndef RFB_DIRECT diff --git a/code/espurna/config/hardware.h b/code/espurna/config/hardware.h index 2992e2bf..471ce2ab 100644 --- a/code/espurna/config/hardware.h +++ b/code/espurna/config/hardware.h @@ -3975,6 +3975,7 @@ #define INFLUXDB_SUPPORT 1 #define IR_SUPPORT 1 #define RF_SUPPORT 1 + #define OTA_MQTT_SUPPORT 1 #define RFB_DIRECT 1 #define RFB_RX_PIN 4 diff --git a/code/espurna/config/progmem.h b/code/espurna/config/progmem.h index 5f8649ab..c304b7b4 100644 --- a/code/espurna/config/progmem.h +++ b/code/espurna/config/progmem.h @@ -135,6 +135,32 @@ PROGMEM const char espurna_modules[] = #endif ""; +PROGMEM const char espurna_ota_modules[] = + #if OTA_ARDUINOOTA_SUPPORT + "ARDUINO " + #endif + #if (OTA_CLIENT == OTA_CLIENT_ASYNCTCP) + "ASYNCTCP " + #endif + #if (OTA_CLIENT == OTA_CLIENT_HTTPUPDATE) + #if (SECURE_CLIENT == SECURE_CLIENT_NONE) + "*HTTPUPDATE " + #endif + #if (SECURE_CLIENT == SECURE_CLIENT_AXTLS) + "*HTTPUPDATE_AXTLS " + #endif + #if (SECURE_CLIENT == SECURE_CLIENT_BEARSSL) + "*HTTPUPDATE_BEARSSL " + #endif + #endif // OTA_CLIENT_HTTPUPDATE + #if OTA_MQTT_SUPPORT + "MQTT " + #endif + #if WEB_SUPPORT + "WEB " + #endif + ""; + //-------------------------------------------------------------------------------- // Sensors //-------------------------------------------------------------------------------- diff --git a/code/espurna/config/prototypes.h b/code/espurna/config/prototypes.h index d961bd0d..dd8ec0b0 100644 --- a/code/espurna/config/prototypes.h +++ b/code/espurna/config/prototypes.h @@ -1,6 +1,7 @@ #include #include #include +#include #include extern "C" { @@ -168,7 +169,30 @@ void i2c_read_buffer(uint8_t address, uint8_t * buffer, size_t len); // ----------------------------------------------------------------------------- // OTA // ----------------------------------------------------------------------------- -#include "ESPAsyncTCP.h" + +#include + +#if OTA_CLIENT == OTA_CLIENT_ASYNCTCP + #include +#endif + +#if OTA_CLIENT == OTA_CLIENT_HTTPUPDATE + #include + #include +#endif + +#if SECURE_CLIENT != SECURE_CLIENT_NONE + + #include + + #if OTA_SECURE_CLIENT_INCLUDE_CA + #include "static/ota_secure_client_ca.h" + #else + #include "static/digicert_evroot_pem.h" + #define _ota_client_http_update_ca _ssl_digicert_ev_root_ca + #endif + +#endif // SECURE_CLIENT_SUPPORT // ----------------------------------------------------------------------------- // RFM69 @@ -285,3 +309,14 @@ bool wifiConnected(); // ----------------------------------------------------------------------------- #include "rtcmem.h" +// ----------------------------------------------------------------------------- +// std::make_unique backport for C++11 +// ----------------------------------------------------------------------------- +#if 201103L >= __cplusplus +namespace std { + template + std::unique_ptr make_unique(Args&&... args) { + return std::unique_ptr(new T(std::forward(args)...)); + } +} +#endif diff --git a/code/espurna/config/types.h b/code/espurna/config/types.h index cf67b02a..530349a4 100644 --- a/code/espurna/config/types.h +++ b/code/espurna/config/types.h @@ -348,4 +348,25 @@ // Telnet server //------------------------------------------------------------------------------ #define TELNET_SERVER_ASYNC 0 -#define TELNET_SERVER_WIFISERVER 1 \ No newline at end of file +#define TELNET_SERVER_WIFISERVER 1 + +//------------------------------------------------------------------------------ +// OTA Client (not related to the Web OTA support) +//------------------------------------------------------------------------------ + +#define OTA_CLIENT_NONE 0 +#define OTA_CLIENT_ASYNCTCP 1 +#define OTA_CLIENT_HTTPUPDATE 2 + +//------------------------------------------------------------------------------ +// Secure Client +//------------------------------------------------------------------------------ + +#define SECURE_CLIENT_NONE 0 +#define SECURE_CLIENT_AXTLS 1 +#define SECURE_CLIENT_BEARSSL 2 + +#define SECURE_CLIENT_CHECK_NONE 0 // !!! INSECURE CONNECTION !!! +#define SECURE_CLIENT_CHECK_FINGERPRINT 1 // legacy fingerprint validation +#define SECURE_CLIENT_CHECK_CA 2 // set trust anchor from PROGMEM CA certificate + diff --git a/code/espurna/espurna.ino b/code/espurna/espurna.ino index faefbd14..4eaa8d2b 100644 --- a/code/espurna/espurna.ino +++ b/code/espurna/espurna.ino @@ -110,10 +110,15 @@ void setup() { info(); wifiSetup(); - otaSetup(); + #if OTA_ARDUINOOTA_SUPPORT + arduinoOtaSetup(); + #endif #if TELNET_SUPPORT telnetSetup(); #endif + #if OTA_CLIENT != OTA_CLIENT_NONE + otaClientSetup(); + #endif // ------------------------------------------------------------------------- // Check if system is stable diff --git a/code/espurna/libs/URL.h b/code/espurna/libs/URL.h new file mode 100644 index 00000000..a51e54fd --- /dev/null +++ b/code/espurna/libs/URL.h @@ -0,0 +1,68 @@ +// ----------------------------------------------------------------------------- +// Parse char string as URL +// +// Adapted from HTTPClient::beginInternal() +// https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp +// +// ----------------------------------------------------------------------------- + +#pragma once + +struct URL { + String value; + String protocol; + String host; + String path; + uint16_t port; + + URL(const char* url) { init(url); } + URL(const String& url) { init(url); } + + void init(String url); +}; + +void URL::init(String url) { + + this->value = url; + + // cut the protocol part + int index = url.indexOf("://"); + if (index > 0) { + this->protocol = url.substring(0, index); + url.remove(0, (index + 3)); + } + + if (this->protocol == "http") { + this->port = 80; + } else if (this->protocol == "https") { + this->port = 443; + } + + // cut the host part + String _host; + + index = url.indexOf('/'); + if (index >= 0) { + _host = url.substring(0, index); + } else { + _host = url; + } + + // store the remaining part as path + if (index >= 0) { + url.remove(0, index); + this->path = url; + } else { + this->path = "/"; + } + + // separate host from port, when present + index = _host.indexOf(':'); + if (index >= 0) { + this->port = _host.substring(index + 1).toInt(); + this->host = _host.substring(0, index); + } else { + this->host = _host; + } + +} diff --git a/code/espurna/ota.ino b/code/espurna/ota.ino deleted file mode 100644 index 69ae888e..00000000 --- a/code/espurna/ota.ino +++ /dev/null @@ -1,298 +0,0 @@ -/* - -OTA MODULE - -Copyright (C) 2016-2019 by Xose Pérez - -*/ - -#include "ArduinoOTA.h" - -// ----------------------------------------------------------------------------- -// Arduino OTA -// ----------------------------------------------------------------------------- - -void _otaConfigure() { - ArduinoOTA.setPort(OTA_PORT); - ArduinoOTA.setHostname(getSetting("hostname").c_str()); - #if USE_PASSWORD - ArduinoOTA.setPassword(getAdminPass().c_str()); - #endif -} - -void _otaLoop() { - ArduinoOTA.handle(); -} - -// ----------------------------------------------------------------------------- -// Terminal OTA -// ----------------------------------------------------------------------------- - -#if TERMINAL_SUPPORT || OTA_MQTT_SUPPORT - -#include -AsyncClient * _ota_client; -char * _ota_host; -char * _ota_url; -unsigned int _ota_port = 80; -unsigned long _ota_size = 0; - -const char OTA_REQUEST_TEMPLATE[] PROGMEM = - "GET %s HTTP/1.1\r\n" - "Host: %s\r\n" - "User-Agent: ESPurna\r\n" - "Connection: close\r\n" - "Content-Type: application/x-www-form-urlencoded\r\n" - "Content-Length: 0\r\n\r\n\r\n"; - - -void _otaFrom(const char * host, unsigned int port, const char * url) { - - if (_ota_host) free(_ota_host); - if (_ota_url) free(_ota_url); - _ota_host = strdup(host); - _ota_url = strdup(url); - _ota_port = port; - _ota_size = 0; - - if (_ota_client == NULL) { - _ota_client = new AsyncClient(); - } - - _ota_client->onDisconnect([](void *s, AsyncClient *c) { - - DEBUG_MSG_P(PSTR("\n")); - - if (Update.end(true)){ - DEBUG_MSG_P(PSTR("[OTA] Success: %u bytes\n"), _ota_size); - deferredReset(100, CUSTOM_RESET_OTA); - } else { - #ifdef DEBUG_PORT - Update.printError(DEBUG_PORT); - #endif - eepromRotate(true); - } - - DEBUG_MSG_P(PSTR("[OTA] Disconnected\n")); - - _ota_client->free(); - delete _ota_client; - _ota_client = NULL; - free(_ota_host); - _ota_host = NULL; - free(_ota_url); - _ota_url = NULL; - - }, 0); - - _ota_client->onTimeout([](void *s, AsyncClient *c, uint32_t time) { - _ota_client->close(true); - }, 0); - - _ota_client->onData([](void * arg, AsyncClient * c, void * data, size_t len) { - - char * p = (char *) data; - - if (_ota_size == 0) { - - Update.runAsync(true); - if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) { - #ifdef DEBUG_PORT - Update.printError(DEBUG_PORT); - #endif - } - - p = strstr((char *)data, "\r\n\r\n") + 4; - len = len - (p - (char *) data); - - } - - if (!Update.hasError()) { - if (Update.write((uint8_t *) p, len) != len) { - #ifdef DEBUG_PORT - Update.printError(DEBUG_PORT); - #endif - } - } - - _ota_size += len; - DEBUG_MSG_P(PSTR("[OTA] Progress: %u bytes\r"), _ota_size); - - delay(0); - - }, NULL); - - _ota_client->onConnect([](void * arg, AsyncClient * client) { - - #if ASYNC_TCP_SSL_ENABLED - if (443 == _ota_port) { - uint8_t fp[20] = {0}; - sslFingerPrintArray(getSetting("otafp", OTA_GITHUB_FP).c_str(), fp); - SSL * ssl = _ota_client->getSSL(); - if (ssl_match_fingerprint(ssl, fp) != SSL_OK) { - DEBUG_MSG_P(PSTR("[OTA] Warning: certificate doesn't match\n")); - } - } - #endif - - // Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade - eepromRotate(false); - - DEBUG_MSG_P(PSTR("[OTA] Downloading %s\n"), _ota_url); - char buffer[strlen_P(OTA_REQUEST_TEMPLATE) + strlen(_ota_url) + strlen(_ota_host)]; - snprintf_P(buffer, sizeof(buffer), OTA_REQUEST_TEMPLATE, _ota_url, _ota_host); - client->write(buffer); - - }, NULL); - - #if ASYNC_TCP_SSL_ENABLED - bool connected = _ota_client->connect(host, port, 443 == port); - #else - bool connected = _ota_client->connect(host, port); - #endif - - if (!connected) { - DEBUG_MSG_P(PSTR("[OTA] Connection failed\n")); - _ota_client->close(true); - } - -} - -void _otaFrom(String url) { - if (!url.startsWith("http://") && !url.startsWith("https://")) { - DEBUG_MSG_P(PSTR("[OTA] Incorrect URL specified\n")); - return; - } - - // Port from protocol - unsigned int port = 80; - if (url.startsWith("https://")) port = 443; - url = url.substring(url.indexOf("/") + 2); - - // Get host - String host = url.substring(0, url.indexOf("/")); - - // Explicit port - int p = host.indexOf(":"); - if (p > 0) { - port = host.substring(p + 1).toInt(); - host = host.substring(0, p); - } - - // Get URL - String uri = url.substring(url.indexOf("/")); - - _otaFrom(host.c_str(), port, uri.c_str()); - -} - -#endif // TERMINAL_SUPPORT || OTA_MQTT_SUPPORT - - -#if TERMINAL_SUPPORT - -void _otaInitCommands() { - - terminalRegisterCommand(F("OTA"), [](Embedis* e) { - if (e->argc < 2) { - terminalError(F("Wrong arguments")); - } else { - terminalOK(); - String url = String(e->argv[1]); - _otaFrom(url); - } - }); - -} - -#endif // TERMINAL_SUPPORT - -#if OTA_MQTT_SUPPORT - -void _otaMQTTCallback(unsigned int type, const char * topic, const char * payload) { - if (type == MQTT_CONNECT_EVENT) { - mqttSubscribe(MQTT_TOPIC_OTA); - } - - if (type == MQTT_MESSAGE_EVENT) { - // Match topic - String t = mqttMagnitude((char *) topic); - if (t.equals(MQTT_TOPIC_OTA)) { - DEBUG_MSG_P(PSTR("[OTA] Initiating from URL: %s\n"), payload); - _otaFrom(payload); - } - } -} - -#endif // OTA_MQTT_SUPPORT - -// ----------------------------------------------------------------------------- - -void otaSetup() { - - _otaConfigure(); - - #if TERMINAL_SUPPORT - _otaInitCommands(); - #endif - - #if OTA_MQTT_SUPPORT - mqttRegister(_otaMQTTCallback); - #endif - - // Main callbacks - espurnaRegisterLoop(_otaLoop); - espurnaRegisterReload(_otaConfigure); - - // ------------------------------------------------------------------------- - - ArduinoOTA.onStart([]() { - - // Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade - eepromRotate(false); - - // Because ArduinoOTA is synchronous, force backup right now instead of waiting for the next loop() - eepromBackup(0); - - DEBUG_MSG_P(PSTR("[OTA] Start\n")); - - #if WEB_SUPPORT - wsSend_P(PSTR("{\"message\": 2}")); - #endif - - }); - - ArduinoOTA.onEnd([]() { - DEBUG_MSG_P(PSTR("\n")); - DEBUG_MSG_P(PSTR("[OTA] Done, restarting...\n")); - #if WEB_SUPPORT - wsSend_P(PSTR("{\"action\": \"reload\"}")); - #endif - deferredReset(100, CUSTOM_RESET_OTA); - }); - - ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { - static unsigned int _progOld; - - unsigned int _prog = (progress / (total / 100)); - if (_prog != _progOld) { - DEBUG_MSG_P(PSTR("[OTA] Progress: %u%%\r"), _prog); - _progOld = _prog; - } - }); - - ArduinoOTA.onError([](ota_error_t error) { - #if DEBUG_SUPPORT - DEBUG_MSG_P(PSTR("\n[OTA] Error #%u: "), error); - if (error == OTA_AUTH_ERROR) DEBUG_MSG_P(PSTR("Auth Failed\n")); - else if (error == OTA_BEGIN_ERROR) DEBUG_MSG_P(PSTR("Begin Failed\n")); - else if (error == OTA_CONNECT_ERROR) DEBUG_MSG_P(PSTR("Connect Failed\n")); - else if (error == OTA_RECEIVE_ERROR) DEBUG_MSG_P(PSTR("Receive Failed\n")); - else if (error == OTA_END_ERROR) DEBUG_MSG_P(PSTR("End Failed\n")); - #endif - eepromRotate(true); - }); - - ArduinoOTA.begin(); - -} diff --git a/code/espurna/ota_arduinoota.ino b/code/espurna/ota_arduinoota.ino new file mode 100644 index 00000000..78664421 --- /dev/null +++ b/code/espurna/ota_arduinoota.ino @@ -0,0 +1,101 @@ +/* + +ARDUINO OTA MODULE + +Copyright (C) 2016-2019 by Xose Pérez + +*/ + +#if OTA_ARDUINOOTA_SUPPORT + +// TODO: allocate ArduinoOTAClass on-demand, stop using global instance + +void _arduinoOtaConfigure() { + + ArduinoOTA.setPort(OTA_PORT); + ArduinoOTA.setHostname(getSetting("hostname").c_str()); + #if USE_PASSWORD + ArduinoOTA.setPassword(getAdminPass().c_str()); + #endif + ArduinoOTA.begin(); + +} + +void _arduinoOtaLoop() { + ArduinoOTA.handle(); +} + +void _arduinoOtaOnStart() { + + // Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade + eepromRotate(false); + + // Because ArduinoOTA is synchronous, force backup right now instead of waiting for the next loop() + eepromBackup(0); + + DEBUG_MSG_P(PSTR("[OTA] Start\n")); + + #if WEB_SUPPORT + wsSend_P(PSTR("{\"message\": 2}")); + #endif + +} + +void _arduinoOtaOnEnd() { + + DEBUG_MSG_P(PSTR("\n")); + DEBUG_MSG_P(PSTR("[OTA] Done, restarting...\n")); + #if WEB_SUPPORT + wsSend_P(PSTR("{\"action\": \"reload\"}")); + #endif + deferredReset(100, CUSTOM_RESET_OTA); + +} + +void _arduinoOtaOnProgress(unsigned int progress, unsigned int total) { + + // Removed to avoid websocket ping back during upgrade (see #1574) + // TODO: implement as separate from debugging message + #if WEB_SUPPORT + if (wsConnected()) return; + #endif + + static unsigned int _progOld; + + unsigned int _prog = (progress / (total / 100)); + if (_prog != _progOld) { + DEBUG_MSG_P(PSTR("[OTA] Progress: %u%%\r"), _prog); + _progOld = _prog; + } + +} + +void _arduinoOtaOnError(ota_error_t error) { + + #if DEBUG_SUPPORT + DEBUG_MSG_P(PSTR("\n[OTA] Error #%u: "), error); + if (error == OTA_AUTH_ERROR) DEBUG_MSG_P(PSTR("Auth Failed\n")); + else if (error == OTA_BEGIN_ERROR) DEBUG_MSG_P(PSTR("Begin Failed\n")); + else if (error == OTA_CONNECT_ERROR) DEBUG_MSG_P(PSTR("Connect Failed\n")); + else if (error == OTA_RECEIVE_ERROR) DEBUG_MSG_P(PSTR("Receive Failed\n")); + else if (error == OTA_END_ERROR) DEBUG_MSG_P(PSTR("End Failed\n")); + #endif + eepromRotate(true); + +} + +void arduinoOtaSetup() { + + espurnaRegisterLoop(_arduinoOtaLoop); + espurnaRegisterReload(_arduinoOtaConfigure); + + ArduinoOTA.onStart(_arduinoOtaOnStart); + ArduinoOTA.onEnd(_arduinoOtaOnEnd); + ArduinoOTA.onError(_arduinoOtaOnError); + ArduinoOTA.onProgress(_arduinoOtaOnProgress); + + _arduinoOtaConfigure(); + +} + +#endif // OTA_ARDUINOOTA_SUPPORT diff --git a/code/espurna/ota_asynctcp.ino b/code/espurna/ota_asynctcp.ino new file mode 100644 index 00000000..e01ca236 --- /dev/null +++ b/code/espurna/ota_asynctcp.ino @@ -0,0 +1,227 @@ +/* + +ASYNC CLIENT OTA MODULE + +Copyright (C) 2016-2019 by Xose Pérez + +*/ + +#if OTA_CLIENT == OTA_CLIENT_ASYNCTCP + +// ----------------------------------------------------------------------------- +// Terminal OTA command +// ----------------------------------------------------------------------------- + +#if TERMINAL_SUPPORT || OTA_MQTT_SUPPORT + +#include +#include "libs/URL.h" + +std::unique_ptr _ota_client = nullptr; +unsigned long _ota_size = 0; +bool _ota_connected = false; +std::unique_ptr _ota_url = nullptr; + +const char OTA_REQUEST_TEMPLATE[] PROGMEM = + "GET %s HTTP/1.1\r\n" + "Host: %s\r\n" + "User-Agent: ESPurna\r\n" + "Connection: close\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: 0\r\n\r\n\r\n"; + +void _otaClientOnDisconnect(void *s, AsyncClient *c) { + + DEBUG_MSG_P(PSTR("\n")); + + if (Update.end(true)){ + DEBUG_MSG_P(PSTR("[OTA] Success: %u bytes\n"), _ota_size); + deferredReset(100, CUSTOM_RESET_OTA); + } else { + #ifdef DEBUG_PORT + Update.printError(DEBUG_PORT); + #endif + eepromRotate(true); + } + + DEBUG_MSG_P(PSTR("[OTA] Disconnected\n")); + + _ota_connected = false; + _ota_url = nullptr; + _ota_client = nullptr; + +} + +void _otaClientOnTimeout(void *s, AsyncClient *c, uint32_t time) { + _ota_connected = false; + _ota_url = nullptr; + _ota_client->close(true); +} + +void _otaClientOnData(void * arg, AsyncClient * c, void * data, size_t len) { + + char * p = (char *) data; + + if (_ota_size == 0) { + + Update.runAsync(true); + if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) { + #ifdef DEBUG_PORT + Update.printError(DEBUG_PORT); + #endif + c->close(true); + return; + } + + p = strstr((char *)data, "\r\n\r\n") + 4; + len = len - (p - (char *) data); + + } + + if (!Update.hasError()) { + if (Update.write((uint8_t *) p, len) != len) { + #ifdef DEBUG_PORT + Update.printError(DEBUG_PORT); + #endif + c->close(true); + return; + } + } + + _ota_size += len; + DEBUG_MSG_P(PSTR("[OTA] Progress: %u bytes\r"), _ota_size); + + delay(0); + +} + +void _otaClientOnConnect(void *arg, AsyncClient *client) { + + #if ASYNC_TCP_SSL_ENABLED + int check = getSetting("otaScCheck", OTA_SECURE_CLIENT_CHECK).toInt(); + if ((check == SECURE_CLIENT_CHECK_FINGERPRINT) && (443 == _ota_url->port)) { + uint8_t fp[20] = {0}; + sslFingerPrintArray(getSetting("otafp", OTA_FINGERPRINT).c_str(), fp); + SSL * ssl = _ota_client->getSSL(); + if (ssl_match_fingerprint(ssl, fp) != SSL_OK) { + DEBUG_MSG_P(PSTR("[OTA] Warning: certificate fingerpint doesn't match\n")); + client->close(true); + return; + } + } + #endif + + // Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade + eepromRotate(false); + + DEBUG_MSG_P(PSTR("[OTA] Downloading %s\n"), _ota_url->path.c_str()); + char buffer[strlen_P(OTA_REQUEST_TEMPLATE) + _ota_url->path.length() + _ota_url->host.length()]; + snprintf_P(buffer, sizeof(buffer), OTA_REQUEST_TEMPLATE, _ota_url->path.c_str(), _ota_url->host.c_str()); + client->write(buffer); +} + +void _otaClientFrom(const String& url) { + + if (_ota_connected) { + DEBUG_MSG_P(PSTR("[OTA] Already connected\n")); + return; + } + + _ota_size = 0; + + if (_ota_url) _ota_url = nullptr; + _ota_url = std::make_unique(url); + /* + DEBUG_MSG_P(PSTR("[OTA] proto:%s host:%s port:%u path:%s\n"), + _ota_url->protocol.c_str(), + _ota_url->host.c_str(), + _ota_url->port, + _ota_url->path.c_str() + ); + */ + + // we only support HTTP + if ((!_ota_url->protocol.equals("http")) && (!_ota_url->protocol.equals("https"))) { + DEBUG_MSG_P(PSTR("[OTA] Incorrect URL specified\n")); + _ota_url = nullptr; + return; + } + + if (!_ota_client) { + _ota_client = std::make_unique(); + } + + _ota_client->onDisconnect(_otaClientOnDisconnect, nullptr); + _ota_client->onTimeout(_otaClientOnTimeout, nullptr); + _ota_client->onData(_otaClientOnData, nullptr); + _ota_client->onConnect(_otaClientOnConnect, nullptr); + + #if ASYNC_TCP_SSL_ENABLED + _ota_connected = _ota_client->connect(_ota_url->host.c_str(), _ota_url->port, 443 == _ota_url->port); + #else + _ota_connected = _ota_client->connect(_ota_url->host.c_str(), _ota_url->port); + #endif + + if (!_ota_connected) { + DEBUG_MSG_P(PSTR("[OTA] Connection failed\n")); + _ota_url = nullptr; + _ota_client->close(true); + } + +} + +#endif // TERMINAL_SUPPORT || OTA_MQTT_SUPPORT + + +#if TERMINAL_SUPPORT + +void _otaClientInitCommands() { + + terminalRegisterCommand(F("OTA"), [](Embedis* e) { + if (e->argc < 2) { + terminalError(F("OTA ")); + } else { + _otaClientFrom(String(e->argv[1])); + terminalOK(); + } + }); + +} + +#endif // TERMINAL_SUPPORT + +#if OTA_MQTT_SUPPORT + +void _otaClientMqttCallback(unsigned int type, const char * topic, const char * payload) { + + if (type == MQTT_CONNECT_EVENT) { + mqttSubscribe(MQTT_TOPIC_OTA); + } + + if (type == MQTT_MESSAGE_EVENT) { + String t = mqttMagnitude((char *) topic); + if (t.equals(MQTT_TOPIC_OTA)) { + DEBUG_MSG_P(PSTR("[OTA] Initiating from URL: %s\n"), payload); + _otaClientFrom(payload); + } + } + +} + +#endif // OTA_MQTT_SUPPORT + +// ----------------------------------------------------------------------------- + +void otaClientSetup() { + + #if TERMINAL_SUPPORT + _otaClientInitCommands(); + #endif + + #if (MQTT_SUPPORT && OTA_MQTT_SUPPORT) + mqttRegister(_otaClientMqttCallback); + #endif + +} + +#endif // OTA_CLIENT == OTA_CLIENT_ASYNCTCP diff --git a/code/espurna/ota_httpupdate.ino b/code/espurna/ota_httpupdate.ino new file mode 100644 index 00000000..7cc24d80 --- /dev/null +++ b/code/espurna/ota_httpupdate.ino @@ -0,0 +1,252 @@ +/* + +HTTP(s) OTA MODULE + +Copyright (C) 2019 by Maxim Prokhorov + +*/ + +// ----------------------------------------------------------------------------- +// OTA by using Core's HTTP(s) updater +// ----------------------------------------------------------------------------- + +#if OTA_CLIENT == OTA_CLIENT_HTTPUPDATE + +#include + +#include +#include + +#include "libs/URL.h" + +void _otaClientRunUpdater(WiFiClient* client, const String& url, const String& fp = "") { + + UNUSED(client); + UNUSED(fp); + + // Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade + eepromRotate(false); + + DEBUG_MSG_P(PSTR("[OTA] Downloading %s ...\n"), url.c_str()); + + // TODO: support currentVersion (string arg after 'url') + // NOTE: ESPhttpUpdate.update(..., fp) will **always** fail with empty fingerprint + // NOTE: It is possible to support BearSSL with 2.4.2 by using uint8_t[20] instead of String for fingerprint argument + + ESPhttpUpdate.rebootOnUpdate(false); + t_httpUpdate_return result = HTTP_UPDATE_NO_UPDATES; + + // We expect both .update(url, "", String_fp) and .update(url) to survice until axTLS is removed from the Core + #if (SECURE_CLIENT == SECURE_CLIENT_AXTLS) + if (url.startsWith("https://")) { + result = ESPhttpUpdate.update(url, "", fp); + } else { + result = ESPhttpUpdate.update(url); + } + #elif OTA_CLIENT_HTTPUPDATE_2_3_0_COMPATIBLE + result = ESPhttpUpdate.update(url); + #else + result = ESPhttpUpdate.update(*client, url); + #endif + + switch (result) { + case HTTP_UPDATE_FAILED: + DEBUG_MSG_P(PSTR("[OTA] Update failed (error %d): %s\n"), ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); + eepromRotate(true); + break; + case HTTP_UPDATE_NO_UPDATES: + DEBUG_MSG_P(PSTR("[OTA] No updates")); + eepromRotate(true); + break; + case HTTP_UPDATE_OK: + DEBUG_MSG_P(PSTR("[OTA] Done, restarting...")); + deferredReset(500, CUSTOM_RESET_OTA); // wait a bit more than usual + break; + } + +} + +#if OTA_CLIENT_HTTPUPDATE_2_3_0_COMPATIBLE +void _otaClientFromHttp(const String& url) { + _otaClientRunUpdater(nullptr, url, ""); +} +#else +void _otaClientFromHttp(const String& url) { + auto client = std::make_unique(); + _otaClientRunUpdater(client.get(), url, ""); +} +#endif + +#if SECURE_CLIENT == SECURE_CLIENT_BEARSSL + +void _otaClientFromHttps(const String& url) { + + int check = getSetting("otaScCheck", OTA_SECURE_CLIENT_CHECK).toInt(); + bool settime = (check == SECURE_CLIENT_CHECK_CA); + + if (!ntpSynced() && settime) { + DEBUG_MSG_P(PSTR("[OTA] Time not synced!\n")); + return; + } + + // unique_ptr self-destructs after exiting function scope + // create WiFiClient on heap to use less stack space + auto client = std::make_unique(); + + if (check == SECURE_CLIENT_CHECK_NONE) { + DEBUG_MSG_P(PSTR("[OTA] !!! Connection will not be validated !!!\n")); + client->setInsecure(); + } + + if (check == SECURE_CLIENT_CHECK_FINGERPRINT) { + String fp_string = getSetting("otafp", OTA_FINGERPRINT); + if (!fp_string.length()) { + DEBUG_MSG_P(PSTR("[OTA] Requested fingerprint auth, but 'otafp' is not set\n")); + return; + } + + uint8_t fp_bytes[20] = {0}; + sslFingerPrintArray(fp_string.c_str(), fp_bytes); + + client->setFingerprint(fp_bytes); + } + + BearSSL::X509List *ca = nullptr; + if (check == SECURE_CLIENT_CHECK_CA) { + ca = new BearSSL::X509List(_ota_client_http_update_ca); + // because we do not support libc methods of getting time, force client to use ntpclientlib's current time + // XXX: local2utc method use is detrimental when DST happening. now() should be utc + client->setX509Time(ntpLocal2UTC(now())); + client->setTrustAnchors(ca); + } + + // TODO: RX and TX buffer sizes must be equal? + const uint16_t requested_mfln = getSetting("otaScMFLN", OTA_SECURE_CLIENT_MFLN).toInt(); + switch (requested_mfln) { + // default, do nothing + case 0: + break; + // match valid sizes only + case 512: + case 1024: + case 2048: + case 4096: + { + client->setBufferSizes(requested_mfln, requested_mfln); + break; + } + default: + DEBUG_MSG_P(PSTR("[OTA] Warning: MFLN buffer size must be one of 512, 1024, 2048 or 4096\n")); + } + + _otaClientRunUpdater(client.get(), url); + +} + +#endif // SECURE_CLIENT_BEARSSL + + +#if SECURE_CLIENT == SECURE_CLIENT_AXTLS + +void _otaClientFromHttps(const String& url) { + + const int check = getSetting("otaScCheck", OTA_SECURE_CLIENT_CHECK).toInt(); + + String fp_string; + if (check == SECURE_CLIENT_CHECK_FINGERPRINT) { + fp_string = getSetting("otafp", OTA_FINGERPRINT); + if (!fp_string.length() || !sslCheckFingerPrint(fp_string.c_str())) { + DEBUG_MSG_P(PSTR("[OTA] Wrong fingerprint\n")); + return; + } + } + + _otaClientRunUpdater(nullptr, url, fp_string); + +} + +#endif // SECURE_CLIENT_AXTLS + +void _otaClientFrom(const String& url) { + + if (url.startsWith("http://")) { + _otaClientFromHttp(url); + return; + } + + #if SECURE_CLIENT_SUPPORT + if (url.startsWith("https://")) { + _otaClientFromHttps(url); + return; + } + #endif + + DEBUG_MSG_P(PSTR("[OTA] Incorrect URL specified\n")); + +} + +#if TERMINAL_SUPPORT + +void _otaClientInitCommands() { + + terminalRegisterCommand(F("OTA"), [](Embedis* e) { + if (e->argc < 2) { + terminalError(F("OTA ")); + } else { + _otaClientFrom(String(e->argv[1])); + terminalOK(); + } + }); + +} + +#endif // TERMINAL_SUPPORT + +#if (MQTT_SUPPORT && OTA_MQTT_SUPPORT) + +bool _ota_do_update = false; +String _ota_url; + +void _otaClientLoop() { + if (_ota_do_update) { + _otaClientFrom(_ota_url); + _ota_do_update = false; + _ota_url = ""; + } +} + +void _otaClientMqttCallback(unsigned int type, const char * topic, const char * payload) { + + if (type == MQTT_CONNECT_EVENT) { + mqttSubscribe(MQTT_TOPIC_OTA); + } + + if (type == MQTT_MESSAGE_EVENT) { + String t = mqttMagnitude((char *) topic); + if (t.equals(MQTT_TOPIC_OTA)) { + DEBUG_MSG_P(PSTR("[OTA] Queuing from URL: %s\n"), payload); + _ota_do_update = true; + _ota_url = payload; + } + } + +} + +#endif // MQTT_SUPPORT + +// ----------------------------------------------------------------------------- + +void otaClientSetup() { + + #if TERMINAL_SUPPORT + _otaClientInitCommands(); + #endif + + #if (MQTT_SUPPORT && OTA_MQTT_SUPPORT) + mqttRegister(_otaClientMqttCallback); + espurnaRegisterLoop(_otaClientLoop); + #endif + +} + +#endif // OTA_CLIENT == OTA_CLIENT_HTTPUPDATE diff --git a/code/espurna/static/digicert_evroot_pem.h b/code/espurna/static/digicert_evroot_pem.h new file mode 100644 index 00000000..aa7b8c3d --- /dev/null +++ b/code/espurna/static/digicert_evroot_pem.h @@ -0,0 +1,42 @@ +// https://github.com root issuer + +// Issuer: C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert High Assurance EV Root CA +// Validity +// Not Before: Oct 22 12:00:00 2013 GMT +// Not After : Oct 22 12:00:00 2028 GMT +// Subject: C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert SHA2 Extended Validation Server CA + +#pragma once + +#include + +const char PROGMEM _ssl_digicert_ev_root_ca[] = R"EOF( +-----BEGIN CERTIFICATE----- +MIIEtjCCA56gAwIBAgIQDHmpRLCMEZUgkmFf4msdgzANBgkqhkiG9w0BAQsFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowdTEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTE0MDIGA1UEAxMrRGlnaUNlcnQgU0hBMiBFeHRlbmRlZCBW +YWxpZGF0aW9uIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBANdTpARR+JmmFkhLZyeqk0nQOe0MsLAAh/FnKIaFjI5j2ryxQDji0/XspQUY +uD0+xZkXMuwYjPrxDKZkIYXLBxA0sFKIKx9om9KxjxKws9LniB8f7zh3VFNfgHk/ +LhqqqB5LKw2rt2O5Nbd9FLxZS99RStKh4gzikIKHaq7q12TWmFXo/a8aUGxUvBHy +/Urynbt/DvTVvo4WiRJV2MBxNO723C3sxIclho3YIeSwTQyJ3DkmF93215SF2AQh +cJ1vb/9cuhnhRctWVyh+HA1BV6q3uCe7seT6Ku8hI3UarS2bhjWMnHe1c63YlC3k +8wyd7sFOYn4XwHGeLN7x+RAoGTMCAwEAAaOCAUkwggFFMBIGA1UdEwEB/wQIMAYB +Af8CAQAwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF +BQcDAjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp +Z2ljZXJ0LmNvbTBLBgNVHR8ERDBCMECgPqA8hjpodHRwOi8vY3JsNC5kaWdpY2Vy +dC5jb20vRGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3JsMD0GA1UdIAQ2 +MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5j +b20vQ1BTMB0GA1UdDgQWBBQ901Cl1qCt7vNKYApl0yHU+PjWDzAfBgNVHSMEGDAW +gBSxPsNpA/i/RwHUmCYaCALvY2QrwzANBgkqhkiG9w0BAQsFAAOCAQEAnbbQkIbh +hgLtxaDwNBx0wY12zIYKqPBKikLWP8ipTa18CK3mtlC4ohpNiAexKSHc59rGPCHg +4xFJcKx6HQGkyhE6V6t9VypAdP3THYUYUN9XR3WhfVUgLkc3UHKMf4Ib0mKPLQNa +2sPIoc4sUqIAY+tzunHISScjl2SFnjgOrWNoPLpSgVh5oywM395t6zHyuqB8bPEs +1OG9d4Q3A84ytciagRpKkk47RpqF/oOi+Z6Mo8wNXrM9zwR4jxQUezKcxwCmXMS1 +oVWNWlZopCJwqjyBcdmdqEU79OX2olHdx3ti6G8MdOu42vi/hw15UJGQmxg7kVkn +8TUoE6smftX3eg== +-----END CERTIFICATE----- +)EOF"; diff --git a/code/espurna/static/letsencrypt_isrgrootx1_pem.h b/code/espurna/static/letsencrypt_isrgrootx1_pem.h new file mode 100644 index 00000000..18d979a2 --- /dev/null +++ b/code/espurna/static/letsencrypt_isrgrootx1_pem.h @@ -0,0 +1,46 @@ +// ISRG Root X1 (self-signed) +// from https://letsencrypt.org/certs/isrgrootx1.pem.txt + +// Issuer: C = US, O = Internet Security Research Group, CN = ISRG Root X1 +// Validity +// Not Before: Jun 4 11:04:38 2015 GMT +// Not After : Jun 4 11:04:38 2035 GMT +// Subject: C = US, O = Internet Security Research Group, CN = ISRG Root X1 + +#pragma once + +#include + +const char PROGMEM _ssl_letsencrypt_isrg_x1_ca[]= R"EOF( +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- +)EOF"; diff --git a/code/espurna/terminal.ino b/code/espurna/terminal.ino index b92698aa..754f84e1 100644 --- a/code/espurna/terminal.ino +++ b/code/espurna/terminal.ino @@ -204,7 +204,28 @@ void _terminalInitCommand() { #if not SETTINGS_AUTOSAVE terminalRegisterCommand(F("SAVE"), [](Embedis* e) { eepromCommit(); - DEBUG_MSG_P(PSTR("\n+OK\n")); + terminalOK(); + }); + #endif + + #if SECURE_CLIENT == SECURE_CLIENT_BEARSSL + terminalRegisterCommand(F("MFLN.PROBE"), [](Embedis* e) { + if (e->argc != 3) { + terminalError(F("[url] [value]")); + return; + } + + URL _url(e->argv[1]); + uint16_t requested_mfln = atol(e->argv[2]); + + auto client = std::make_unique(); + client->setInsecure(); + + if (client->probeMaxFragmentLength(_url.host.c_str(), _url.port, requested_mfln)) { + terminalOK(); + } else { + terminalError(F("Buffer size not supported")); + } }); #endif diff --git a/code/espurna/utils.ino b/code/espurna/utils.ino index aefac90a..2c381b0d 100644 --- a/code/espurna/utils.ino +++ b/code/espurna/utils.ino @@ -68,6 +68,10 @@ String getEspurnaModules() { return FPSTR(espurna_modules); } +String getEspurnaOTAModules() { + return FPSTR(espurna_ota_modules); +} + #if SENSOR_SUPPORT String getEspurnaSensors() { return FPSTR(espurna_sensors); @@ -464,6 +468,7 @@ void info() { DEBUG_MSG_P(PSTR("[MAIN] Board: %s\n"), getBoardName().c_str()); DEBUG_MSG_P(PSTR("[MAIN] Support: %s\n"), getEspurnaModules().c_str()); + DEBUG_MSG_P(PSTR("[MAIN] OTA: %s\n"), getEspurnaOTAModules().c_str()); #if SENSOR_SUPPORT DEBUG_MSG_P(PSTR("[MAIN] Sensors: %s\n"), getEspurnaSensors().c_str()); #endif // SENSOR_SUPPORT @@ -504,8 +509,6 @@ void info() { // SSL // ----------------------------------------------------------------------------- -#if ASYNC_TCP_SSL_ENABLED - bool sslCheckFingerPrint(const char * fingerprint) { return (strlen(fingerprint) == 59); } @@ -541,8 +544,6 @@ bool sslFingerPrintChar(const char * fingerprint, char * destination) { } -#endif - // ----------------------------------------------------------------------------- // Reset // ----------------------------------------------------------------------------- diff --git a/code/espurna/web.ino b/code/espurna/web.ino index 52822296..d477908d 100644 --- a/code/espurna/web.ino +++ b/code/espurna/web.ino @@ -339,8 +339,10 @@ void _onUpgradeFile(AsyncWebServerRequest *request, String filename, size_t inde #endif } } else { - //Removed to avoid websocket ping back during upgrade (see #1574) - //DEBUG_MSG_P(PSTR("[UPGRADE] Progress: %u bytes\r"), index + len); + // Removed to avoid websocket ping back during upgrade (see #1574) + // TODO: implement as separate from debugging message + if (wsConnected()) return; + DEBUG_MSG_P(PSTR("[UPGRADE] Progress: %u bytes\r"), index + len); } }