Files
espurna/code/espurna/ota_httpupdate.cpp
Maxim Prokhorov 2598c4a77b tspk: pass sensor data as string, rework http parser
schedule immediately with module functions, don't wait
more flash-strings in the module and utility functions

support rare condition when body cannot be sent on connect, use poll to send it
don't wait for header line in body, and bail out when done parsing
make sure data is copied into the async variant, and remains there for
the duration of the connection

general async api is... more complicated that it needs to be
until wolfssl / brssl port is here, though, there is no other choice
secureclient config becomes a simple struct, pending further rework
2022-05-25 03:50:17 +03:00

207 lines
5.2 KiB
C++

/*
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
// -----------------------------------------------------------------------------
#include "espurna.h"
#if OTA_CLIENT == OTA_CLIENT_HTTPUPDATE
#include "mqtt.h"
#include "ota.h"
#include "system.h"
#include "terminal.h"
#include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>
#include "libs/URL.h"
#include "libs/TypeChecks.h"
#include "libs/SecureClientHelpers.h"
#if SECURE_CLIENT != SECURE_CLIENT_NONE
#include <WiFiClientSecure.h>
namespace {
#if OTA_SECURE_CLIENT_INCLUDE_CA
#include "static/ota_client_trusted_root_ca.h"
#else
#include "static/digicert_evroot_pem.h"
#define _ota_client_trusted_root_ca _ssl_digicert_ev_root_ca
#endif
#endif // SECURE_CLIENT != SECURE_CLIENT_NONE
} // namespace
// -----------------------------------------------------------------------------
namespace ota {
namespace httpupdate {
namespace {
// -----------------------------------------------------------------------------
// Generic update methods
// -----------------------------------------------------------------------------
void run(WiFiClient* client, const String& url) {
// Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade
// Must happen right now, since HTTP updater will block until it's done
eepromRotate(false);
DEBUG_MSG_P(PSTR("[OTA] Downloading %s ...\n"), url.c_str());
ESPhttpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
ESPhttpUpdate.rebootOnUpdate(false);
t_httpUpdate_return result = HTTP_UPDATE_NO_UPDATES;
result = ESPhttpUpdate.update(*client, url);
switch (result) {
case HTTP_UPDATE_FAILED:
DEBUG_MSG_P(PSTR("[OTA] Update failed (error %d): %s\n"), ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
break;
case HTTP_UPDATE_NO_UPDATES:
DEBUG_MSG_P(PSTR("[OTA] No updates"));
break;
case HTTP_UPDATE_OK:
DEBUG_MSG_P(PSTR("[OTA] Done, restarting..."));
prepareReset(CustomResetReason::Ota);
return;
}
eepromRotate(true);
}
void clientFromHttp(const String& url) {
auto client = std::make_unique<WiFiClient>();
run(client.get(), url);
}
#if SECURE_CLIENT == SECURE_CLIENT_BEARSSL
void clientFromHttps(const String& url, SecureClientConfig& config) {
// Check for NTP early to avoid constructing SecureClient prematurely
const int check = config.on_check();
if (!ntpSynced() && (check == SECURE_CLIENT_CHECK_CA)) {
DEBUG_MSG_P(PSTR("[OTA] Time not synced! Cannot use CA validation\n"));
return;
}
auto client = std::make_unique<SecureClient>(config);
if (!client->beforeConnected()) {
return;
}
run(&client->get(), url);
}
static SecureClientConfig defaultSecureClientConfig {
.tag = "OTA",
.on_check = []() -> int {
return getSetting("otaScCheck", OTA_SECURE_CLIENT_CHECK);
},
.on_certificate = []() -> PGM_P {
return _ota_client_trusted_root_ca;
},
.on_fingerprint = []() -> String {
return getSetting("otaFP", OTA_FINGERPRINT);
},
.on_mfln = []() -> uint16_t {
return getSetting("otaScMFLN", OTA_SECURE_CLIENT_MFLN);
},
.debug = true,
};
void clientFromHttps(const String& url) {
clientFromHttps(url, defaultSecureClientConfig);
}
#endif // SECURE_CLIENT_BEARSSL
void clientFromUrl(const String& url) {
if (url.startsWith("http://")) {
clientFromHttp(url);
return;
}
#if SECURE_CLIENT != SECURE_CLIENT_NONE
else if (url.startsWith("https://")) {
clientFromHttps(url);
return;
}
#endif
DEBUG_MSG_P(PSTR("[OTA] Incorrect URL specified\n"));
}
#if TERMINAL_SUPPORT
void terminalCommands() {
terminalRegisterCommand(F("OTA"), [](::terminal::CommandContext&& ctx) {
if (ctx.argv.size() == 2) {
clientFromUrl(ctx.argv[1]);
terminalOK(ctx);
return;
}
terminalError(ctx, F("OTA <url>"));
});
}
#endif // TERMINAL_SUPPORT
#if (MQTT_SUPPORT && OTA_MQTT_SUPPORT)
void mqttCallback(unsigned int type, const char* topic, char* payload) {
if (type == MQTT_CONNECT_EVENT) {
mqttSubscribe(MQTT_TOPIC_OTA);
return;
}
if (type == MQTT_MESSAGE_EVENT) {
const String t = mqttMagnitude(topic);
static bool busy { false };
if (!busy && t.equals(MQTT_TOPIC_OTA)) {
DEBUG_MSG_P(PSTR("[OTA] Queuing from URL: %s\n"), payload);
const String url(payload);
schedule_function([url]() {
clientFromUrl(url);
busy = false;
});
}
return;
}
}
#endif // MQTT_SUPPORT
} // namespace
} // namespace httpupdate
} // namespace ota
// -----------------------------------------------------------------------------
void otaClientSetup() {
moveSetting("otafp", "otaFP");
#if TERMINAL_SUPPORT
ota::httpupdate::terminalCommands();
#endif
#if (MQTT_SUPPORT && OTA_MQTT_SUPPORT)
mqttRegister(ota::httpupdate::mqttCallback);
#endif
}
#endif // OTA_CLIENT == OTA_CLIENT_HTTPUPDATE