From 9e9d0d86c9bba128d4ffc36616ef589363315014 Mon Sep 17 00:00:00 2001 From: h2zero Date: Mon, 17 May 2021 21:46:20 -0600 Subject: [PATCH] Trigger firmware update via MQTT (ESP Only). If enabled in user_config.h this will trigger the OMG to update it's firmware from the link provided in the MQTT message. Example message: {\"version\":\"test\",\"password\":\"OMGPASSWORD\",\"url\":\"https://github.com/1technophile/OpenMQTTGateway/releases/download/v0.9.6/esp32dev-ble-cont-firmware.bin\"} If the firmware version in the message is not the same as the current OMG version the device will self update using the link in the url of the message. --- docs/use/gateway.md | 20 +++ main/Ota_github.h | 25 +++ main/User_config.h | 32 +++- main/main.ino | 136 +++++++++++++- main/zzHTTPUpdate.cpp | 409 ++++++++++++++++++++++++++++++++++++++++++ main/zzHTTPUpdate.h | 113 ++++++++++++ platformio.ini | 2 + 7 files changed, 728 insertions(+), 9 deletions(-) create mode 100644 main/Ota_github.h create mode 100644 main/zzHTTPUpdate.cpp create mode 100644 main/zzHTTPUpdate.h diff --git a/docs/use/gateway.md b/docs/use/gateway.md index 153ae5b23f..f46b1ce4d7 100644 --- a/docs/use/gateway.md +++ b/docs/use/gateway.md @@ -33,6 +33,26 @@ If you want the settings to be kept upon gateway restart, you can publish the co Auto discovery is enable by default on release binaries, on platformio (except for UNO). With Arduino IDE please read the [advanced configuration section](../upload/advanced-configuration#auto-discovery) of the documentation. ::: +# Firmware update from MQTT (ESP only) + +The gateway can be updated through an MQTT message by providing a JSON formatted message with a version number, OTA password (optional, see below), and URL to fetch the update from. + +To enable this functionality, `MQTT_HTTPS_FW_UPDATE` will need to be defined or the line that defines in in user_config.h will need to be uncommented. + +::: tip +If using an unsecure MQTT broker it is **highly recommended** to disable the password checking by setting the macro `MQTT_HTTPS_FW_UPDATE_USE_PASSWORD` to 0 (default is 1 (enabled)), otherwise a clear text password may be sent over the network. +::: + +### Example firmware update message: +``` +mosquitto_pub -t "home//commands/firmware_update" -m +'{ + "version": "test", + "password": "OTAPASSWORD", + "url": "https://github.com/1technophile/OpenMQTTGateway/releases/download/v0.9.6/esp32-m5stack-ble-firmware.bin" +}' +``` + # State LED usage The gateway can support up to 3 LED to display its operating state: diff --git a/main/Ota_github.h b/main/Ota_github.h new file mode 100644 index 0000000000..8cc835f871 --- /dev/null +++ b/main/Ota_github.h @@ -0,0 +1,25 @@ +const char* _github_cert PROGMEM = R"EOF(" +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- +")EOF"; \ No newline at end of file diff --git a/main/User_config.h b/main/User_config.h index 1ee35b1046..5e72e36994 100755 --- a/main/User_config.h +++ b/main/User_config.h @@ -74,11 +74,30 @@ const byte subnet[] = {255, 255, 255, 0}; #if defined(ESP8266) || defined(ESP32) // for nodemcu, weemos and esp8266 //# define ESPWifiManualSetup true //uncomment you don't want to use wifimanager for your credential settings on ESP +//# define MQTT_HTTPS_FW_UPDATE //uncomment to enable updating via mqtt message. #else // for arduino boards const byte ip[] = {192, 168, 1, 99}; const byte mac[] = {0xDE, 0xED, 0xBA, 0xFE, 0x54, 0x95}; //W5100 ethernet shield mac adress #endif +#ifdef MQTT_HTTPS_FW_UPDATE +# if defined(ESP8266) || defined(ESP32) +//If used, this should be set to the root CA certificate of the server hosting the firmware. +// The certificate must be in PEM ascii format +const char* https_fw_server_cert PROGMEM = R"EOF(" +-----BEGIN CERTIFICATE----- +... +-----END CERTIFICATE----- +")EOF"; +# define NTP_SERVER "pool.ntp.org" +# ifndef MQTT_HTTPS_FW_UPDATE_USE_PASSWORD +# define MQTT_HTTPS_FW_UPDATE_USE_PASSWORD 1 // Set this to 0 if not using TLS connection to MQTT broker to prevent clear text passwords being sent. +# endif +# else +# error "only ESP8266 and ESP32 support MQTT_HTTPS_FW_UPDATE" +# endif +#endif + //#define ESP32_ETHERNET=true // Uncomment to use Ethernet module on OLIMEX ESP32 Ethernet gateway #if defined(ESPWifiManualSetup) // for nodemcu, weemos and esp8266 @@ -148,22 +167,18 @@ const byte mac[] = {0xDE, 0xED, 0xBA, 0xFE, 0x54, 0x95}; //W5100 ethernet shield #ifdef SECURE_CONNECTION # if defined(ESP8266) || defined(ESP32) -# if defined(ESP32) -# define CERT_ATTRIBUTE -# elif defined(ESP8266) -# define CERT_ATTRIBUTE PROGMEM -# endif - // The root ca certificate used for validating the MQTT broker // The certificate must be in PEM ascii format -const char* certificate CERT_ATTRIBUTE = R"EOF(" +const char* certificate PROGMEM = R"EOF(" -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- ")EOF"; // specify a NTP server here or else the NTP server from DHCP is used +# ifndef NTP_SERVER //# define NTP_SERVER "pool.ntp.org" +# endif # else # error "only ESP8266 and ESP32 support SECURE_CONNECTION with TLS" # endif @@ -336,6 +351,7 @@ int lowpowermode = DEFAULT_LOW_POWER_MODE; #define subjectMQTTtoX "/commands/#" #define subjectMultiGTWKey "toMQTT" #define subjectGTWSendKey "MQTTto" +#define subjectFWUpdate "firmware_update" // key used for launching commands to the gateway #define restartCmd "restart" @@ -381,4 +397,4 @@ int lowpowermode = DEFAULT_LOW_POWER_MODE; # define LOG_LEVEL LOG_LEVEL_NOTICE #endif -#endif \ No newline at end of file +#endif diff --git a/main/main.ino b/main/main.ino index 7219bcbef0..d3f495375e 100644 --- a/main/main.ino +++ b/main/main.ino @@ -507,6 +507,9 @@ void connectMQTT() { #endif #ifdef ZgatewayIR client.subscribe(subjectMultiGTWIR); // subject on which other OMG will publish, this OMG will store these msg and by the way don't republish them if they have been already published +#endif +#ifdef MQTT_HTTPS_FW_UPDATE + client.subscribe(subjectFWUpdate); #endif Log.trace(F("Subscription OK to the subjects %s" CR), topic2); } @@ -539,8 +542,11 @@ void callback(char* topic, byte* payload, unsigned int length) { // Conversion to a printable string p[length] = '\0'; //launch the function to treat received data if this data concern OpenMQTTGateway - if ((strstr(topic, subjectMultiGTWKey) != NULL) || (strstr(topic, subjectGTWSendKey) != NULL)) + if ((strstr(topic, subjectMultiGTWKey) != NULL) || + (strstr(topic, subjectGTWSendKey) != NULL) || + (strstr(topic, subjectFWUpdate) != NULL)) receivingMQTT(topic, (char*)p); + // Free the memory free(p); } @@ -1576,6 +1582,9 @@ void receivingMQTT(char* topicOri, char* datacallback) { # ifdef ZgatewayRS232 MQTTtoRS232(topicOri, jsondata); # endif +# ifdef MQTT_HTTPS_FW_UPDATE + MQTTHttpsFWUpdate(topicOri, jsondata); +# endif #endif digitalWrite(LED_SEND, LED_SEND_ON); @@ -1613,6 +1622,131 @@ void receivingMQTT(char* topicOri, char* datacallback) { } } +#ifdef MQTT_HTTPS_FW_UPDATE +# ifndef NTP_SERVER +# error no NTP_SERVER defined +# endif +# include +# ifdef ESP32 +# include "Ota_github.h" +# include "zzHTTPUpdate.h" +# elif ESP8266 +# include +# endif +void MQTTHttpsFWUpdate(char* topicOri, JsonObject& HttpsFwUpdateData) { + if (strstr(topicOri, subjectFWUpdate) != NULL) { + const char* version = HttpsFwUpdateData["version"]; + if (version && ((strlen(version) != strlen(OMG_VERSION)) || strcmp(version, OMG_VERSION) != 0)) { + const char* url = HttpsFwUpdateData["url"]; + if (url) { + if (!strstr((url + (strlen(url) - 5)), ".bin")) { + Log.error(F("Invalid firmware extension" CR)); + return; + } + } else { + Log.error(F("Invalid URL" CR)); + return; + } + +# if MQTT_HTTPS_FW_UPDATE_USE_PASSWORD > 0 +# ifndef SECURE_CONNECTION +# warning using a password with an unsecure MQTT connection will send it as clear text!!! +# endif + const char* pwd = HttpsFwUpdateData["password"]; + if (pwd) { + if (strcmp(pwd, ota_password) != 0) { + Log.error(F("Invalid OTA password" CR)); + return; + } + } else { + Log.error(F("No password sent" CR)); + return; + } +# endif + + Log.warning(F("Starting firmware update" CR)); + +# if defined(ZgatewayBT) && defined(ESP32) + stopProcessing(); +# endif + + t_httpUpdate_return result = HTTP_UPDATE_FAILED; + if (strstr(url, "http:")) { + WiFiClient update_client; +# ifdef ESP32 + httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + result = httpUpdate.update(update_client, url); +# elif ESP8266 + ESPhttpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + result = ESPhttpUpdate.update(update_client, url); +# endif + + } else { + WiFiClientSecure update_client; +# ifdef SECURE_CONNECTION + client.disconnect(); + update_client = eClient; +# else + configTime(0, 0, NTP_SERVER); + time_t now = time(nullptr); + uint8_t count = 0; + Log.trace(F("Waiting for NTP time sync" CR)); + while ((now < 8 * 3600 * 2) && count++ < 60) { + vTaskDelay(500); + now = time(nullptr); + } + + if (count >= 60) { + Log.error(F("Unable to update - invalid time" CR)); +# if defined(ZgatewayBT) && defined(ESP32) + startProcessing(); +# endif + return; + } +# endif +# ifdef ESP32 + if (strstr(url, "github") != 0) { + update_client.setCACert(_github_cert); + } else { + update_client.setCACert(https_fw_server_cert); + } + update_client.setTimeout(12); + httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + result = httpUpdate.update(update_client, url); +# elif ESP8266 + update_client.setInsecure(); // TODO: replace with cert checking + update_client.setTimeout(12000); + ESPhttpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + result = ESPhttpUpdate.update(update_client, url); +# endif + } + + switch (result) { + case HTTP_UPDATE_FAILED: +# ifdef ESP32 + Log.error(F("HTTP_UPDATE_FAILED Error (%d): %s\n" CR), httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str()); +# elif ESP8266 + Log.error(F("HTTP_UPDATE_FAILED Error (%d): %s\n" CR), ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); +# endif + break; + + case HTTP_UPDATE_NO_UPDATES: + Log.notice(F("HTTP_UPDATE_NO_UPDATES" CR)); + break; + + case HTTP_UPDATE_OK: + Log.notice(F("HTTP_UPDATE_OK" CR)); + break; + } + +# if defined(ZgatewayBT) && defined(ESP32) + startProcessing(); +# endif + } + } +} +#endif + void MQTTtoSYS(char* topicOri, JsonObject& SYSdata) { // json object decoding if (cmpToMainTopic(topicOri, subjectMQTTtoSYSset)) { Log.trace(F("MQTTtoSYS json" CR)); diff --git a/main/zzHTTPUpdate.cpp b/main/zzHTTPUpdate.cpp new file mode 100644 index 0000000000..99456b1a32 --- /dev/null +++ b/main/zzHTTPUpdate.cpp @@ -0,0 +1,409 @@ +/** + * + * @file HTTPUpdate.cpp based om ESP8266HTTPUpdate.cpp + * @date 16.10.2018 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the ESP32 Http Updater. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifdef ESP32 +# include "zzHTTPUpdate.h" + +# include +# include // get running partition +# include + + +// To do extern "C" uint32_t _SPIFFS_start; +// To do extern "C" uint32_t _SPIFFS_end; + +HTTPUpdate::HTTPUpdate(void) + : _httpClientTimeout(8000), _ledPin(-1) { + _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS; +} + +HTTPUpdate::HTTPUpdate(int httpClientTimeout) + : _httpClientTimeout(httpClientTimeout), _ledPin(-1) { + _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS; +} + +HTTPUpdate::~HTTPUpdate(void) { +} + +HTTPUpdateResult HTTPUpdate::update(WiFiClient& client, const String& url, const String& currentVersion) { + HTTPClient http; + if (!http.begin(client, url)) { + return HTTP_UPDATE_FAILED; + } + return handleUpdate(http, currentVersion, false); +} + +HTTPUpdateResult HTTPUpdate::updateSpiffs(HTTPClient& httpClient, const String& currentVersion) { + return handleUpdate(httpClient, currentVersion, true); +} + +HTTPUpdateResult HTTPUpdate::updateSpiffs(WiFiClient& client, const String& url, const String& currentVersion) { + HTTPClient http; + if (!http.begin(client, url)) { + return HTTP_UPDATE_FAILED; + } + return handleUpdate(http, currentVersion, true); +} + +HTTPUpdateResult HTTPUpdate::update(HTTPClient& httpClient, + const String& currentVersion) { + return handleUpdate(httpClient, currentVersion, false); +} + +HTTPUpdateResult HTTPUpdate::update(WiFiClient& client, const String& host, uint16_t port, const String& uri, + const String& currentVersion) { + HTTPClient http; + if (!http.begin(client, host, port, uri)) { + return HTTP_UPDATE_FAILED; + } + return handleUpdate(http, currentVersion, false); +} + +/** + * return error code as int + * @return int error code + */ +int HTTPUpdate::getLastError(void) { + return _lastError; +} + +/** + * return error code as String + * @return String error + */ +String HTTPUpdate::getLastErrorString(void) { + if (_lastError == 0) { + return String(); // no error + } + + // error from Update class + if (_lastError > 0) { + StreamString error; + Update.printError(error); + error.trim(); // remove line ending + return String("Update error: ") + error; + } + + // error from http client + if (_lastError > -100) { + return String("HTTP error: ") + HTTPClient::errorToString(_lastError); + } + + switch (_lastError) { + case HTTP_UE_TOO_LESS_SPACE: + return "Not Enough space"; + case HTTP_UE_SERVER_NOT_REPORT_SIZE: + return "Server Did Not Report Size"; + case HTTP_UE_SERVER_FILE_NOT_FOUND: + return "File Not Found (404)"; + case HTTP_UE_SERVER_FORBIDDEN: + return "Forbidden (403)"; + case HTTP_UE_SERVER_WRONG_HTTP_CODE: + return "Wrong HTTP Code"; + case HTTP_UE_SERVER_FAULTY_MD5: + return "Wrong MD5"; + case HTTP_UE_BIN_VERIFY_HEADER_FAILED: + return "Verify Bin Header Failed"; + case HTTP_UE_BIN_FOR_WRONG_FLASH: + return "New Binary Does Not Fit Flash Size"; + case HTTP_UE_NO_PARTITION: + return "Partition Could Not be Found"; + } + + return String(); +} + +String getSketchSHA256() { + const size_t HASH_LEN = 32; // SHA-256 digest length + + uint8_t sha_256[HASH_LEN] = {0}; + + // get sha256 digest for running partition + if (esp_partition_get_sha256(esp_ota_get_running_partition(), sha_256) == 0) { + char buffer[2 * HASH_LEN + 1]; + + for (size_t index = 0; index < HASH_LEN; index++) { + uint8_t nibble = (sha_256[index] & 0xf0) >> 4; + buffer[2 * index] = nibble < 10 ? char(nibble + '0') : char(nibble - 10 + 'A'); + + nibble = sha_256[index] & 0x0f; + buffer[2 * index + 1] = nibble < 10 ? char(nibble + '0') : char(nibble - 10 + 'A'); + } + + buffer[2 * HASH_LEN] = '\0'; + + return String(buffer); + } else { + return String(); + } +} + +/** + * + * @param http HTTPClient * + * @param currentVersion const char * + * @return HTTPUpdateResult + */ +HTTPUpdateResult HTTPUpdate::handleUpdate(HTTPClient& http, const String& currentVersion, bool spiffs) { + HTTPUpdateResult ret = HTTP_UPDATE_FAILED; + + // use HTTP/1.0 for update since the update handler not support any transfer Encoding + http.useHTTP10(true); + http.setTimeout(_httpClientTimeout); + http.setFollowRedirects(_followRedirects); + http.setUserAgent("ESP32-http-Update"); + http.addHeader("Cache-Control", "no-cache"); + http.addHeader("x-ESP32-STA-MAC", WiFi.macAddress()); + http.addHeader("x-ESP32-AP-MAC", WiFi.softAPmacAddress()); + http.addHeader("x-ESP32-free-space", String(ESP.getFreeSketchSpace())); + http.addHeader("x-ESP32-sketch-size", String(ESP.getSketchSize())); + String sketchMD5 = ESP.getSketchMD5(); + if (sketchMD5.length() != 0) { + http.addHeader("x-ESP32-sketch-md5", sketchMD5); + } + // Add also a SHA256 + String sketchSHA256 = getSketchSHA256(); + if (sketchSHA256.length() != 0) { + http.addHeader("x-ESP32-sketch-sha256", sketchSHA256); + } + http.addHeader("x-ESP32-chip-size", String(ESP.getFlashChipSize())); + http.addHeader("x-ESP32-sdk-version", ESP.getSdkVersion()); + + if (spiffs) { + http.addHeader("x-ESP32-mode", "spiffs"); + } else { + http.addHeader("x-ESP32-mode", "sketch"); + } + + if (currentVersion && currentVersion[0] != 0x00) { + http.addHeader("x-ESP32-version", currentVersion); + } + + const char* headerkeys[] = {"x-MD5"}; + size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*); + + // track these headers + http.collectHeaders(headerkeys, headerkeyssize); + + int code = http.GET(); + int len = http.getSize(); + + if (code <= 0) { + log_e("HTTP error: %s\n", http.errorToString(code).c_str()); + _lastError = code; + http.end(); + return HTTP_UPDATE_FAILED; + } + + log_d("Header read fin.\n"); + log_d("Server header:\n"); + log_d(" - code: %d\n", code); + log_d(" - len: %d\n", len); + + if (http.hasHeader("x-MD5")) { + log_d(" - MD5: %s\n", http.header("x-MD5").c_str()); + } + + log_d("ESP32 info:\n"); + log_d(" - free Space: %d\n", ESP.getFreeSketchSpace()); + log_d(" - current Sketch Size: %d\n", ESP.getSketchSize()); + + if (currentVersion && currentVersion[0] != 0x00) { + log_d(" - current version: %s\n", currentVersion.c_str()); + } + + switch (code) { + case HTTP_CODE_OK: ///< OK (Start Update) + if (len > 0) { + bool startUpdate = true; + if (spiffs) { + const esp_partition_t* _partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, NULL); + if (!_partition) { + _lastError = HTTP_UE_NO_PARTITION; + return HTTP_UPDATE_FAILED; + } + + if (len > _partition->size) { + log_e("spiffsSize to low (%d) needed: %d\n", _partition->size, len); + startUpdate = false; + } + } else { + int sketchFreeSpace = ESP.getFreeSketchSpace(); + if (!sketchFreeSpace) { + _lastError = HTTP_UE_NO_PARTITION; + return HTTP_UPDATE_FAILED; + } + + if (len > sketchFreeSpace) { + log_e("FreeSketchSpace to low (%d) needed: %d\n", sketchFreeSpace, len); + startUpdate = false; + } + } + + if (!startUpdate) { + _lastError = HTTP_UE_TOO_LESS_SPACE; + ret = HTTP_UPDATE_FAILED; + } else { + WiFiClient* tcp = http.getStreamPtr(); + + // To do? WiFiUDP::stopAll(); + // To do? WiFiClient::stopAllExcept(tcp); + + delay(100); + + int command; + + if (spiffs) { + command = U_SPIFFS; + log_d("runUpdate spiffs...\n"); + } else { + command = U_FLASH; + log_d("runUpdate flash...\n"); + } + + if (!spiffs) { + /* To do + uint8_t buf[4]; + if(tcp->peekBytes(&buf[0], 4) != 4) { + log_e("peekBytes magic header failed\n"); + _lastError = HTTP_UE_BIN_VERIFY_HEADER_FAILED; + http.end(); + return HTTP_UPDATE_FAILED; + } +*/ + + // check for valid first magic byte + // if(buf[0] != 0xE9) { + if (tcp->peek() != 0xE9) { + log_e("Magic header does not start with 0xE9\n"); + _lastError = HTTP_UE_BIN_VERIFY_HEADER_FAILED; + http.end(); + return HTTP_UPDATE_FAILED; + } + /* To do + uint32_t bin_flash_size = ESP.magicFlashChipSize((buf[3] & 0xf0) >> 4); + + // check if new bin fits to SPI flash + if(bin_flash_size > ESP.getFlashChipRealSize()) { + log_e("New binary does not fit SPI Flash size\n"); + _lastError = HTTP_UE_BIN_FOR_WRONG_FLASH; + http.end(); + return HTTP_UPDATE_FAILED; + } +*/ + } + if (runUpdate(*tcp, len, http.header("x-MD5"), command)) { + ret = HTTP_UPDATE_OK; + log_d("Update ok\n"); + http.end(); + + if (_rebootOnUpdate && !spiffs) { + ESP.restart(); + } + + } else { + ret = HTTP_UPDATE_FAILED; + log_e("Update failed\n"); + } + } + } else { + _lastError = HTTP_UE_SERVER_NOT_REPORT_SIZE; + ret = HTTP_UPDATE_FAILED; + log_e("Content-Length was 0 or wasn't set by Server?!\n"); + } + break; + case HTTP_CODE_NOT_MODIFIED: + ///< Not Modified (No updates) + ret = HTTP_UPDATE_NO_UPDATES; + break; + case HTTP_CODE_NOT_FOUND: + _lastError = HTTP_UE_SERVER_FILE_NOT_FOUND; + ret = HTTP_UPDATE_FAILED; + break; + case HTTP_CODE_FORBIDDEN: + _lastError = HTTP_UE_SERVER_FORBIDDEN; + ret = HTTP_UPDATE_FAILED; + break; + default: + _lastError = HTTP_UE_SERVER_WRONG_HTTP_CODE; + ret = HTTP_UPDATE_FAILED; + log_e("HTTP Code is (%d)\n", code); + break; + } + + http.end(); + return ret; +} + +/** + * write Update to flash + * @param in Stream& + * @param size uint32_t + * @param md5 String + * @return true if Update ok + */ +bool HTTPUpdate::runUpdate(Stream& in, uint32_t size, String md5, int command) { + StreamString error; + + if (!Update.begin(size, command, _ledPin, _ledOn)) { + _lastError = Update.getError(); + Update.printError(error); + error.trim(); // remove line ending + log_e("Update.begin failed! (%s)\n", error.c_str()); + return false; + } + + if (md5.length()) { + if (!Update.setMD5(md5.c_str())) { + _lastError = HTTP_UE_SERVER_FAULTY_MD5; + log_e("Update.setMD5 failed! (%s)\n", md5.c_str()); + return false; + } + } + + // To do: the SHA256 could be checked if the server sends it + + if (Update.writeStream(in) != size) { + _lastError = Update.getError(); + Update.printError(error); + error.trim(); // remove line ending + log_e("Update.writeStream failed! (%s)\n", error.c_str()); + return false; + } + + if (!Update.end()) { + _lastError = Update.getError(); + Update.printError(error); + error.trim(); // remove line ending + log_e("Update.end failed! (%s)\n", error.c_str()); + return false; + } + + return true; +} + +# if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_HTTPUPDATE) +HTTPUpdate httpUpdate; +# endif +#endif \ No newline at end of file diff --git a/main/zzHTTPUpdate.h b/main/zzHTTPUpdate.h new file mode 100644 index 0000000000..2ae68237d1 --- /dev/null +++ b/main/zzHTTPUpdate.h @@ -0,0 +1,113 @@ +/** + * + * @file HTTPUpdate.h based on ESP8266HTTPUpdate.h + * @date 16.10.2018 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the ESP32 Http Updater. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifdef ESP32 +# ifndef ___zzHTTP_UPDATE_H___ +# define ___zzHTTP_UPDATE_H___ + +# include +# include +# include +# include +# include +# include + +/// note we use HTTP client errors too so we start at 100 +# define HTTP_UE_TOO_LESS_SPACE (-100) +# define HTTP_UE_SERVER_NOT_REPORT_SIZE (-101) +# define HTTP_UE_SERVER_FILE_NOT_FOUND (-102) +# define HTTP_UE_SERVER_FORBIDDEN (-103) +# define HTTP_UE_SERVER_WRONG_HTTP_CODE (-104) +# define HTTP_UE_SERVER_FAULTY_MD5 (-105) +# define HTTP_UE_BIN_VERIFY_HEADER_FAILED (-106) +# define HTTP_UE_BIN_FOR_WRONG_FLASH (-107) +# define HTTP_UE_NO_PARTITION (-108) + +enum HTTPUpdateResult { + HTTP_UPDATE_FAILED, + HTTP_UPDATE_NO_UPDATES, + HTTP_UPDATE_OK +}; + +typedef HTTPUpdateResult t_httpUpdate_return; // backward compatibility + +class HTTPUpdate { +public: + HTTPUpdate(void); + HTTPUpdate(int httpClientTimeout); + ~HTTPUpdate(void); + + void rebootOnUpdate(bool reboot) { + _rebootOnUpdate = reboot; + } + + /** + * set redirect follow mode. See `followRedirects_t` enum for avaliable modes. + * @param follow + */ + void setFollowRedirects(followRedirects_t follow) { + _followRedirects = follow; + } + + void setLedPin(int ledPin = -1, uint8_t ledOn = HIGH) { + _ledPin = ledPin; + _ledOn = ledOn; + } + + t_httpUpdate_return update(WiFiClient& client, const String& url, const String& currentVersion = ""); + + t_httpUpdate_return update(WiFiClient& client, const String& host, uint16_t port, const String& uri = "/", + const String& currentVersion = ""); + + t_httpUpdate_return updateSpiffs(WiFiClient& client, const String& url, const String& currentVersion = ""); + + t_httpUpdate_return update(HTTPClient& httpClient, + const String& currentVersion = ""); + + t_httpUpdate_return updateSpiffs(HTTPClient& httpClient, const String& currentVersion = ""); + + int getLastError(void); + String getLastErrorString(void); + +protected: + t_httpUpdate_return handleUpdate(HTTPClient& http, const String& currentVersion, bool spiffs = false); + bool runUpdate(Stream& in, uint32_t size, String md5, int command = U_FLASH); + + int _lastError; + bool _rebootOnUpdate = true; + +private: + int _httpClientTimeout; + followRedirects_t _followRedirects; + + int _ledPin; + uint8_t _ledOn; +}; + +# if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_HTTPUPDATE) +extern HTTPUpdate httpUpdate; +# endif + +# endif /* ___HTTP_UPDATE_H___ */ +#endif \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index d45a6d0831..8d720fce60 100644 --- a/platformio.ini +++ b/platformio.ini @@ -830,6 +830,7 @@ lib_deps = build_flags = ${com-esp.build_flags} '-DSECURE_CONNECTION' + '-DMQTT_HTTPS_FW_UPDATE' '-DGateway_Name="OpenMQTTGateway_TEST_TLS"' board_build.flash_mode = dout @@ -842,6 +843,7 @@ lib_deps = build_flags = ${com-esp.build_flags} '-DSECURE_CONNECTION' + '-DMQTT_HTTPS_FW_UPDATE' '-DGateway_Name="OpenMQTTGateway_TEST_TLS"' board_build.flash_mode = dout