diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 8660f5707..8a24fdce9 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -179,7 +179,8 @@ jobs: # Install ESP32 platform via Boards Manager - name: esp32:esp32 source-url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json - libraries: + libraries: | + - name: Arduino_ESP32_OTA sketch-paths: steps: diff --git a/extras/tools/bin2ota.py b/extras/tools/bin2ota.py index a99130ebe..7ec4c6846 100755 --- a/extras/tools/bin2ota.py +++ b/extras/tools/bin2ota.py @@ -30,6 +30,9 @@ magic_number = 0x2341025F.to_bytes(4,byteorder='little') elif board == "OPTA": magic_number = 0x23410064.to_bytes(4,byteorder='little') +# Magic number for all ESP32 boards not related to (VID/PID) +elif board == "ESP32": + magic_number = 0x45535033.to_bytes(4,byteorder='little') else: print ("Error,", board, "is not a supported board type") sys.exit() diff --git a/src/AIoTC_Config.h b/src/AIoTC_Config.h index 36ece27bd..b1ba04082 100644 --- a/src/AIoTC_Config.h +++ b/src/AIoTC_Config.h @@ -108,7 +108,11 @@ #define OTA_STORAGE_PORTENTA_QSPI (0) #endif -#if (OTA_STORAGE_SFU || OTA_STORAGE_SSU || OTA_STORAGE_SNU || OTA_STORAGE_PORTENTA_QSPI) && !defined(ARDUINO_AVR_UNO_WIFI_REV2) +#if defined(ARDUINO_ARCH_ESP32) + #define OTA_STORAGE_ESP (1) +#endif + +#if (OTA_STORAGE_SFU || OTA_STORAGE_SSU || OTA_STORAGE_SNU || OTA_STORAGE_PORTENTA_QSPI || OTA_STORAGE_ESP) && !defined(ARDUINO_AVR_UNO_WIFI_REV2) #define OTA_ENABLED (1) #else #define OTA_ENABLED (0) diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index 3c11dea8f..62d2d7025 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -23,6 +23,7 @@ #ifdef HAS_TCP #include + #ifdef BOARD_HAS_ECCX08 #include "tls/BearSSLTrustAnchors.h" #include "tls/utility/CryptoUtil.h" @@ -34,32 +35,18 @@ #endif #ifdef BOARD_HAS_OFFLOADED_ECCX08 -#include -#include "tls/utility/CryptoUtil.h" + #include + #include "tls/utility/CryptoUtil.h" #endif -#ifdef BOARD_STM32H7 -# include "tls/utility/SHA256.h" -# include -# include +#if OTA_ENABLED + #include "utility/ota/OTA.h" #endif -#include "utility/ota/OTA.h" -#include "utility/ota/FlashSHA256.h" #include #include "cbor/CBOREncoder.h" - #include "utility/watchdog/Watchdog.h" - -/****************************************************************************** - * EXTERN - ******************************************************************************/ - -#ifdef BOARD_STM32H7 -extern RTC_HandleTypeDef RTCHandle; -#endif - /****************************************************************************** LOCAL MODULE FUNCTIONS ******************************************************************************/ @@ -151,61 +138,8 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, #endif /* AVR */ #if OTA_ENABLED && !defined(__AVR__) -#if defined(BOARD_STM32H7) - /* The length of the application can be retrieved the same way it was - * communicated to the bootloader, that is by writing to the non-volatile - * storage registers of the RTC. - */ - SHA256 sha256; - uint32_t const app_start = 0x8040000; - uint32_t const app_size = HAL_RTCEx_BKUPRead(&RTCHandle, RTC_BKP_DR3); - - sha256.begin(); - uint32_t b = 0; - uint32_t bytes_read = 0; for(uint32_t a = app_start; - bytes_read < app_size; - bytes_read += sizeof(b), a += sizeof(b)) - { - /* Read the next chunk of memory. */ - memcpy(&b, reinterpret_cast(a), sizeof(b)); - /* Feed it to SHA256. */ - sha256.update(reinterpret_cast(&b), sizeof(b)); - } - /* Retrieve the final hash string. */ - uint8_t sha256_hash[SHA256::HASH_SIZE] = {0}; - sha256.finalize(sha256_hash); - String sha256_str; - std::for_each(sha256_hash, - sha256_hash + SHA256::HASH_SIZE, - [&sha256_str](uint8_t const elem) - { - char buf[4]; - snprintf(buf, 4, "%02X", elem); - sha256_str += buf; - }); - DEBUG_VERBOSE("SHA256: %d bytes (of %d) read", bytes_read, app_size); -#elif defined(ARDUINO_ARCH_SAMD) - /* Calculate the SHA256 checksum over the firmware stored in the flash of the - * MCU. Note: As we don't know the length per-se we read chunks of the flash - * until we detect one containing only 0xFF (= flash erased). This only works - * for firmware updated via OTA and second stage bootloaders (SxU family) - * because only those erase the complete flash before performing an update. - * Since the SHA256 firmware image is only required for the cloud servers to - * perform a version check after the OTA update this is a acceptable trade off. - * The bootloader is excluded from the calculation and occupies flash address - * range 0 to 0x2000, total flash size of 0x40000 bytes (256 kByte). - */ - String const sha256_str = FlashSHA256::calc(0x2000, 0x40000 - 0x2000); -#elif defined(ARDUINO_NANO_RP2040_CONNECT) - /* The maximum size of a RP2040 OTA update image is 1 MByte (that is 1024 * - * 1024 bytes or 0x100'000 bytes). - */ - String const sha256_str = FlashSHA256::calc(XIP_BASE, 0x100000); -#else -# error "No method for SHA256 checksum calculation over application image defined for this architecture." -#endif - DEBUG_VERBOSE("SHA256: HASH(%d) = %s", strlen(sha256_str.c_str()), sha256_str.c_str()); - _ota_img_sha256 = sha256_str; + _ota_img_sha256 = getOTAImageSHA256(); + DEBUG_VERBOSE("SHA256: HASH(%d) = %s", strlen(_ota_img_sha256.c_str()), _ota_img_sha256.c_str()); #endif /* OTA_ENABLED */ #if defined(BOARD_HAS_ECCX08) || defined(BOARD_HAS_OFFLOADED_ECCX08) || defined(BOARD_HAS_SE050) @@ -300,6 +234,11 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, _ota_cap = true; #endif +#if defined(ARDUINO_ARCH_ESP32) && OTA_ENABLED + /* NOTE: here is possible to check if current partition scheme is OTA compatible */ + _ota_cap = true; +#endif + #ifdef BOARD_HAS_OFFLOADED_ECCX08 if (String(WiFi.firmwareVersion()) < String("1.4.4")) { DEBUG_ERROR("ArduinoIoTCloudTCP::%s In order to connect to Arduino IoT Cloud, NINA firmware needs to be >= 1.4.4, current %s", __FUNCTION__, WiFi.firmwareVersion()); @@ -840,6 +779,25 @@ void ArduinoIoTCloudTCP::onOTARequest() bool const use_ethernet = _connection->getInterface() == NetworkAdapter::ETHERNET ? true : false; _ota_error = portenta_h7_onOTARequest(_ota_url.c_str(), use_ethernet); #endif + +#ifdef ARDUINO_ARCH_ESP32 + _ota_error = esp32_onOTARequest(_ota_url.c_str()); +#endif +} + +String ArduinoIoTCloudTCP::getOTAImageSHA256() +{ +#if defined (ARDUINO_ARCH_SAMD) + return samd_getOTAImageSHA256(); +#elif defined (ARDUINO_NANO_RP2040_CONNECT) + return rp2040_connect_getOTAImageSHA256(); +#elif defined (BOARD_STM32H7) + return portenta_h7_getOTAImageSHA256(); +#elif defined (ARDUINO_ARCH_ESP32) + return esp32_getOTAImageSHA256(); +#else + # error "No method for SHA256 checksum calculation over application image defined for this architecture." +#endif } #endif diff --git a/src/ArduinoIoTCloudTCP.h b/src/ArduinoIoTCloudTCP.h index 28c6315be..3b7d431da 100644 --- a/src/ArduinoIoTCloudTCP.h +++ b/src/ArduinoIoTCloudTCP.h @@ -202,6 +202,7 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass #if OTA_ENABLED void onOTARequest(); + String getOTAImageSHA256(); void sendDevicePropertyToCloud(String const name); #endif diff --git a/src/tls/bearssl/dec32be.c b/src/tls/bearssl/dec32be.c index 064f9bf98..0b8b05a5e 100644 --- a/src/tls/bearssl/dec32be.c +++ b/src/tls/bearssl/dec32be.c @@ -23,7 +23,7 @@ */ #include -#if defined(BOARD_HAS_ECCX08) || defined(BOARD_HAS_OFFLOADED_ECCX08) || defined(BOARD_HAS_SE050) +#if defined(BOARD_HAS_ECCX08) || defined(BOARD_HAS_OFFLOADED_ECCX08) || defined(BOARD_HAS_SE050) || defined(ARDUINO_ARCH_ESP32) #include "inner.h" diff --git a/src/tls/bearssl/enc32be.c b/src/tls/bearssl/enc32be.c index b6be4e13f..a00d96123 100644 --- a/src/tls/bearssl/enc32be.c +++ b/src/tls/bearssl/enc32be.c @@ -23,7 +23,7 @@ */ #include -#if defined(BOARD_HAS_ECCX08) || defined(BOARD_HAS_OFFLOADED_ECCX08) || defined(BOARD_HAS_SE050) +#if defined(BOARD_HAS_ECCX08) || defined(BOARD_HAS_OFFLOADED_ECCX08) || defined(BOARD_HAS_SE050) || defined(ARDUINO_ARCH_ESP32) #include "inner.h" diff --git a/src/tls/bearssl/sha2small.c b/src/tls/bearssl/sha2small.c index 789623de0..02283f05c 100644 --- a/src/tls/bearssl/sha2small.c +++ b/src/tls/bearssl/sha2small.c @@ -23,7 +23,7 @@ */ #include -#if defined(BOARD_HAS_ECCX08) || defined(BOARD_HAS_OFFLOADED_ECCX08) || defined(BOARD_HAS_SE050) +#if defined(BOARD_HAS_ECCX08) || defined(BOARD_HAS_OFFLOADED_ECCX08) || defined(BOARD_HAS_SE050) || defined(ARDUINO_ARCH_ESP32) #include "inner.h" diff --git a/src/utility/ota/OTA-esp32.cpp b/src/utility/ota/OTA-esp32.cpp new file mode 100644 index 000000000..97da4041c --- /dev/null +++ b/src/utility/ota/OTA-esp32.cpp @@ -0,0 +1,123 @@ +/* + This file is part of ArduinoIoTCloud. + + Copyright 2020 ARDUINO SA (http://www.arduino.cc/) + + This software is released under the GNU General Public License version 3, + which covers the main part of arduino-cli. + The terms of this license can be found at: + https://www.gnu.org/licenses/gpl-3.0.en.html + + You can be released from the requirements of the above licenses by purchasing + a commercial license. Buying such a license is mandatory if you want to modify or + otherwise use the software for commercial activities involving the Arduino + software without disclosing the source code of your own applications. To purchase + a commercial license, send an email to license@arduino.cc. +*/ + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include + +#if defined ARDUINO_ARCH_ESP32 && OTA_ENABLED + +#include "OTA.h" +#include +#include +#include "tls/utility/SHA256.h" + +#include + +/****************************************************************************** + * FUNCTION DEFINITION + ******************************************************************************/ + +int esp32_onOTARequest(char const * ota_url) +{ + Arduino_ESP32_OTA::Error ota_err = Arduino_ESP32_OTA::Error::None; + Arduino_ESP32_OTA ota; + + /* Initialize the board for OTA handling. */ + if ((ota_err = ota.begin()) != Arduino_ESP32_OTA::Error::None) + { + DEBUG_ERROR("Arduino_ESP32_OTA::begin() failed with %d", static_cast(ota_err)); + return static_cast(ota_err); + } + + /* Download the OTA file from the web storage location. */ + int const ota_download = ota.download(ota_url); + if (ota_download <= 0) + { + DEBUG_ERROR("Arduino_ESP_OTA::download() failed with %d", ota_download); + return ota_download; + } + DEBUG_VERBOSE("Arduino_ESP_OTA::download() %d bytes downloaded", static_cast(ota_download)); + + /* Verify update integrity and apply */ + if ((ota_err = ota.update()) != Arduino_ESP32_OTA::Error::None) + { + DEBUG_ERROR("Arduino_ESP_OTA::update() failed with %d", static_cast(ota_err)); + return static_cast(ota_err); + } + + /* Perform the reset to reboot */ + ota.reset(); + + return static_cast(OTAError::None); +} + +String esp32_getOTAImageSHA256() +{ + const esp_partition_t *running = esp_ota_get_running_partition(); + if (!running) { + DEBUG_ERROR("ESP32::SHA256 Running partition could not be found"); + return String(); + } + + uint8_t *b = (uint8_t*)malloc(SPI_FLASH_SEC_SIZE); + if(b == nullptr) { + DEBUG_ERROR("ESP32::SHA256 Not enough memory to allocate buffer"); + return String(); + } + + SHA256 sha256; + uint32_t const app_start = running->address; + uint32_t const app_size = ESP.getSketchSize(); + uint32_t read_bytes = 0; + + sha256.begin(); + for(uint32_t a = app_start; read_bytes < app_size; ) + { + /* Check if we are reading last sector and compute used size */ + uint32_t const read_size = read_bytes + SPI_FLASH_SEC_SIZE < app_size ? SPI_FLASH_SEC_SIZE : app_size - read_bytes; + + /* Use always 4 bytes aligned reads */ + if (!ESP.flashRead(a, reinterpret_cast(b), (read_size + 3) & ~3)) { + DEBUG_ERROR("ESP32::SHA256 Could not read data from flash"); + return String(); + } + sha256.update(b, read_size); + a += read_size; + read_bytes += read_size; + } + free(b); + + /* Retrieve the final hash string. */ + uint8_t sha256_hash[SHA256::HASH_SIZE] = {0}; + sha256.finalize(sha256_hash); + String sha256_str; + std::for_each(sha256_hash, + sha256_hash + SHA256::HASH_SIZE, + [&sha256_str](uint8_t const elem) + { + char buf[4]; + snprintf(buf, 4, "%02X", elem); + sha256_str += buf; + }); + DEBUG_VERBOSE("SHA256: %d bytes (of %d) read", read_bytes, app_size); + return sha256_str; +} + +#endif /* ARDUINO_ARCH_ESP32 */ diff --git a/src/utility/ota/OTA-nano-rp2040.cpp b/src/utility/ota/OTA-nano-rp2040.cpp index 48071be7c..acdf6e8ec 100644 --- a/src/utility/ota/OTA-nano-rp2040.cpp +++ b/src/utility/ota/OTA-nano-rp2040.cpp @@ -32,6 +32,7 @@ #include "mbed.h" #include "FATFileSystem.h" #include "FlashIAPBlockDevice.h" +#include "utility/ota/FlashSHA256.h" /****************************************************************************** * FUNCTION DEFINITION @@ -249,4 +250,12 @@ int rp2040_connect_onOTARequest(char const * ota_url) return static_cast(OTAError::None); } +String rp2040_connect_getOTAImageSHA256() +{ + /* The maximum size of a RP2040 OTA update image is 1 MByte (that is 1024 * + * 1024 bytes or 0x100'000 bytes). + */ + return FlashSHA256::calc(XIP_BASE, 0x100000); +} + #endif /* ARDUINO_NANO_RP2040_CONNECT */ diff --git a/src/utility/ota/OTA-portenta-h7.cpp b/src/utility/ota/OTA-portenta-h7.cpp index 784f71969..12481a7ed 100644 --- a/src/utility/ota/OTA-portenta-h7.cpp +++ b/src/utility/ota/OTA-portenta-h7.cpp @@ -29,8 +29,18 @@ #include #include +#include + +#include "tls/utility/SHA256.h" + #include "../watchdog/Watchdog.h" +/****************************************************************************** + * EXTERN + ******************************************************************************/ + +extern RTC_HandleTypeDef RTCHandle; + /****************************************************************************** * FUNCTION DEFINITION ******************************************************************************/ @@ -96,4 +106,41 @@ int portenta_h7_onOTARequest(char const * ota_url, const bool use_ethernet) NVIC_SystemReset(); } +String portenta_h7_getOTAImageSHA256() +{ + /* The length of the application can be retrieved the same way it was + * communicated to the bootloader, that is by writing to the non-volatile + * storage registers of the RTC. + */ + SHA256 sha256; + uint32_t const app_start = 0x8040000; + uint32_t const app_size = HAL_RTCEx_BKUPRead(&RTCHandle, RTC_BKP_DR3); + + sha256.begin(); + uint32_t b = 0; + uint32_t bytes_read = 0; for(uint32_t a = app_start; + bytes_read < app_size; + bytes_read += sizeof(b), a += sizeof(b)) + { + /* Read the next chunk of memory. */ + memcpy(&b, reinterpret_cast(a), sizeof(b)); + /* Feed it to SHA256. */ + sha256.update(reinterpret_cast(&b), sizeof(b)); + } + /* Retrieve the final hash string. */ + uint8_t sha256_hash[SHA256::HASH_SIZE] = {0}; + sha256.finalize(sha256_hash); + String sha256_str; + std::for_each(sha256_hash, + sha256_hash + SHA256::HASH_SIZE, + [&sha256_str](uint8_t const elem) + { + char buf[4]; + snprintf(buf, 4, "%02X", elem); + sha256_str += buf; + }); + DEBUG_VERBOSE("SHA256: %d bytes (of %d) read", bytes_read, app_size); + return sha256_str; +} + #endif /* BOARD_STM32H7 */ diff --git a/src/utility/ota/OTA-samd.cpp b/src/utility/ota/OTA-samd.cpp index da4a98e27..fa62bb359 100644 --- a/src/utility/ota/OTA-samd.cpp +++ b/src/utility/ota/OTA-samd.cpp @@ -15,17 +15,18 @@ a commercial license, send an email to license@arduino.cc. */ -#ifdef ARDUINO_ARCH_SAMD - /****************************************************************************** * INCLUDE ******************************************************************************/ -#include "OTA.h" +#include -#include +#if defined (ARDUINO_ARCH_SAMD) && OTA_ENABLED +#include "OTA.h" +#include #include "../watchdog/Watchdog.h" +#include "utility/ota/FlashSHA256.h" #if OTA_STORAGE_SNU # include @@ -65,4 +66,19 @@ int samd_onOTARequest(char const * ota_url) return static_cast(OTAError::DownloadFailed); } +String samd_getOTAImageSHA256() +{ + /* Calculate the SHA256 checksum over the firmware stored in the flash of the + * MCU. Note: As we don't know the length per-se we read chunks of the flash + * until we detect one containing only 0xFF (= flash erased). This only works + * for firmware updated via OTA and second stage bootloaders (SxU family) + * because only those erase the complete flash before performing an update. + * Since the SHA256 firmware image is only required for the cloud servers to + * perform a version check after the OTA update this is a acceptable trade off. + * The bootloader is excluded from the calculation and occupies flash address + * range 0 to 0x2000, total flash size of 0x40000 bytes (256 kByte). + */ + return FlashSHA256::calc(0x2000, 0x40000 - 0x2000); +} + #endif /* ARDUINO_ARCH_SAMD */ diff --git a/src/utility/ota/OTA.h b/src/utility/ota/OTA.h index c1d3f79a0..26139d46d 100644 --- a/src/utility/ota/OTA.h +++ b/src/utility/ota/OTA.h @@ -23,6 +23,7 @@ ******************************************************************************/ #include +#include /****************************************************************************** * DEFINES @@ -56,14 +57,22 @@ enum class OTAError : int #ifdef ARDUINO_ARCH_SAMD int samd_onOTARequest(char const * ota_url); +String samd_getOTAImageSHA256(); #endif #ifdef ARDUINO_NANO_RP2040_CONNECT int rp2040_connect_onOTARequest(char const * ota_url); +String rp2040_connect_getOTAImageSHA256(); #endif #ifdef BOARD_STM32H7 int portenta_h7_onOTARequest(char const * ota_url, const bool use_ethernet); +String portenta_h7_getOTAImageSHA256(); +#endif + +#ifdef ARDUINO_ARCH_ESP32 +int esp32_onOTARequest(char const * ota_url); +String esp32_getOTAImageSHA256(); #endif #endif /* ARDUINO_OTA_LOGIC_H_ */