- 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"; |