- add httpupdate ota impelementation - move async ota into a separate module - support bearssl & axtls through wificlientsecure - allow to disable arduino ide ota modulemaster
@ -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; | |||||
} | |||||
} |
@ -1,298 +0,0 @@ | |||||
/* | |||||
OTA MODULE | |||||
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com> | |||||
*/ | |||||
#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 <ESPAsyncTCP.h> | |||||
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(); | |||||
} |
@ -0,0 +1,101 @@ | |||||
/* | |||||
ARDUINO OTA MODULE | |||||
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com> | |||||
*/ | |||||
#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 |
@ -0,0 +1,227 @@ | |||||
/* | |||||
ASYNC CLIENT OTA MODULE | |||||
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com> | |||||
*/ | |||||
#if OTA_CLIENT == OTA_CLIENT_ASYNCTCP | |||||
// ----------------------------------------------------------------------------- | |||||
// Terminal OTA command | |||||
// ----------------------------------------------------------------------------- | |||||
#if TERMINAL_SUPPORT || OTA_MQTT_SUPPORT | |||||
#include <ESPAsyncTCP.h> | |||||
#include "libs/URL.h" | |||||
std::unique_ptr<AsyncClient> _ota_client = nullptr; | |||||
unsigned long _ota_size = 0; | |||||
bool _ota_connected = false; | |||||
std::unique_ptr<URL> _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>(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<AsyncClient>(); | |||||
} | |||||
_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 <url>")); | |||||
} 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 |
@ -0,0 +1,252 @@ | |||||
/* | |||||
HTTP(s) OTA MODULE | |||||
Copyright (C) 2019 by Maxim Prokhorov <prokhorov dot max at outlook dot com> | |||||
*/ | |||||
// ----------------------------------------------------------------------------- | |||||
// OTA by using Core's HTTP(s) updater | |||||
// ----------------------------------------------------------------------------- | |||||
#if OTA_CLIENT == OTA_CLIENT_HTTPUPDATE | |||||
#include <memory> | |||||
#include <ESP8266HTTPClient.h> | |||||
#include <ESP8266httpUpdate.h> | |||||
#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<WiFiClient>(); | |||||
_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<BearSSL::WiFiClientSecure>(); | |||||
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 <url>")); | |||||
} 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 |
@ -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 <pgmspace.h> | |||||
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"; |
@ -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 <pgmspace.h> | |||||
const char PROGMEM _ssl_letsencrypt_isrg_x1_ca[]= R"EOF( | |||||
-----BEGIN CERTIFICATE----- | |||||
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 | |||||
-----END CERTIFICATE----- | |||||
)EOF"; |