/* 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