From 4577d59c69a672cad70cc76ca3cbc7f20cb84df4 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Thu, 18 Apr 2024 15:42:49 +0200 Subject: [PATCH 01/18] CI: added ArduinoHttpClient as dependency --- .github/workflows/compile-examples.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index baf9a4b1..0e6c98ef 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -22,6 +22,7 @@ jobs: # Install the ArduinoIoTCloud library from the repository - source-path: ./ - name: Arduino_ConnectionHandler + - name: ArduinoHttpClient - name: Arduino_DebugUtils - name: ArduinoMqttClient - name: Arduino_SecureElement From ec187076953201640f7bea529f390ea60006d996 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 12 Mar 2024 15:43:32 +0100 Subject: [PATCH 02/18] removing unused dependencies: ESP and Portente OTA --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index c98825ea..3664ce8c 100644 --- a/library.properties +++ b/library.properties @@ -8,4 +8,4 @@ category=Communication url=https://github.com/arduino-libraries/ArduinoIoTCloud architectures=mbed,samd,esp8266,mbed_nano,mbed_portenta,mbed_nicla,esp32,mbed_opta,mbed_giga,renesas_portenta,renesas_uno,mbed_edge includes=ArduinoIoTCloud.h -depends=Arduino_ConnectionHandler,Arduino_DebugUtils,Arduino_SecureElement,ArduinoMqttClient,ArduinoECCX08,RTCZero,Adafruit SleepyDog Library,Arduino_ESP32_OTA,Arduino_Portenta_OTA +depends=Arduino_ConnectionHandler,Arduino_DebugUtils,Arduino_SecureElement,ArduinoMqttClient,ArduinoECCX08,RTCZero,Adafruit SleepyDog Library,ArduinoHttpClient From f494795a1a365810dee1863406d59ccf20e800df Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 21 Feb 2024 17:22:27 +0100 Subject: [PATCH 03/18] changing include enrtypoint for OTA --- src/utility/ota/OTA.cpp | 125 ---------------------------------------- src/utility/ota/OTA.h | 81 ++++++++++++-------------- 2 files changed, 36 insertions(+), 170 deletions(-) delete mode 100644 src/utility/ota/OTA.cpp diff --git a/src/utility/ota/OTA.cpp b/src/utility/ota/OTA.cpp deleted file mode 100644 index e71aac5a..00000000 --- a/src/utility/ota/OTA.cpp +++ /dev/null @@ -1,125 +0,0 @@ -/* - 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 OTA_ENABLED - -#include "OTA.h" -#include - -/****************************************************************************** - * FUNCTION DECLARATION - ******************************************************************************/ - -#ifdef ARDUINO_ARCH_SAMD -int samd_onOTARequest(char const * url); -String samd_getOTAImageSHA256(); -bool samd_isOTACapable(); -#endif - -#ifdef ARDUINO_NANO_RP2040_CONNECT -int rp2040_connect_onOTARequest(char const * url); -String rp2040_connect_getOTAImageSHA256(); -bool rp2040_connect_isOTACapable(); -#endif - -#ifdef BOARD_STM32H7 -int portenta_h7_onOTARequest(char const * url, NetworkAdapter iface); -String portenta_h7_getOTAImageSHA256(); -void portenta_h7_setNetworkAdapter(NetworkAdapter iface); -bool portenta_h7_isOTACapable(); -#endif - -#ifdef ARDUINO_ARCH_ESP32 -int esp32_onOTARequest(char const * url); -String esp32_getOTAImageSHA256(); -bool esp32_isOTACapable(); -#endif - -#ifdef ARDUINO_UNOR4_WIFI -int unor4_onOTARequest(char const * url); -String unor4_getOTAImageSHA256(); -bool unor4_isOTACapable(); -#endif - -/****************************************************************************** - * PUBLIC MEMBER FUNCTIONS - ******************************************************************************/ - -int OTA::onRequest(String url, NetworkAdapter iface) -{ - DEBUG_INFO("ArduinoIoTCloudTCP::%s _ota_url = %s", __FUNCTION__, url.c_str()); - -#if defined (ARDUINO_ARCH_SAMD) - (void)iface; - return samd_onOTARequest(url.c_str()); -#elif defined (ARDUINO_NANO_RP2040_CONNECT) - (void)iface; - return rp2040_connect_onOTARequest(url.c_str()); -#elif defined (BOARD_STM32H7) - return portenta_h7_onOTARequest(url.c_str(), iface); -#elif defined (ARDUINO_ARCH_ESP32) - (void)iface; - return esp32_onOTARequest(url.c_str()); -#elif defined (ARDUINO_UNOR4_WIFI) - (void)iface; - return unor4_onOTARequest(url.c_str()); -#else - #error "OTA not supported for this architecture" -#endif -} - -String OTA::getImageSHA256() -{ -#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(); -#elif defined (ARDUINO_UNOR4_WIFI) - return unor4_getOTAImageSHA256(); -#else - #error "No method for SHA256 checksum calculation over application image defined for this architecture." -#endif -} - -bool OTA::isCapable() -{ -#if defined (ARDUINO_ARCH_SAMD) - return samd_isOTACapable(); -#elif defined (ARDUINO_NANO_RP2040_CONNECT) - return rp2040_connect_isOTACapable(); -#elif defined (BOARD_STM32H7) - return portenta_h7_isOTACapable(); -#elif defined (ARDUINO_ARCH_ESP32) - return esp32_isOTACapable(); -#elif defined (ARDUINO_UNOR4_WIFI) - return unor4_isOTACapable(); -#else - #error "OTA not supported for this architecture" -#endif -} - -#endif /* OTA_ENABLED */ diff --git a/src/utility/ota/OTA.h b/src/utility/ota/OTA.h index 88c7229b..f8d5fcfd 100644 --- a/src/utility/ota/OTA.h +++ b/src/utility/ota/OTA.h @@ -1,42 +1,49 @@ /* - This file is part of ArduinoIoTCloud. + This file is part of the ArduinoIoTCloud library. - Copyright 2020 ARDUINO SA (http://www.arduino.cc/) + Copyright (c) 2024 Arduino SA - 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. + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef ARDUINO_OTA_LOGIC_H_ -#define ARDUINO_OTA_LOGIC_H_ +#pragma once +#include "AIoTC_Config.h" +#if OTA_ENABLED -/****************************************************************************** - * INCLUDE - ******************************************************************************/ +#ifdef ARDUINO_ARCH_SAMD +#include "OTASamd.h" +using OTACloudProcess = SAMDOTACloudProcess; -#include +#elif defined(ARDUINO_NANO_RP2040_CONNECT) +#include "OTANanoRP2040.h" +using OTACloudProcess = NANO_RP2040OTACloudProcess; -#if OTA_ENABLED -#include -#include +#elif defined(BOARD_STM32H7) +#include "OTAPortentaH7.h" +using OTACloudProcess = STM32H7OTACloudProcess; -/****************************************************************************** - * DEFINES - ******************************************************************************/ +#elif defined(ARDUINO_ARCH_ESP32) +#include "OTAEsp32.h" +using OTACloudProcess = ESP32OTACloudProcess; -#define RP2040_OTA_ERROR_BASE (-100) -/****************************************************************************** - * TYPEDEF - ******************************************************************************/ +#if defined (ARDUINO_NANO_ESP32) + constexpr uint32_t OtaMagicNumber = 0x23410070; +#else + constexpr uint32_t OtaMagicNumber = 0x45535033; +#endif + +#elif defined(ARDUINO_UNOR4_WIFI) +#include "OTAUnoR4.h" +using OTACloudProcess = UNOR4OTACloudProcess; + +#else +#error "This Board doesn't support OTA" +#endif + +#define RP2040_OTA_ERROR_BASE (-100) enum class OTAError : int { @@ -54,20 +61,4 @@ enum class OTAError : int RP2040_ErrorUnmount = RP2040_OTA_ERROR_BASE - 9, }; -/****************************************************************************** - * CLASS DECLARATION - ******************************************************************************/ - -class OTA -{ -public: - - static int onRequest(String url, NetworkAdapter iface); - static String getImageSHA256(); - static bool isCapable(); - -}; - -#endif /* OTA_ENABLED */ - -#endif /* ARDUINO_OTA_LOGIC_H_ */ +#endif // OTA_ENABLED From a07a23d4390618345b99dfe94e9259f7d7043377 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 21 Feb 2024 17:10:46 +0100 Subject: [PATCH 04/18] included LZSSDecoder library beforme making it a library --- src/utility/lzss/lzss.cpp | 163 ++++++++++++++++++++++++++++++++++++++ src/utility/lzss/lzss.h | 108 +++++++++++++++++++++++++ 2 files changed, 271 insertions(+) create mode 100644 src/utility/lzss/lzss.cpp create mode 100644 src/utility/lzss/lzss.h diff --git a/src/utility/lzss/lzss.cpp b/src/utility/lzss/lzss.cpp new file mode 100644 index 00000000..1a11399f --- /dev/null +++ b/src/utility/lzss/lzss.cpp @@ -0,0 +1,163 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + + This implementation took inspiration from https://okumuralab.org/~okumura/compression/lzss.c source code +*/ + +/************************************************************************************** + INCLUDE + **************************************************************************************/ +#include "lzss.h" + +#include + +/************************************************************************************** + LZSS DECODER CLASS IMPLEMENTATION + **************************************************************************************/ + +// get the number of bits the algorithm will try to get given the state +uint8_t LZSSDecoder::bits_required(LZSSDecoder::FSM_STATES s) { + switch(s) { + case FSM_0: + return 1; + case FSM_1: + return 8; + case FSM_2: + return EI; + case FSM_3: + return EJ; + default: + return 0; + } +} + +LZSSDecoder::LZSSDecoder(std::function getc_cbk, std::function putc_cbk) +: available(0), state(FSM_0), put_char_cbk(putc_cbk), get_char_cbk(getc_cbk) { + for (int i = 0; i < N - F; i++) buffer[i] = ' '; + r = N - F; +} + + +LZSSDecoder::LZSSDecoder(std::function putc_cbk) +: available(0), state(FSM_0), put_char_cbk(putc_cbk), get_char_cbk(nullptr) { + for (int i = 0; i < N - F; i++) buffer[i] = ' '; + r = N - F; +} + +LZSSDecoder::status LZSSDecoder::handle_state() { + LZSSDecoder::status res = IN_PROGRESS; + + int c = getbit(bits_required(this->state)); + + if(c == LZSS_BUFFER_EMPTY) { + res = NOT_COMPLETED; + } else if (c == LZSS_EOF) { + res = DONE; + this->state = FSM_EOF; + } else { + switch(this->state) { + case FSM_0: + if(c) { + this->state = FSM_1; + } else { + this->state = FSM_2; + } + break; + case FSM_1: + putc(c); + buffer[r++] = c; + r &= (N - 1); // equivalent to r = r % N when N is a power of 2 + + this->state = FSM_0; + break; + case FSM_2: + this->i = c; + this->state = FSM_3; + break; + case FSM_3: { + int j = c; + + // This is where the actual decompression takes place: we look into the local buffer for reuse + // of byte chunks. This can be improved by means of memcpy and by changing the putc function + // into a put_buf function in order to avoid buffering on the other end. + // TODO improve this section of code + for (int k = 0; k <= j + 1; k++) { + c = buffer[(this->i + k) & (N - 1)]; // equivalent to buffer[(i+k) % N] when N is a power of 2 + putc(c); + buffer[r++] = c; + r &= (N - 1); // equivalent to r = r % N + } + this->state = FSM_0; + + break; + } + case FSM_EOF: + break; + } + } + + return res; +} + +LZSSDecoder::status LZSSDecoder::decompress(uint8_t* const buffer, uint32_t size) { + if(!get_char_cbk) { + this->in_buffer = buffer; + this->available += size; + } + + status res = IN_PROGRESS; + + while((res = handle_state()) == IN_PROGRESS); + + this->in_buffer = nullptr; + + return res; +} + +int LZSSDecoder::getbit(uint8_t n) { // get n bits from buffer + int x=0, c; + + // if the local bit buffer doesn't have enough bit get them + while(buf_size < n) { + switch(c=getc()) { + case LZSS_EOF: + case LZSS_BUFFER_EMPTY: + return c; + } + buf <<= 8; + + buf |= (uint8_t)c; + buf_size += sizeof(uint8_t)*8; + } + + // the result is the content of the buffer starting from msb to n successive bits + x = buf >> (buf_size-n); + + // remove from the buffer the read bits with a mask + buf &= (1<<(buf_size-n))-1; + + buf_size-=n; + + return x; +} + +int LZSSDecoder::getc() { + int c; + + if(get_char_cbk) { + c = get_char_cbk(); + } else if(in_buffer == nullptr || available == 0) { + c = LZSS_BUFFER_EMPTY; + } else { + c = *in_buffer; + in_buffer++; + available--; + } + return c; +} \ No newline at end of file diff --git a/src/utility/lzss/lzss.h b/src/utility/lzss/lzss.h new file mode 100644 index 00000000..bf652026 --- /dev/null +++ b/src/utility/lzss/lzss.h @@ -0,0 +1,108 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +/************************************************************************************** + INCLUDE + **************************************************************************************/ +#include +#include +#include + +/************************************************************************************** + FUNCTION DEFINITION + **************************************************************************************/ + +/************************************************************************************** + LZSS DECODER CLASS + **************************************************************************************/ + + +class LZSSDecoder { +public: + + /** + * Build an LZSS decoder by providing a callback for storing the decoded bytes + * @param putc_cbk: a callback that takes a char and stores it e.g. a callback to fwrite + */ + LZSSDecoder(std::function putc_cbk); + + /** + * Build an LZSS decoder providing a callback for getting a char and putting a char + * in this way you need to call decompress with no parameters + * @param putc_cbk: a callback that takes a char and stores it e.g. a callback to fwrite + * @param getc_cbk: a callback that returns the next char to consume + * -1 means EOF, -2 means buffer is temporairly finished + */ + LZSSDecoder(std::function getc_cbk, std::function putc_cbk); + + /** + * this enum describes the result of the computation of a single FSM computation + * DONE: the decompression is completed + * IN_PROGRESS: the decompression cycle completed successfully, ready to compute next + * NOT_COMPLETED: the current cycle didn't complete because the available data is not enough + */ + enum status: uint8_t { + DONE, + IN_PROGRESS, + NOT_COMPLETED + }; + + /** + * decode the provided buffer until buffer ends, then pause the process + * @return DONE if the decompression is completed, NOT_COMPLETED if not + */ + status decompress(uint8_t* const buffer=nullptr, uint32_t size=0); + + static const int LZSS_EOF = -1; + static const int LZSS_BUFFER_EMPTY = -2; +private: + // TODO provide a way for the user to set these parameters + static const int EI = 11; /* typically 10..13 */ + static const int EJ = 4; /* typically 4..5 */ + static const int N = (1 << EI); /* buffer size */ + static const int F = ((1 << EJ) + 1); /* lookahead buffer size */ + + // algorithm specific buffer used to store text that could be later referenced and copied + uint8_t buffer[N * 2]; + + // this function gets 1 single char from the input buffer + int getc(); + uint8_t* in_buffer = nullptr; + uint32_t available = 0; + + status handle_state(); + + // get 1 bit from the available input buffer + int getbit(uint8_t n); + // the following 2 are variables used by getbits + uint32_t buf, buf_size=0; + + enum FSM_STATES: uint8_t { + FSM_0 = 0, + FSM_1 = 1, + FSM_2 = 2, + FSM_3 = 3, + FSM_EOF + } state; + + // these variable are used in a decode session and specific to the old C implementation + // there is no documentation about their meaning + int i, r; + + std::function put_char_cbk; + std::function get_char_cbk; + + inline void putc(const uint8_t c) { if(put_char_cbk) { put_char_cbk(c); } } + + // get the number of bits the FSM will require given its state + uint8_t bits_required(FSM_STATES s); +}; \ No newline at end of file From a3747d3ed4274e90ad2c0a7b625e945e77c311a9 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Mon, 15 Apr 2024 12:20:22 +0200 Subject: [PATCH 05/18] removed FlashSHA256.h --- src/utility/ota/FlashSHA256.cpp | 110 -------------------------------- src/utility/ota/FlashSHA256.h | 51 --------------- 2 files changed, 161 deletions(-) delete mode 100644 src/utility/ota/FlashSHA256.cpp delete mode 100644 src/utility/ota/FlashSHA256.h diff --git a/src/utility/ota/FlashSHA256.cpp b/src/utility/ota/FlashSHA256.cpp deleted file mode 100644 index d0fc3d53..00000000 --- a/src/utility/ota/FlashSHA256.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - 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 OTA_ENABLED - -#include "FlashSHA256.h" - -#include "../../tls/utility/SHA256.h" - -#include - -#undef max -#undef min -#include - -/****************************************************************************** - * PUBLIC MEMBER FUNCTIONS - ******************************************************************************/ - -String FlashSHA256::calc(uint32_t const start_addr, uint32_t const max_flash_size) -{ - SHA256 sha256; - uint8_t chunk [FLASH_READ_CHUNK_SIZE], - next_chunk[FLASH_READ_CHUNK_SIZE]; - - sha256.begin(); - - /* Read the first two chunks of flash. */ - uint32_t flash_addr = start_addr; - uint32_t bytes_read = 0; - memcpy(chunk, reinterpret_cast(flash_addr), FLASH_READ_CHUNK_SIZE); - flash_addr += FLASH_READ_CHUNK_SIZE; - - for(; bytes_read < max_flash_size; flash_addr += FLASH_READ_CHUNK_SIZE) - { - /* Read the next chunk of memory. */ - memcpy(next_chunk, reinterpret_cast(flash_addr), FLASH_READ_CHUNK_SIZE); - - /* Check if the next segment is erased, that is if all bytes within - * a read segment are 0xFF -> then we've reached the end of the firmware. - */ - bool const next_chunk_is_erased_flash = std::all_of(next_chunk, - next_chunk+FLASH_READ_CHUNK_SIZE, - [](uint8_t const elem) { return (elem == 0xFF); }); - /* Determine how many bytes at the end of the current chunk are - * already set to 0xFF and therefore erased/non-written flash - * memory. - */ - if (next_chunk_is_erased_flash) - { - /* Eliminate trailing 0xFF. */ - size_t valid_bytes_in_chunk = 0; - for(valid_bytes_in_chunk = FLASH_READ_CHUNK_SIZE; valid_bytes_in_chunk > 0; valid_bytes_in_chunk--) - { - if (chunk[valid_bytes_in_chunk-1] != 0xFF) - break; - } - /* Update with the remaining bytes. */ - sha256.update(chunk, valid_bytes_in_chunk); - bytes_read += valid_bytes_in_chunk; - break; - } - - /* We've read a normal segment with the next segment not containing - * any erased elements, just update the SHA256 hash calculation. - */ - sha256.update(chunk, FLASH_READ_CHUNK_SIZE); - bytes_read += FLASH_READ_CHUNK_SIZE; - - /* Copy next_chunk to chunk. */ - memcpy(chunk, next_chunk, FLASH_READ_CHUNK_SIZE); - } - - /* 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; - }); - /* Do some debug printout. */ - DEBUG_VERBOSE("SHA256: %d bytes read", bytes_read); - return sha256_str; -} - -#endif /* OTA_ENABLED */ diff --git a/src/utility/ota/FlashSHA256.h b/src/utility/ota/FlashSHA256.h deleted file mode 100644 index a3125f71..00000000 --- a/src/utility/ota/FlashSHA256.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - 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. -*/ - -#ifndef ARDUINO_OTA_FLASH_SHA256_H_ -#define ARDUINO_OTA_FLASH_SHA256_H_ - -/****************************************************************************** - * INCLUDE - ******************************************************************************/ - -#include -#if OTA_ENABLED - -#include - -/****************************************************************************** - * CLASS DECLARATION - ******************************************************************************/ - -class FlashSHA256 -{ -public: - - static String calc(uint32_t const start_addr, uint32_t const max_flash_size); - -private: - - FlashSHA256() { } - FlashSHA256(FlashSHA256 const &) { } - - static constexpr uint32_t FLASH_READ_CHUNK_SIZE = 64; - -}; - -#endif /* OTA_ENABLED */ - -#endif /* ARDUINO_OTA_FLASH_SHA256_H_ */ From f843fd06f53fcc6e5f40f88610a3852aaf2cce8e Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Mon, 15 Apr 2024 12:20:57 +0200 Subject: [PATCH 06/18] adjusting OTA.h --- src/ota/OTA.h | 60 +++++++++++++++++++++++++++++++++ src/ota/OTAConfig.h | 47 ++++++++++++++++++++++++++ src/ota/OTATypes.h | 78 +++++++++++++++++++++++++++++++++++++++++++ src/utility/ota/OTA.h | 64 ----------------------------------- 4 files changed, 185 insertions(+), 64 deletions(-) create mode 100644 src/ota/OTA.h create mode 100644 src/ota/OTAConfig.h create mode 100644 src/ota/OTATypes.h delete mode 100644 src/utility/ota/OTA.h diff --git a/src/ota/OTA.h b/src/ota/OTA.h new file mode 100644 index 00000000..c1a56194 --- /dev/null +++ b/src/ota/OTA.h @@ -0,0 +1,60 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once +#include "AIoTC_Config.h" +#if OTA_ENABLED + +#include "OTAConfig.h" +#ifdef ARDUINO_ARCH_SAMD + +#include "implementation/OTASamd.h" +using ArduinoCloudOTA = SAMDOTACloudProcess; + +// TODO Check if a macro already exist +// constexpr uint32_t OtaMagicNumber = 0x23418054; // MKR_WIFI_1010 +constexpr uint32_t OtaMagicNumber = 0x23418057; // NANO_33_IOT + +#elif defined(ARDUINO_NANO_RP2040_CONNECT) +#include "implementation/OTANanoRP2040.h" +using ArduinoCloudOTA = NANO_RP2040OTACloudProcess; + +// TODO Check if a macro already exist +constexpr uint32_t OtaMagicNumber = 0x2341005E; // TODO check this value is correct + +#elif defined(BOARD_STM32H7) +#include "implementation/OTASTM32H7.h" +using ArduinoCloudOTA = STM32H7OTACloudProcess; + +constexpr uint32_t OtaMagicNumber = ARDUINO_PORTENTA_OTA_MAGIC; + +#elif defined(ARDUINO_ARCH_ESP32) +#include "implementation/OTAEsp32.h" +using ArduinoCloudOTA = ESP32OTACloudProcess; + +#if defined (ARDUINO_NANO_ESP32) + constexpr uint32_t OtaMagicNumber = 0x23410070; +#else + constexpr uint32_t OtaMagicNumber = 0x45535033; +#endif + +#elif defined(ARDUINO_UNOR4_WIFI) + +#include "implementation/OTAUnoR4.h" +using ArduinoCloudOTA = UNOR4OTACloudProcess; + +// TODO Check if a macro already exist +constexpr uint32_t OtaMagicNumber = 0x234110020; // TODO check this value is correct + +#else +#error "This Board doesn't support OTA" +#endif + +#endif // OTA_ENABLED diff --git a/src/ota/OTAConfig.h b/src/ota/OTAConfig.h new file mode 100644 index 00000000..71f78d00 --- /dev/null +++ b/src/ota/OTAConfig.h @@ -0,0 +1,47 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#ifdef ARDUINO_ARCH_SAMD +#define OFFLOADED_DOWNLOAD + +#elif defined(ARDUINO_NANO_RP2040_CONNECT) + +#elif defined(BOARD_STM32H7) + +#if defined(ARDUINO_PORTENTA_H7_M7) + #define ARDUINO_PORTENTA_OTA_MAGIC 0x2341025b + #define ARDUINO_PORTENTA_OTA_SDMMC_SUPPORT + #define ARDUINO_PORTENTA_OTA_QSPI_SUPPORT +#endif + +#if defined(ARDUINO_NICLA_VISION) + #define ARDUINO_PORTENTA_OTA_MAGIC 0x2341025f + #define ARDUINO_PORTENTA_OTA_QSPI_SUPPORT +#endif + +#if defined(ARDUINO_OPTA) + #define ARDUINO_PORTENTA_OTA_MAGIC 0x23410064 + #define ARDUINO_PORTENTA_OTA_QSPI_SUPPORT +#endif + +#if defined(ARDUINO_GIGA) + #define ARDUINO_PORTENTA_OTA_MAGIC 0x23410266 + #define ARDUINO_PORTENTA_OTA_QSPI_SUPPORT +#endif + + +#elif defined(ARDUINO_ARCH_ESP32) +#define OTA_BASIC_AUTH + +#elif defined(ARDUINO_UNOR4_WIFI) +#define OFFLOADED_DOWNLOAD +#endif diff --git a/src/ota/OTATypes.h b/src/ota/OTATypes.h new file mode 100644 index 00000000..409b9154 --- /dev/null +++ b/src/ota/OTATypes.h @@ -0,0 +1,78 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include "AIoTC_Config.h" + +#if OTA_ENABLED +#include + +namespace ota { + enum class OTAError : int16_t { + None = 0, + NoCapableBootloader = -1, + NoOtaStorage = -2, + OtaStorageInit = -3, + OtaStorageOpen = -4, + OtaHeaderLength = -5, + OtaHeaderCrc = -6, + OtaHeaterMagicNumber = -7, + ParseHttpHeader = -8, + UrlParseError = -9, + ServerConnectError = -10, + HttpHeaderError = -11, + OtaDownload = -12, + OtaHeaderTimeout = -13, + HttpResponse = -14, + OtaStorageEnd = -15, + StorageConfig = -16, + Library = -17, + Modem = -18, + ErrorOpenUpdateFile = -19, + ErrorWriteUpdateFile = -20, + ErrorReformat = -21, + ErrorUnmount = -22, + ErrorRename = -23, + CaStorageInit = -24, + CaStorageOpen = -25, + }; + +#ifndef OFFLOADED_DOWNLOAD + union HeaderVersion { + struct __attribute__((packed)) { + uint32_t header_version : 6; + uint32_t compression : 1; + uint32_t signature : 1; + uint32_t spare : 4; + uint32_t payload_target : 4; + uint32_t payload_major : 8; + uint32_t payload_minor : 8; + uint32_t payload_patch : 8; + uint32_t payload_build_num : 24; + } field; + uint8_t buf[sizeof(field)]; + static_assert(sizeof(buf) == 8, "Error: sizeof(HEADER.VERSION) != 8"); + }; + + union OTAHeader { + struct __attribute__((packed)) { + uint32_t len; + uint32_t crc32; + uint32_t magic_number; + HeaderVersion hdr_version; + } header; + uint8_t buf[sizeof(header)]; + static_assert(sizeof(buf) == 20, "Error: sizeof(HEADER) != 20"); + }; +#endif // OFFLOADED_DOWNLOAD +} + +#endif // OTA_ENABLED diff --git a/src/utility/ota/OTA.h b/src/utility/ota/OTA.h deleted file mode 100644 index f8d5fcfd..00000000 --- a/src/utility/ota/OTA.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - This file is part of the ArduinoIoTCloud library. - - Copyright (c) 2024 Arduino SA - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#pragma once -#include "AIoTC_Config.h" -#if OTA_ENABLED - -#ifdef ARDUINO_ARCH_SAMD -#include "OTASamd.h" -using OTACloudProcess = SAMDOTACloudProcess; - -#elif defined(ARDUINO_NANO_RP2040_CONNECT) -#include "OTANanoRP2040.h" -using OTACloudProcess = NANO_RP2040OTACloudProcess; - -#elif defined(BOARD_STM32H7) -#include "OTAPortentaH7.h" -using OTACloudProcess = STM32H7OTACloudProcess; - -#elif defined(ARDUINO_ARCH_ESP32) -#include "OTAEsp32.h" -using OTACloudProcess = ESP32OTACloudProcess; - - -#if defined (ARDUINO_NANO_ESP32) - constexpr uint32_t OtaMagicNumber = 0x23410070; -#else - constexpr uint32_t OtaMagicNumber = 0x45535033; -#endif - -#elif defined(ARDUINO_UNOR4_WIFI) -#include "OTAUnoR4.h" -using OTACloudProcess = UNOR4OTACloudProcess; - -#else -#error "This Board doesn't support OTA" -#endif - -#define RP2040_OTA_ERROR_BASE (-100) - -enum class OTAError : int -{ - None = 0, - DownloadFailed = 1, - RP2040_UrlParseError = RP2040_OTA_ERROR_BASE - 0, - RP2040_ServerConnectError = RP2040_OTA_ERROR_BASE - 1, - RP2040_HttpHeaderError = RP2040_OTA_ERROR_BASE - 2, - RP2040_HttpDataError = RP2040_OTA_ERROR_BASE - 3, - RP2040_ErrorOpenUpdateFile = RP2040_OTA_ERROR_BASE - 4, - RP2040_ErrorWriteUpdateFile = RP2040_OTA_ERROR_BASE - 5, - RP2040_ErrorParseHttpHeader = RP2040_OTA_ERROR_BASE - 6, - RP2040_ErrorFlashInit = RP2040_OTA_ERROR_BASE - 7, - RP2040_ErrorReformat = RP2040_OTA_ERROR_BASE - 8, - RP2040_ErrorUnmount = RP2040_OTA_ERROR_BASE - 9, -}; - -#endif // OTA_ENABLED From 9e24711fd6cce37a1553e12b35a84a611a9a3f66 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Mon, 15 Apr 2024 12:21:31 +0200 Subject: [PATCH 07/18] defining OTAInterface --- src/ota/interface/OTAInterface.cpp | 235 ++++++++++++++++++ src/ota/interface/OTAInterface.h | 185 ++++++++++++++ src/ota/interface/OTAInterfaceDefault.cpp | 283 ++++++++++++++++++++++ src/ota/interface/OTAInterfaceDefault.h | 82 +++++++ 4 files changed, 785 insertions(+) create mode 100644 src/ota/interface/OTAInterface.cpp create mode 100644 src/ota/interface/OTAInterface.h create mode 100644 src/ota/interface/OTAInterfaceDefault.cpp create mode 100644 src/ota/interface/OTAInterfaceDefault.h diff --git a/src/ota/interface/OTAInterface.cpp b/src/ota/interface/OTAInterface.cpp new file mode 100644 index 00000000..b659917b --- /dev/null +++ b/src/ota/interface/OTAInterface.cpp @@ -0,0 +1,235 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include + +#if OTA_ENABLED +#include "OTAInterface.h" +#include "../OTA.h" + +extern "C" unsigned long getTime(); + +/****************************************************************************** + * PUBLIC MEMBER FUNCTIONS + ******************************************************************************/ + +#ifdef DEBUG_VERBOSE +const char* const OTACloudProcessInterface::STATE_NAMES[] = { // used only for debug purposes + "Resume", + "OtaBegin", + "Idle", + "OtaAvailable", + "StartOTA", + "Fetch", + "FlashOTA", + "Reboot", + "Fail", + "NoCapableBootloaderFail", + "NoOtaStorageFail", + "OtaStorageInitFail", + "OtaStorageOpenFail", + "OtaHeaderLengthFail", + "OtaHeaderCrcFail", + "OtaHeaderMagicNumberFail", + "ParseHttpHeaderFail", + "UrlParseErrorFail", + "ServerConnectErrorFail", + "HttpHeaderErrorFail", + "OtaDownloadFail", + "OtaHeaderTimeoutFail", + "HttpResponseFail", + "OtaStorageEndFail", + "StorageConfigFail", + "LibraryFail", + "ModemFail", + "ErrorOpenUpdateFileFail", + "ErrorWriteUpdateFileFail", + "ErrorReformatFail", + "ErrorUnmountFail", + "ErrorRenameFail", +}; +#endif // DEBUG_VERBOSE + +OTACloudProcessInterface::OTACloudProcessInterface(MessageStream *ms) +: CloudProcess(ms) +, policies(None) +, state(Resume) +, previous_state(Resume) +, report_last_timestamp(0) +, report_counter(0) +, context(nullptr) { +} + +OTACloudProcessInterface::~OTACloudProcessInterface() { + clean(); +} + +void OTACloudProcessInterface::handleMessage(Message* msg) { + + if ((state >= OtaAvailable || state < 0) && previous_state != state) { + reportStatus(static_cast(state<0? state : 0)); + } + + // this allows to do status report only when the state changes + previous_state = state; + + switch(state) { + case Resume: updateState(resume(msg)); break; + case OtaBegin: updateState(otaBegin()); break; + case Idle: updateState(idle(msg)); break; + case OtaAvailable: updateState(otaAvailable()); break; + case StartOTA: updateState(startOTA()); break; + case Fetch: updateState(fetch()); break; + case FlashOTA: updateState(flashOTA()); break; + case Reboot: updateState(reboot()); break; + case OTAUnavailable: break; + default: updateState(fail()); // all the states that are not defined are failures + } +} + +OTACloudProcessInterface::State OTACloudProcessInterface::otaBegin() { + if(!isOtaCapable()) { + DEBUG_VERBOSE("OTA is not available on this board"); + return OTAUnavailable; + } + + struct OtaBeginUp msg = { + OtaBeginUpId, + }; + + SHA256 sha256_calc; + calculateSHA256(sha256_calc); + + sha256_calc.finalize(sha256); + memcpy(msg.params.sha, sha256, SHA256::HASH_SIZE); + + DEBUG_VERBOSE("calculated SHA256: " + "0x%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X" + "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", + sha256[0], sha256[1], sha256[2], sha256[3], sha256[4], sha256[5], sha256[6], sha256[7], + sha256[8], sha256[9], sha256[10], sha256[11], sha256[12], sha256[13], sha256[14], sha256[15], + sha256[16], sha256[17], sha256[18], sha256[19], sha256[20], sha256[21], sha256[22], sha256[23], + sha256[24], sha256[25], sha256[26], sha256[27], sha256[28], sha256[29], sha256[30], sha256[31] + ); + + deliver((Message*)&msg); + + return Idle; +} + +void OTACloudProcessInterface::calculateSHA256(SHA256& sha256_calc) { + auto res = appFlashOpen(); + if(!res) { + // TODO return error + return; + } + + sha256_calc.begin(); + sha256_calc.update( + reinterpret_cast(appStartAddress()), + appSize()); + appFlashClose(); +} + +OTACloudProcessInterface::State OTACloudProcessInterface::idle(Message* msg) { + // if a msg arrived, it may be an OTAavailable, then go to otaAvailable + // otherwise do nothing + if(msg!=nullptr && msg->id == OtaUpdateCmdDownId) { + // save info coming from this message + assert(context == nullptr); // This should never fail + + struct OtaUpdateCmdDown* ota_msg = (struct OtaUpdateCmdDown*)msg; + + context = new OtaContext( + ota_msg->params.id, ota_msg->params.url, + ota_msg->params.initialSha256, ota_msg->params.finalSha256 + ); + + // TODO verify that initialSha256 is the sha256 on board + // TODO verify that final sha is not the current sha256 (?) + return OtaAvailable; + } + + return Idle; +} + +OTACloudProcessInterface::State OTACloudProcessInterface::otaAvailable() { + // depending on the policy decided on this device the ota process can start immediately + // or wait for confirmation from the user + if((policies & (ApprovalRequired | Approved)) == ApprovalRequired ) { + return OtaAvailable; + } else { + policies &= ~Approved; + return StartOTA; + } // TODO add an abortOTA command? in this case delete the context +} + +OTACloudProcessInterface::State OTACloudProcessInterface::fail() { + reset(); + clean(); + + return Idle; +} + +void OTACloudProcessInterface::clean() { + // free the context pointer + if(context != nullptr) { + delete context; + context = nullptr; + } +} + +void OTACloudProcessInterface::reportStatus(int32_t state_data) { + if(context == nullptr) { + // FIXME handle this case: ota not in progress + return; + } + uint32_t new_timestamp = getTime(); + + struct OtaProgressCmdUp msg = { + OtaProgressCmdUpId, + }; + + memcpy(msg.params.id, context->id, ID_SIZE); + msg.params.state = state>=0 ? state : State::Fail; + + if(new_timestamp == report_last_timestamp) { + msg.params.time = new_timestamp*1e6 + ++report_counter; + } else { + msg.params.time = new_timestamp*1e6; + report_counter = 0; + report_last_timestamp = new_timestamp; + } + + msg.params.state_data = state_data; + + deliver((Message*)&msg); +} + +OTACloudProcessInterface::OtaContext::OtaContext( + uint8_t id[ID_SIZE], const char* url, + uint8_t* initialSha256, uint8_t* finalSha256 + ) : url((char*) malloc(strlen(url) + 1)) { + + memcpy(this->id, id, ID_SIZE); + strcpy(this->url, url); + memcpy(this->initialSha256, initialSha256, 32); + memcpy(this->finalSha256, finalSha256, 32); +} + +OTACloudProcessInterface::OtaContext::~OtaContext() { + free(url); +} + +#endif /* OTA_ENABLED */ diff --git a/src/ota/interface/OTAInterface.h b/src/ota/interface/OTAInterface.h new file mode 100644 index 00000000..a62b7cb2 --- /dev/null +++ b/src/ota/interface/OTAInterface.h @@ -0,0 +1,185 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include + +#if OTA_ENABLED +#include "../OTATypes.h" +#include "tls/utility/SHA256.h" + +#include +#include + +/****************************************************************************** + * CLASS DECLARATION + ******************************************************************************/ + +class OTACloudProcessInterface: public CloudProcess { +public: + OTACloudProcessInterface(MessageStream *ms); + + virtual ~OTACloudProcessInterface(); + + enum State: int16_t { + Resume = 0, + OtaBegin = 1, + Idle = 2, + OtaAvailable = 3, + StartOTA = 4, + Fetch = 5, + FlashOTA = 6, + Reboot = 7, + Fail = 8, + OTAUnavailable = 9, + + // Error states that may generically happen on all board + NoCapableBootloaderFail = static_cast(ota::OTAError::NoCapableBootloader), + NoOtaStorageFail = static_cast(ota::OTAError::NoOtaStorage), + OtaStorageInitFail = static_cast(ota::OTAError::OtaStorageInit), + OtaStorageOpenFail = static_cast(ota::OTAError::OtaStorageOpen), + OtaHeaderLengthFail = static_cast(ota::OTAError::OtaHeaderLength), + OtaHeaderCrcFail = static_cast(ota::OTAError::OtaHeaderCrc), + OtaHeaderMagicNumberFail = static_cast(ota::OTAError::OtaHeaterMagicNumber), + ParseHttpHeaderFail = static_cast(ota::OTAError::ParseHttpHeader), + UrlParseErrorFail = static_cast(ota::OTAError::UrlParseError), + ServerConnectErrorFail = static_cast(ota::OTAError::ServerConnectError), + HttpHeaderErrorFail = static_cast(ota::OTAError::HttpHeaderError), + OtaDownloadFail = static_cast(ota::OTAError::OtaDownload), + OtaHeaderTimeoutFail = static_cast(ota::OTAError::OtaHeaderTimeout), + HttpResponseFail = static_cast(ota::OTAError::HttpResponse), + OtaStorageEndFail = static_cast(ota::OTAError::OtaStorageEnd), + StorageConfigFail = static_cast(ota::OTAError::StorageConfig), + LibraryFail = static_cast(ota::OTAError::Library), + ModemFail = static_cast(ota::OTAError::Modem), + ErrorOpenUpdateFileFail = static_cast(ota::OTAError::ErrorOpenUpdateFile), // FIXME fopen + ErrorWriteUpdateFileFail = static_cast(ota::OTAError::ErrorWriteUpdateFile), // FIXME fwrite + ErrorReformatFail = static_cast(ota::OTAError::ErrorReformat), + ErrorUnmountFail = static_cast(ota::OTAError::ErrorUnmount), + ErrorRenameFail = static_cast(ota::OTAError::ErrorRename), + CaStorageInitFail = static_cast(ota::OTAError::CaStorageInit), + CaStorageOpenFail = static_cast(ota::OTAError::CaStorageOpen), + }; + +#ifdef DEBUG_VERBOSE + static const char* const STATE_NAMES[]; +#endif // DEBUG_VERBOSE + + enum OtaFlags: uint16_t { + None = 0, + ApprovalRequired = 1, + Approved = 1<<1 + }; + + virtual void handleMessage(Message*); + // virtual CloudProcess::State getState(); + // virtual void hook(State s, void* action); + virtual void update() { handleMessage(nullptr); } + + inline void approveOta() { policies |= Approved; } + inline void setOtaPolicies(uint16_t policies) { this->policies = policies; } + + inline State getState() { return state; } + + virtual bool isOtaCapable() = 0; +protected: + // The following methods represent the FSM actions performed in each state + + // the first state is 'resume', where we try to understand if we are booting after a ota + // the objective is to understand the result and report it to the cloud + virtual State resume(Message* msg=nullptr) = 0; + + // This state is used to send to the cloud the initial information on the fw present on the mcu + // this state will only send the sha256 of the board fw and go into Idle state + virtual State otaBegin(); + + // this state is the normal state where no action has to be performed. We may poll the cloud + // for updates in this state + virtual State idle(Message* msg=nullptr); + + // we go in this state if there is an ota available, depending on the policies implemented we may + // start the ota or wait for an user interaction + virtual State otaAvailable(); + + // we start the process of ota update and wait for the server to respond with the ota url and other info + virtual State startOTA() = 0; + + // we start the download and decompress process + virtual State fetch() = 0; + + // whene the download is correctly finished we set the mcu to use the newly downloaded binary + virtual State flashOTA() = 0; + + // we reboot the device + virtual State reboot() = 0; + + // if any of the steps described fail we get into this state and report to the cloud what happened + // then we go back to idle state + virtual State fail(); + + virtual void reset() = 0; + + uint16_t policies; + + inline void updateState(State s) { + if(state!=s) { + DEBUG_VERBOSE("OTAInterface: state change to %s from %s", + STATE_NAMES[s < 0? Fail - s : s], + STATE_NAMES[state < 0? Fail - state : state]); + previous_state = state; state = s; + } + } + + // This method is called to report the current state of the OtaClass + void reportStatus(int32_t state_data); + + // in order to calculate the SHA256 we need to get the start and end address of the Application, + // The Implementation of this class have to implement them. + // The calculation is performed during the otaBegin phase + virtual void* appStartAddress() = 0; + virtual uint32_t appSize() = 0; + + // some architecture require to explicitly open the flash in order to read it + virtual bool appFlashOpen() = 0; + virtual bool appFlashClose() = 0; + + // sha256 is going to be used in the ota process for validation, avoid calculating it twice + uint8_t sha256[SHA256::HASH_SIZE]; + + // calculateSHA256 method is overridable for platforms that do not support access through pointer to program memory + virtual void calculateSHA256(SHA256&); // FIXME return error +private: + void clean(); + + State state, previous_state; + + // status report related attributes + uint32_t report_last_timestamp, report_counter; +protected: + struct OtaContext { + OtaContext( + uint8_t id[ID_SIZE], const char* url, + uint8_t initialSha256[32], uint8_t finalSha256[32]); + ~OtaContext(); + + // parameters present in the ota available message that are of interest of the process + uint8_t id[ID_SIZE]; + char* const url; + uint8_t initialSha256[32]; + uint8_t finalSha256[32]; + } *context; +}; + +#endif // OTA_ENABLED diff --git a/src/ota/interface/OTAInterfaceDefault.cpp b/src/ota/interface/OTAInterfaceDefault.cpp new file mode 100644 index 00000000..de698e66 --- /dev/null +++ b/src/ota/interface/OTAInterfaceDefault.cpp @@ -0,0 +1,283 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ +#include + +#if OTA_ENABLED && ! defined(OFFLOADED_DOWNLOAD) +#include "OTAInterfaceDefault.h" +#include "../OTA.h" + +static uint32_t crc_update(uint32_t crc, const void * data, size_t data_len); + +OTADefaultCloudProcessInterface::OTADefaultCloudProcessInterface(MessageStream *ms, Client* client) +: OTACloudProcessInterface(ms) +, client(client) +, http_client(nullptr) +, username(nullptr), password(nullptr) +, context(nullptr) { +} + +OTADefaultCloudProcessInterface::~OTADefaultCloudProcessInterface() { + reset(); +} + +OTACloudProcessInterface::State OTADefaultCloudProcessInterface::startOTA() { + assert(client != nullptr); + assert(OTACloudProcessInterface::context != nullptr); + assert(context == nullptr); + + context = new Context( + OTACloudProcessInterface::context->url, + [this](uint8_t c) { + // int res = + this->writeFlash(&c, 1); + + // TODO report error in write flash, throw it? + } + ); + + // make the http get request + if(strcmp(context->parsed_url.schema(), "https") == 0) { + http_client = new HttpClient(*client, context->parsed_url.host(), context->parsed_url.port()); + } else { + return UrlParseErrorFail; + } + + http_client->beginRequest(); + auto res = http_client->get(context->parsed_url.path()); + + if(username != nullptr && password != nullptr) { + http_client->sendBasicAuth(username, password); + } + + http_client->endRequest(); + + if(res == HTTP_ERROR_CONNECTION_FAILED) { + DEBUG_VERBOSE("OTA ERROR: http client error connecting to server \"%s:%d\"", + context->parsed_url.host(), context->parsed_url.port()); + return ServerConnectErrorFail; + } else if(res == HTTP_ERROR_TIMED_OUT) { + DEBUG_VERBOSE("OTA ERROR: http client timeout \"%s\"", OTACloudProcessInterface::context->url); + return OtaHeaderTimeoutFail; + } else if(res != HTTP_SUCCESS) { + DEBUG_VERBOSE("OTA ERROR: http client returned %d on get \"%s\"", res, OTACloudProcessInterface::context->url); + return OtaDownloadFail; + } + + int statusCode = http_client->responseStatusCode(); + + if(statusCode != 200) { + DEBUG_VERBOSE("OTA ERROR: get response on \"%s\" returned status %d", OTACloudProcessInterface::context->url, statusCode); + return HttpResponseFail; + } + + // The following call is required to save the header value , keep it + if(http_client->contentLength() == HttpClient::kNoContentLengthHeader) { + DEBUG_VERBOSE("OTA ERROR: the response header doesn't contain \"ContentLength\" field"); + return HttpHeaderErrorFail; + } + + context->lastReportTime = millis(); + + return Fetch; +} + +OTACloudProcessInterface::State OTADefaultCloudProcessInterface::fetch() { + OTACloudProcessInterface::State res = Fetch; + int http_res = 0; + + if(http_client->available() == 0) { + goto exit; + } + + http_res = http_client->read(context->buffer, context->buf_len); + + if(http_res < 0) { + DEBUG_VERBOSE("OTA ERROR: Download read error %d", http_res); + res = OtaDownloadFail; + goto exit; + } + + parseOta(context->buffer, http_res); + + // TODO verify that the information present in the ota header match the info in context + if(context->downloadState == OtaDownloadCompleted) { + // Verify that the downloaded file size is matching the expected size ?? + // this could distinguish between consistency of the downloaded bytes and filesize + + // validate CRC + context->calculatedCrc32 ^= 0xFFFFFFFF; // finalize CRC + if(context->header.header.crc32 == context->calculatedCrc32) { + DEBUG_VERBOSE("Ota download completed successfully"); + res = FlashOTA; + } else { + res = OtaHeaderCrcFail; + } + } else if(context->downloadState == OtaDownloadError) { + DEBUG_VERBOSE("OTA ERROR: OtaDownloadError"); + + res = OtaDownloadFail; + } else if(context->downloadState == OtaDownloadMagicNumberMismatch) { + DEBUG_VERBOSE("OTA ERROR: Magic number mismatch"); + res = OtaHeaderMagicNumberFail; + } + +exit: + if(res != Fetch) { + http_client->stop(); // close the connection + delete http_client; + http_client = nullptr; + } + return res; +} + +void OTADefaultCloudProcessInterface::parseOta(uint8_t* buffer, size_t buf_len) { + assert(context != nullptr); // This should never fail + + for(uint8_t* cursor=(uint8_t*)buffer; cursordownloadState) { + case OtaDownloadHeader: { + uint32_t copied = buf_len < sizeof(context->header.buf) ? buf_len : sizeof(context->header.buf); + memcpy(context->header.buf+context->headerCopiedBytes, buffer, copied); + cursor += copied; + context->headerCopiedBytes += copied; + + // when finished go to next state + if(sizeof(context->header.buf) == context->headerCopiedBytes) { + context->downloadState = OtaDownloadFile; + + context->calculatedCrc32 = crc_update( + context->calculatedCrc32, + &(context->header.header.magic_number), + sizeof(context->header) - offsetof(ota::OTAHeader, header.magic_number) + ); + + if(context->header.header.magic_number != OtaMagicNumber) { + context->downloadState = OtaDownloadMagicNumberMismatch; + return; + } + } + + break; + } + case OtaDownloadFile: + context->decoder.decompress(cursor, buf_len - (cursor-buffer)); // TODO verify return value + + context->calculatedCrc32 = crc_update( + context->calculatedCrc32, + cursor, + buf_len - (cursor-buffer) + ); + + cursor += buf_len - (cursor-buffer); + context->downloadedSize += (cursor-buffer); + + if((millis() - context->lastReportTime) > 2000) { // Report the download progress each X millisecond + DEBUG_VERBOSE("OTA Download Progress %d/%d", context->downloadedSize, http_client->contentLength()); + + // FIXME the following line enables the report for download progress, it breaks + // reportStatus(context->downloadedSize); + context->lastReportTime = millis(); + } + + // TODO there should be no more bytes available when the download is completed + if(context->downloadedSize == http_client->contentLength()) { + context->downloadState = OtaDownloadCompleted; + } + + if(context->downloadedSize > http_client->contentLength()) { + context->downloadState = OtaDownloadError; + } + // TODO fail if we exceed a timeout? and available is 0 (client is broken) + break; + case OtaDownloadCompleted: + return; + default: + context->downloadState = OtaDownloadError; + return; + } + } +} + +void OTADefaultCloudProcessInterface::reset() { + if(http_client != nullptr) { + http_client->stop(); // close the connection + delete http_client; + http_client = nullptr; + } + + if(client!=nullptr && client->connected()) { + client->stop(); + } + + // free the context pointer + if(context != nullptr) { + delete context; + context = nullptr; + } +} + +OTADefaultCloudProcessInterface::Context::Context( + const char* url, std::function putc) + : parsed_url(url) + , downloadState(OtaDownloadHeader) + , calculatedCrc32(0xFFFFFFFF) + , headerCopiedBytes(0) + , downloadedSize(0) + , lastReportTime(0) + , decoder(putc) { } + +static const uint32_t crc_table[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +uint32_t crc_update(uint32_t crc, const void * data, size_t data_len) { + const unsigned char *d = (const unsigned char *)data; + unsigned int tbl_idx; + + while (data_len--) { + tbl_idx = (crc ^ *d) & 0xff; + crc = (crc_table[tbl_idx] ^ (crc >> 8)) & 0xffffffff; + d++; + } + + return crc & 0xffffffff; +} +#endif /* OTA_ENABLED && ! defined(OFFLOADED_DOWNLOAD) */ diff --git a/src/ota/interface/OTAInterfaceDefault.h b/src/ota/interface/OTAInterfaceDefault.h new file mode 100644 index 00000000..ae68d53b --- /dev/null +++ b/src/ota/interface/OTAInterfaceDefault.h @@ -0,0 +1,82 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ +#pragma once + +#include + +#if OTA_ENABLED && ! defined(OFFLOADED_DOWNLOAD) +#include + +#include +#include +#include "utility/lzss/lzss.h" +#include "OTAInterface.h" + +/** + * This class is the extension of the abstract class for OTA, with the addition that + * the download is performed by the mcu itself and not offloaded to the network peripheral + */ +class OTADefaultCloudProcessInterface: public OTACloudProcessInterface { +public: + OTADefaultCloudProcessInterface(MessageStream *ms, Client* client=nullptr); + ~OTADefaultCloudProcessInterface(); + + inline virtual void setClient(Client* c) { client = c; } + + void setAuthentication(const char* const username, const char* const password) { + this->username = username; + this->password = password; + } + +protected: + State startOTA(); + State fetch(); + void reset(); + virtual int writeFlash(uint8_t* const buffer, size_t len) = 0; + +private: + void parseOta(uint8_t* buffer, size_t buf_len); + + Client* client; + HttpClient* http_client; + + const char *username, *password; + + enum OTADownloadState: uint8_t { + OtaDownloadHeader, + OtaDownloadFile, + OtaDownloadCompleted, + OtaDownloadMagicNumberMismatch, + OtaDownloadError + }; + +protected: + struct Context { + Context( + const char* url, + std::function putc); + + ParsedUrl parsed_url; + ota::OTAHeader header; + OTADownloadState downloadState; + uint32_t calculatedCrc32; + uint32_t headerCopiedBytes; + uint32_t downloadedSize; + uint32_t lastReportTime; + + // LZSS decoder + LZSSDecoder decoder; + + const size_t buf_len = 64; + uint8_t buffer[64]; + } *context; +}; + +#endif /* OTA_ENABLED && ! defined(OFFLOADED_DOWNLOAD) */ From a8a3bf30d31ed81efb3cf41fd5792f528517c2df Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 21 Feb 2024 17:28:04 +0100 Subject: [PATCH 08/18] renamed files containing implementation of OTA HAL functions --- src/utility/ota/{OTA-esp32.cpp => OTAEsp32.cpp} | 0 src/utility/ota/{OTA-nano-rp2040.cpp => OTANanoRP2040.cpp} | 0 src/utility/ota/{OTA-portenta-h7.cpp => OTAPortentaH7.cpp} | 0 src/utility/ota/{OTA-samd.cpp => OTASamd.cpp} | 0 src/utility/ota/{OTA-unor4.cpp => OTAUnoR4.cpp} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename src/utility/ota/{OTA-esp32.cpp => OTAEsp32.cpp} (100%) rename src/utility/ota/{OTA-nano-rp2040.cpp => OTANanoRP2040.cpp} (100%) rename src/utility/ota/{OTA-portenta-h7.cpp => OTAPortentaH7.cpp} (100%) rename src/utility/ota/{OTA-samd.cpp => OTASamd.cpp} (100%) rename src/utility/ota/{OTA-unor4.cpp => OTAUnoR4.cpp} (100%) diff --git a/src/utility/ota/OTA-esp32.cpp b/src/utility/ota/OTAEsp32.cpp similarity index 100% rename from src/utility/ota/OTA-esp32.cpp rename to src/utility/ota/OTAEsp32.cpp diff --git a/src/utility/ota/OTA-nano-rp2040.cpp b/src/utility/ota/OTANanoRP2040.cpp similarity index 100% rename from src/utility/ota/OTA-nano-rp2040.cpp rename to src/utility/ota/OTANanoRP2040.cpp diff --git a/src/utility/ota/OTA-portenta-h7.cpp b/src/utility/ota/OTAPortentaH7.cpp similarity index 100% rename from src/utility/ota/OTA-portenta-h7.cpp rename to src/utility/ota/OTAPortentaH7.cpp diff --git a/src/utility/ota/OTA-samd.cpp b/src/utility/ota/OTASamd.cpp similarity index 100% rename from src/utility/ota/OTA-samd.cpp rename to src/utility/ota/OTASamd.cpp diff --git a/src/utility/ota/OTA-unor4.cpp b/src/utility/ota/OTAUnoR4.cpp similarity index 100% rename from src/utility/ota/OTA-unor4.cpp rename to src/utility/ota/OTAUnoR4.cpp From a6a2e2316aa7e47e54b895548c30766122196c90 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Mon, 4 Mar 2024 15:47:30 +0100 Subject: [PATCH 09/18] added stub implementation of OTA HAL classes --- src/ota/implementation/OTAEsp32.cpp | 3 + src/ota/implementation/OTAEsp32.h | 20 ++ src/ota/implementation/OTANanoRP2040.cpp | 4 + src/ota/implementation/OTANanoRP2040.h | 23 ++ src/ota/implementation/OTASTM32H7.h | 23 ++ src/ota/implementation/OTASTM32H7.ipp | 32 +++ src/ota/implementation/OTASamd.cpp | 4 + src/ota/implementation/OTASamd.h | 21 ++ src/ota/implementation/OTAUnoR4.cpp | 4 + src/ota/implementation/OTAUnoR4.h | 22 ++ src/utility/ota/OTAEsp32.cpp | 128 ----------- src/utility/ota/OTANanoRP2040.cpp | 266 ----------------------- src/utility/ota/OTAPortentaH7.cpp | 140 ------------ src/utility/ota/OTASamd.cpp | 97 --------- src/utility/ota/OTAUnoR4.cpp | 187 ---------------- 15 files changed, 156 insertions(+), 818 deletions(-) create mode 100644 src/ota/implementation/OTAEsp32.cpp create mode 100644 src/ota/implementation/OTAEsp32.h create mode 100644 src/ota/implementation/OTANanoRP2040.cpp create mode 100644 src/ota/implementation/OTANanoRP2040.h create mode 100644 src/ota/implementation/OTASTM32H7.h create mode 100644 src/ota/implementation/OTASTM32H7.ipp create mode 100644 src/ota/implementation/OTASamd.cpp create mode 100644 src/ota/implementation/OTASamd.h create mode 100644 src/ota/implementation/OTAUnoR4.cpp create mode 100644 src/ota/implementation/OTAUnoR4.h delete mode 100644 src/utility/ota/OTAEsp32.cpp delete mode 100644 src/utility/ota/OTANanoRP2040.cpp delete mode 100644 src/utility/ota/OTAPortentaH7.cpp delete mode 100644 src/utility/ota/OTASamd.cpp delete mode 100644 src/utility/ota/OTAUnoR4.cpp diff --git a/src/ota/implementation/OTAEsp32.cpp b/src/ota/implementation/OTAEsp32.cpp new file mode 100644 index 00000000..7ac03613 --- /dev/null +++ b/src/ota/implementation/OTAEsp32.cpp @@ -0,0 +1,3 @@ +#if defined(ARDUINO_ARCH_ESP32) && OTA_ENABLED + +#endif // defined(ARDUINO_ARCH_ESP32) && OTA_ENABLED \ No newline at end of file diff --git a/src/ota/implementation/OTAEsp32.h b/src/ota/implementation/OTAEsp32.h new file mode 100644 index 00000000..8654d2c7 --- /dev/null +++ b/src/ota/implementation/OTAEsp32.h @@ -0,0 +1,20 @@ +#pragma once + +#include "src/ota/interface/OTAInterface.h" + +class ESP32OTACloudProcess: public OTACloudProcessInterface { +public: + STM32H7OTACloudProcess(); +protected: + // we start the download and decompress process + virtual State fetch(Message* msg=nullptr); + + // when the download is completed we verify for integrity and correctness of the downloaded binary + // virtual State verifyOTA(Message* msg=nullptr); // TODO this may be performed inside download + + // whene the download is correctly finished we set the mcu to use the newly downloaded binary + virtual State flashOTA(Message* msg=nullptr); + + // we reboot the device + virtual State reboot(Message* msg=nullptr); +}; diff --git a/src/ota/implementation/OTANanoRP2040.cpp b/src/ota/implementation/OTANanoRP2040.cpp new file mode 100644 index 00000000..e0f6feb7 --- /dev/null +++ b/src/ota/implementation/OTANanoRP2040.cpp @@ -0,0 +1,4 @@ +#if defined(ARDUINO_NANO_RP2040_CONNECT) && OTA_ENABLED +#include "OTANanoRP2040.h" + +#endif // defined(ARDUINO_NANO_RP2040_CONNECT) && OTA_ENABLED \ No newline at end of file diff --git a/src/ota/implementation/OTANanoRP2040.h b/src/ota/implementation/OTANanoRP2040.h new file mode 100644 index 00000000..165bb1ce --- /dev/null +++ b/src/ota/implementation/OTANanoRP2040.h @@ -0,0 +1,23 @@ +#pragma once + +#include "src/ota/interface/OTAInterface.h" + +#include "FATFileSystem.h" +#include "FlashIAPBlockDevice.h" + +class NANO_RP2040OTACloudProcess: public OTACloudProcessInterface { +public: + STM32H7OTACloudProcess(); +protected: + // we start the download and decompress process + virtual State fetch(Message* msg=nullptr); + + // when the download is completed we verify for integrity and correctness of the downloaded binary + // virtual State verifyOTA(Message* msg=nullptr); // TODO this may be performed inside download + + // whene the download is correctly finished we set the mcu to use the newly downloaded binary + virtual State flashOTA(Message* msg=nullptr); + + // we reboot the device + virtual State reboot(Message* msg=nullptr); +}; diff --git a/src/ota/implementation/OTASTM32H7.h b/src/ota/implementation/OTASTM32H7.h new file mode 100644 index 00000000..455066e3 --- /dev/null +++ b/src/ota/implementation/OTASTM32H7.h @@ -0,0 +1,23 @@ +#pragma once + +#include "src/ota/interface/OTAInterface.h" + +class STM32H7OTACloudProcess: public OTACloudProcessInterface { +public: + STM32H7OTACloudProcess(); + void update(); + + // retrocompatibility functions used in old ota prtotocol based on properties + int otaRequest(char const * ota_url, NetworkAdapter iface); + String getOTAImageSHA256(); + bool isOTACapable(); +protected: + // we start the download and decompress process + virtual State fetch(Message* msg=nullptr); + + // whene the download is correctly finished we set the mcu to use the newly downloaded binary + virtual State flashOTA(Message* msg=nullptr); + + // we reboot the device + virtual State reboot(Message* msg=nullptr); +}; diff --git a/src/ota/implementation/OTASTM32H7.ipp b/src/ota/implementation/OTASTM32H7.ipp new file mode 100644 index 00000000..09093a30 --- /dev/null +++ b/src/ota/implementation/OTASTM32H7.ipp @@ -0,0 +1,32 @@ +#if defined(BOARD_STM32H7) && OTA_ENABLED +#include "OTAPortentaH7.h" + +STM32H7OTACloudProcess::STM32H7OTACloudProcess() {} + +void STM32H7OTACloudProcess::update() { + OTACloudProcessInterface::update(); + watchdog_reset(); +} + +State STM32H7OTACloudProcess::fetch(Message *msg) { + +} + +State STM32H7OTACloudProcess::flashOTA(Message *msg) { + /* Schedule the firmware update. */ + if((ota_portenta_err = ota_portenta_qspi.update()) != Arduino_Portenta_OTA::Error::None) { + DEBUG_ERROR("Arduino_Portenta_OTA_QSPI::update() failed with %d", static_cast(ota_portenta_err)); + return static_cast(ota_portenta_err); + } +} + +State STM32H7OTACloudProcess::reboot(Message *msg) { + // TODO save information about the progress reached in the ota + + // This command reboots the mcu + NVIC_SystemReset(); + + return Resume; // This won't ever be reached +} + +#endif // defined(BOARD_STM32H7) && OTA_ENABLED \ No newline at end of file diff --git a/src/ota/implementation/OTASamd.cpp b/src/ota/implementation/OTASamd.cpp new file mode 100644 index 00000000..1dc307f4 --- /dev/null +++ b/src/ota/implementation/OTASamd.cpp @@ -0,0 +1,4 @@ +#if defined(ARDUINO_ARCH_SAMD) && OTA_ENABLED +#include "OTASamd.h" + +#endif // defined(ARDUINO_ARCH_SAMD) && OTA_ENABLED \ No newline at end of file diff --git a/src/ota/implementation/OTASamd.h b/src/ota/implementation/OTASamd.h new file mode 100644 index 00000000..3c248dd1 --- /dev/null +++ b/src/ota/implementation/OTASamd.h @@ -0,0 +1,21 @@ +#pragma once + +#include "src/ota/interface/OTAInterface.h" +#include + +class SAMDOTACloudProcess: public OTACloudProcessInterface { +public: + STM32H7OTACloudProcess(); +protected: + // we start the download and decompress process + virtual State fetch(Message* msg=nullptr); + + // when the download is completed we verify for integrity and correctness of the downloaded binary + // virtual State verifyOTA(Message* msg=nullptr); // TODO this may be performed inside download + + // whene the download is correctly finished we set the mcu to use the newly downloaded binary + virtual State flashOTA(Message* msg=nullptr); + + // we reboot the device + virtual State reboot(Message* msg=nullptr); +}; diff --git a/src/ota/implementation/OTAUnoR4.cpp b/src/ota/implementation/OTAUnoR4.cpp new file mode 100644 index 00000000..e44e7f65 --- /dev/null +++ b/src/ota/implementation/OTAUnoR4.cpp @@ -0,0 +1,4 @@ +#if defined(ARDUINO_UNOR4_WIFI) && OTA_ENABLED +#include "OTAUnoR4.h" + +#endif // defined(ARDUINO_UNOR4_WIFI) && OTA_ENABLED diff --git a/src/ota/implementation/OTAUnoR4.h b/src/ota/implementation/OTAUnoR4.h new file mode 100644 index 00000000..871ea58a --- /dev/null +++ b/src/ota/implementation/OTAUnoR4.h @@ -0,0 +1,22 @@ +#pragma once + +#include "src/ota/interface/OTAInterface.h" +#include "OTAUpdate.h" +#include "r_flash_lp.h" + +class UNOR4OTACloudProcess: public OTACloudProcessInterface { +public: + STM32H7OTACloudProcess(); +protected: + // we start the download and decompress process + virtual State fetch(Message* msg=nullptr); + + // when the download is completed we verify for integrity and correctness of the downloaded binary + // virtual State verifyOTA(Message* msg=nullptr); // TODO this may be performed inside download + + // whene the download is correctly finished we set the mcu to use the newly downloaded binary + virtual State flashOTA(Message* msg=nullptr); + + // we reboot the device + virtual State reboot(Message* msg=nullptr); +}; diff --git a/src/utility/ota/OTAEsp32.cpp b/src/utility/ota/OTAEsp32.cpp deleted file mode 100644 index 51065d1b..00000000 --- a/src/utility/ota/OTAEsp32.cpp +++ /dev/null @@ -1,128 +0,0 @@ -/* - 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; -} - -bool esp32_isOTACapable() -{ - return Arduino_ESP32_OTA::isCapable(); -} - -#endif /* ARDUINO_ARCH_ESP32 */ diff --git a/src/utility/ota/OTANanoRP2040.cpp b/src/utility/ota/OTANanoRP2040.cpp deleted file mode 100644 index 4e3c1d04..00000000 --- a/src/utility/ota/OTANanoRP2040.cpp +++ /dev/null @@ -1,266 +0,0 @@ -/* - 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. -*/ - -#if defined(ARDUINO_NANO_RP2040_CONNECT) - -/****************************************************************************** - * INCLUDE - ******************************************************************************/ - -#include "OTA.h" - -#include "../watchdog/Watchdog.h" - -#include - -#include - -#include "mbed.h" -#include "FATFileSystem.h" -#include "FlashIAPBlockDevice.h" -#include "utility/ota/FlashSHA256.h" - -/****************************************************************************** - * FUNCTION DEFINITION - ******************************************************************************/ - -/* Original code: http://stackoverflow.com/questions/2616011/easy-way-to-parse-a-url-in-c-cross-platform */ -#include -#include -#include -#include -#include - -struct URI { - public: - URI(const std::string& url_s) { - this->parse(url_s); - } - std::string protocol_, host_, path_, query_; - private: - void parse(const std::string& url_s); -}; - -using namespace std; - -// ctors, copy, equality, ... -// TODO: change me into something embedded friendly (this function adds ~100KB to flash) -void URI::parse(const string& url_s) -{ - const string prot_end("://"); - string::const_iterator prot_i = search(url_s.begin(), url_s.end(), - prot_end.begin(), prot_end.end()); - protocol_.reserve(distance(url_s.begin(), prot_i)); - transform(url_s.begin(), prot_i, - back_inserter(protocol_), - ptr_fun(tolower)); // protocol is icase - if( prot_i == url_s.end() ) - return; - advance(prot_i, prot_end.length()); - string::const_iterator path_i = find(prot_i, url_s.end(), '/'); - host_.reserve(distance(prot_i, path_i)); - transform(prot_i, path_i, - back_inserter(host_), - ptr_fun(tolower)); // host is icase - string::const_iterator query_i = find(path_i, url_s.end(), '?'); - path_.assign(path_i, query_i); - if( query_i != url_s.end() ) - ++query_i; - query_.assign(query_i, url_s.end()); -} - -int rp2040_connect_onOTARequest(char const * ota_url) -{ - watchdog_reset(); - - int err = -1; - FlashIAPBlockDevice flash(XIP_BASE + 0xF00000, 0x100000); - if ((err = flash.init()) < 0) - { - DEBUG_ERROR("%s: flash.init() failed with %d", __FUNCTION__, err); - return static_cast(OTAError::RP2040_ErrorFlashInit); - } - - watchdog_reset(); - - flash.erase(XIP_BASE + 0xF00000, 0x100000); - - watchdog_reset(); - - mbed::FATFileSystem fs("ota"); - if ((err = fs.reformat(&flash)) != 0) - { - DEBUG_ERROR("%s: fs.reformat() failed with %d", __FUNCTION__, err); - return static_cast(OTAError::RP2040_ErrorReformat); - } - - watchdog_reset(); - - FILE * file = fopen("/ota/UPDATE.BIN.LZSS", "wb"); - if (!file) - { - DEBUG_ERROR("%s: fopen() failed", __FUNCTION__); - fclose(file); - return static_cast(OTAError::RP2040_ErrorOpenUpdateFile); - } - - watchdog_reset(); - - URI url(ota_url); - Client * client = nullptr; - int port = 0; - - if (url.protocol_ == "http") { - client = new WiFiClient(); - port = 80; - } else if (url.protocol_ == "https") { - client = new WiFiSSLClient(); - port = 443; - } else { - DEBUG_ERROR("%s: Failed to parse OTA URL %s", __FUNCTION__, ota_url); - fclose(file); - return static_cast(OTAError::RP2040_UrlParseError); - } - - watchdog_reset(); - - if (!client->connect(url.host_.c_str(), port)) - { - DEBUG_ERROR("%s: Connection failure with OTA storage server %s", __FUNCTION__, url.host_.c_str()); - fclose(file); - return static_cast(OTAError::RP2040_ServerConnectError); - } - - watchdog_reset(); - - client->println(String("GET ") + url.path_.c_str() + " HTTP/1.1"); - client->println(String("Host: ") + url.host_.c_str()); - client->println("Connection: close"); - client->println(); - - watchdog_reset(); - - /* Receive HTTP header. */ - String http_header; - bool is_header_complete = false, - is_http_header_timeout = false; - for (unsigned long const start = millis(); !is_header_complete;) - { - is_http_header_timeout = (millis() - start) > AIOT_CONFIG_RP2040_OTA_HTTP_HEADER_RECEIVE_TIMEOUT_ms; - if (is_http_header_timeout) break; - - watchdog_reset(); - - if (client->available()) - { - char const c = client->read(); - - http_header += c; - if (http_header.endsWith("\r\n\r\n")) - is_header_complete = true; - } - } - - if (!is_header_complete) - { - DEBUG_ERROR("%s: Error receiving HTTP header %s", __FUNCTION__, is_http_header_timeout ? "(timeout)":""); - fclose(file); - return static_cast(OTAError::RP2040_HttpHeaderError); - } - - /* Extract concent length from HTTP header. A typical entry looks like - * "Content-Length: 123456" - */ - char const * content_length_ptr = strstr(http_header.c_str(), "Content-Length"); - if (!content_length_ptr) - { - DEBUG_ERROR("%s: Failure to extract content length from http header", __FUNCTION__); - fclose(file); - return static_cast(OTAError::RP2040_ErrorParseHttpHeader); - } - /* Find start of numerical value. */ - char * ptr = const_cast(content_length_ptr); - for (; (*ptr != '\0') && !isDigit(*ptr); ptr++) { } - /* Extract numerical value. */ - String content_length_str; - for (; isDigit(*ptr); ptr++) content_length_str += *ptr; - int const content_length_val = atoi(content_length_str.c_str()); - DEBUG_VERBOSE("%s: Length of OTA binary according to HTTP header = %d bytes", __FUNCTION__, content_length_val); - - /* Receive as many bytes as are indicated by the HTTP header - or die trying. */ - int bytes_received = 0; - bool is_http_data_timeout = false; - for(unsigned long const start = millis(); bytes_received < content_length_val;) - { - is_http_data_timeout = (millis() - start) > AIOT_CONFIG_RP2040_OTA_HTTP_DATA_RECEIVE_TIMEOUT_ms; - if (is_http_data_timeout) break; - - watchdog_reset(); - - if (client->available()) - { - char const c = client->read(); - - if (fwrite(&c, 1, sizeof(c), file) != sizeof(c)) - { - DEBUG_ERROR("%s: Writing of firmware image to flash failed", __FUNCTION__); - fclose(file); - return static_cast(OTAError::RP2040_ErrorWriteUpdateFile); - } - - bytes_received++; - } - } - - if (bytes_received != content_length_val) { - DEBUG_ERROR("%s: Error receiving HTTP data %s (%d bytes received, %d expected)", __FUNCTION__, is_http_data_timeout ? "(timeout)":"", bytes_received, content_length_val); - fclose(file); - return static_cast(OTAError::RP2040_HttpDataError); - } - - DEBUG_INFO("%s: %d bytes received", __FUNCTION__, ftell(file)); - fclose(file); - - /* Unmount the filesystem. */ - if ((err = fs.unmount()) != 0) - { - DEBUG_ERROR("%s: fs.unmount() failed with %d", __FUNCTION__, err); - return static_cast(OTAError::RP2040_ErrorUnmount); - } - - /* Perform the reset to reboot to SFU. */ - mbed_watchdog_trigger_reset(); - /* If watchdog is enabled we should not reach this point */ - NVIC_SystemReset(); - - 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); -} - -bool rp2040_connect_isOTACapable() -{ - return true; -} - -#endif /* ARDUINO_NANO_RP2040_CONNECT */ diff --git a/src/utility/ota/OTAPortentaH7.cpp b/src/utility/ota/OTAPortentaH7.cpp deleted file mode 100644 index 1d7ecf3a..00000000 --- a/src/utility/ota/OTAPortentaH7.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* - 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 - -#ifdef BOARD_STM32H7 - -#include "OTA.h" - -#include -#include -#include - -#include - -#include "tls/utility/SHA256.h" - -#include "../watchdog/Watchdog.h" - -/****************************************************************************** - * EXTERN - ******************************************************************************/ - -extern RTC_HandleTypeDef RTCHandle; - -/****************************************************************************** - * FUNCTION DEFINITION - ******************************************************************************/ - -int portenta_h7_onOTARequest(char const * ota_url, NetworkAdapter iface) -{ - watchdog_reset(); - - Arduino_Portenta_OTA::Error ota_portenta_err = Arduino_Portenta_OTA::Error::None; - /* Use 2nd partition of QSPI (1st partition contains WiFi firmware) */ - Arduino_Portenta_OTA_QSPI ota_portenta_qspi(QSPI_FLASH_FATFS_MBR, 2); - -#if defined (ARDUINO_PORTENTA_OTA_HAS_WATCHDOG_FEED) - ota_portenta_qspi.setFeedWatchdogFunc(watchdog_reset); -#endif - - watchdog_reset(); - - /* Initialize the QSPI memory for OTA handling. */ - if((ota_portenta_err = ota_portenta_qspi.begin()) != Arduino_Portenta_OTA::Error::None) { - DEBUG_ERROR("Arduino_Portenta_OTA_QSPI::begin() failed with %d", static_cast(ota_portenta_err)); - return static_cast(ota_portenta_err); - } - - watchdog_reset(); - - /* Just to be safe delete any remains from previous updates. */ - remove("/fs/UPDATE.BIN"); - remove("/fs/UPDATE.BIN.LZSS"); - - watchdog_reset(); - - /* Download the OTA file from the web storage location. */ - MbedSocketClass * download_socket = static_cast(&WiFi); -#if defined (BOARD_HAS_ETHERNET) - if(iface == NetworkAdapter::ETHERNET) { - download_socket = static_cast(&Ethernet); - } -#endif - int const ota_portenta_qspi_download_ret_code = ota_portenta_qspi.downloadAndDecompress(ota_url, true /* is_https */, download_socket); - DEBUG_VERBOSE("Arduino_Portenta_OTA_QSPI::download(%s) returns %d", ota_url, ota_portenta_qspi_download_ret_code); - - watchdog_reset(); - - /* Schedule the firmware update. */ - if((ota_portenta_err = ota_portenta_qspi.update()) != Arduino_Portenta_OTA::Error::None) { - DEBUG_ERROR("Arduino_Portenta_OTA_QSPI::update() failed with %d", static_cast(ota_portenta_err)); - return static_cast(ota_portenta_err); - } - - /* Perform the reset to reboot - then the bootloader performs the actual application update. */ - 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; -} - -bool portenta_h7_isOTACapable() -{ - return Arduino_Portenta_OTA::isOtaCapable(); -} - -#endif /* BOARD_STM32H7 */ diff --git a/src/utility/ota/OTASamd.cpp b/src/utility/ota/OTASamd.cpp deleted file mode 100644 index 4ef214f3..00000000 --- a/src/utility/ota/OTASamd.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* - 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_SAMD) && OTA_ENABLED - -#include "OTA.h" -#include -#include "../watchdog/Watchdog.h" -#include "utility/ota/FlashSHA256.h" - -#if OTA_STORAGE_SNU -# include -# include /* WiFiStorage */ -#endif - -/****************************************************************************** - * FUNCTION DEFINITION - ******************************************************************************/ - -int samd_onOTARequest(char const * ota_url) -{ - watchdog_reset(); - -#if OTA_STORAGE_SNU - /* Just to be safe delete any remains from previous updates. */ - WiFiStorage.remove("/fs/UPDATE.BIN.LZSS"); - WiFiStorage.remove("/fs/UPDATE.BIN.LZSS.TMP"); - - watchdog_reset(); - - /* Trigger direct download to NINA module. */ - uint8_t nina_ota_err_code = 0; - if (!WiFiStorage.downloadOTA(ota_url, &nina_ota_err_code)) - { - DEBUG_ERROR("ArduinoIoTCloudTCP::%s error download to nina: %d", __FUNCTION__, nina_ota_err_code); - return static_cast(OTAError::DownloadFailed); - } - - /* Perform the reset to reboot to SxU. */ - NVIC_SystemReset(); - - return static_cast(OTAError::None); -#endif /* OTA_STORAGE_SNU */ - - (void)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); -} - -bool samd_isOTACapable() -{ -#if OTA_STORAGE_SNU - if (String(WiFi.firmwareVersion()) < String("1.4.1")) { - DEBUG_WARNING("ArduinoIoTCloudTCP::%s In order to be ready for cloud OTA, NINA firmware needs to be >= 1.4.1, current %s", __FUNCTION__, WiFi.firmwareVersion()); - return false; - } else { - return true; - } -#endif - return false; -} - -#endif /* ARDUINO_ARCH_SAMD */ diff --git a/src/utility/ota/OTAUnoR4.cpp b/src/utility/ota/OTAUnoR4.cpp deleted file mode 100644 index d446476c..00000000 --- a/src/utility/ota/OTAUnoR4.cpp +++ /dev/null @@ -1,187 +0,0 @@ -/* - 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_UNOR4_WIFI && OTA_ENABLED - -#include "OTAUpdate.h" -#include -#include "tls/utility/SHA256.h" -#include "fsp_common_api.h" -#include "r_flash_lp.h" -#include "WiFiS3.h" - -/****************************************************************************** - * DEFINES - ******************************************************************************/ - -/* Key code for writing PRCR register. */ -#define BSP_PRV_PRCR_KEY (0xA500U) -#define BSP_PRV_PRCR_PRC1_UNLOCK ((BSP_PRV_PRCR_KEY) | 0x2U) -#define BSP_PRV_PRCR_LOCK ((BSP_PRV_PRCR_KEY) | 0x0U) - -#define OTA_MAGIC (*((volatile uint16_t *) &R_SYSTEM->VBTBKR[4])) -#define OTA_SIZE (*((volatile uint32_t *) &R_SYSTEM->VBTBKR[6])) - -/****************************************************************************** - * FUNCTION DEFINITION - ******************************************************************************/ - -static void unor4_setOTASize(uint32_t size) -{ - R_SYSTEM->PRCR = (uint16_t) BSP_PRV_PRCR_PRC1_UNLOCK; - OTA_MAGIC = 0x07AA; - OTA_SIZE = size; - R_SYSTEM->PRCR = (uint16_t) BSP_PRV_PRCR_LOCK; -} - -static uint32_t unor4_getOTASize() -{ - if (OTA_MAGIC == 0x07AA) - { - return OTA_SIZE; - } - return 0; -} - -static int unor4_codeFlashOpen(flash_lp_instance_ctrl_t * ctrl) -{ - flash_cfg_t cfg; - - cfg.data_flash_bgo = false; - cfg.p_callback = nullptr; - cfg.p_context = nullptr; - cfg.p_extend = nullptr; - cfg.ipl = (BSP_IRQ_DISABLED); - cfg.irq = FSP_INVALID_VECTOR; - cfg.err_ipl = (BSP_IRQ_DISABLED); - cfg.err_irq = FSP_INVALID_VECTOR; - - fsp_err_t rv = FSP_ERR_UNSUPPORTED; - - rv = R_FLASH_LP_Open(ctrl,&cfg); - return rv; -} - -static int unor4_codeFlashClose(flash_lp_instance_ctrl_t * ctrl) -{ - fsp_err_t rv = FSP_ERR_UNSUPPORTED; - - rv = R_FLASH_LP_Close(ctrl); - return rv; -} - -int unor4_onOTARequest(char const * ota_url) -{ - int ota_err = static_cast(OTAUpdate::Error::None); - OTAUpdate ota; - - /* Initialize the board for OTA handling. */ - if ((ota_err = static_cast(ota.begin("/update.bin"))) != static_cast(OTAUpdate::Error::None)) - { - DEBUG_ERROR("OTAUpdate::begin() failed with %d", ota_err); - return ota_err; - } - - /* Download the OTA file from the web storage location. */ - int const ota_download = ota.download(ota_url,"/update.bin"); - if (ota_download <= 0) - { - DEBUG_ERROR("OTAUpdate::download() failed with %d", ota_download); - return ota_download; - } - DEBUG_VERBOSE("OTAUpdate::download() %d bytes downloaded", ota_download); - - /* Verify update integrity */ - if ((ota_err = static_cast(ota.verify())) != static_cast(OTAUpdate::Error::None)) - { - DEBUG_ERROR("OTAUpdate::verify() failed with %d", ota_err); - return ota_err; - } - - /* Store update size and write OTA magin number */ - unor4_setOTASize(ota_download); - - /* Flash new firmware */ - if ((ota_err = static_cast(ota.update("/update.bin"))) != static_cast(OTAUpdate::Error::None)) - { - DEBUG_ERROR("OTAUpdate::update() failed with %d", ota_err); - return ota_err; - } - - return static_cast(OTAUpdate::Error::None); -} - -String unor4_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 = 0x4000; - uint32_t const app_size = unor4_getOTASize(); - - flash_lp_instance_ctrl_t ctrl; - unor4_codeFlashOpen(&ctrl); - - 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)); - } - - unor4_codeFlashClose(&ctrl); - - /* 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_ERROR("SHA256: %d bytes (of %d) read", bytes_read, app_size); - return sha256_str; -} - -bool unor4_isOTACapable() -{ - /* check firmware version */ - String const fv = WiFi.firmwareVersion(); - if (fv < String("0.3.0")) { - return false; - } - return true; -} - -#endif /* ARDUINO_UNOR4_WIFI */ From 30d665ab57ff099203174524b61f94b087bbe9b7 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Mon, 25 Mar 2024 13:53:21 +0100 Subject: [PATCH 10/18] implemented stm32h7 ota class --- src/ota/implementation/OTASTM32H7.cpp | 215 ++++++++++++++++++++++++++ src/ota/implementation/OTASTM32H7.h | 90 +++++++++-- src/ota/implementation/OTASTM32H7.ipp | 32 ---- 3 files changed, 293 insertions(+), 44 deletions(-) create mode 100644 src/ota/implementation/OTASTM32H7.cpp delete mode 100644 src/ota/implementation/OTASTM32H7.ipp diff --git a/src/ota/implementation/OTASTM32H7.cpp b/src/ota/implementation/OTASTM32H7.cpp new file mode 100644 index 00000000..b1cbe701 --- /dev/null +++ b/src/ota/implementation/OTASTM32H7.cpp @@ -0,0 +1,215 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include "AIoTC_Config.h" +#if defined(BOARD_STM32H7) && OTA_ENABLED +#include "OTASTM32H7.h" + +#include "utility/watchdog/Watchdog.h" +#include + +static bool findProgramLength(DIR * dir, uint32_t & program_length); + +const char STM32H7OTACloudProcess::UPDATE_FILE_NAME[] = "/fs/UPDATE.BIN"; + +STM32H7OTACloudProcess::STM32H7OTACloudProcess(MessageStream *ms, Client* client) +: OTADefaultCloudProcessInterface(ms, client) +, decompressed(nullptr) +, _bd_raw_qspi(nullptr) +, _program_length(0) +, _bd(nullptr) +, _fs(nullptr) { + +} + +STM32H7OTACloudProcess::~STM32H7OTACloudProcess() { + if(decompressed != nullptr) { + fclose(decompressed); + decompressed = nullptr; + } + + storageClean(); +} + +OTACloudProcessInterface::State STM32H7OTACloudProcess::resume(Message* msg) { + return OtaBegin; +} + +void STM32H7OTACloudProcess::update() { + OTADefaultCloudProcessInterface::update(); + watchdog_reset(); // FIXME this should npot be performed here +} + +int STM32H7OTACloudProcess::writeFlash(uint8_t* const buffer, size_t len) { + if (decompressed == nullptr) { + return -1; + } + return fwrite(buffer, sizeof(uint8_t), len, decompressed); +} + +OTACloudProcessInterface::State STM32H7OTACloudProcess::startOTA() { + if (!isOtaCapable()) { + return NoCapableBootloaderFail; + } + + /* Initialize the QSPI memory for OTA handling. */ + if (!storageInit()) { + return OtaStorageInitFail; + } + + // this could be useless, since we are writing over it + remove(UPDATE_FILE_NAME); + + decompressed = fopen(UPDATE_FILE_NAME, "wb"); + + // start the download if the setup for ota storage is successful + return OTADefaultCloudProcessInterface::startOTA(); +} + + +OTACloudProcessInterface::State STM32H7OTACloudProcess::flashOTA() { + fclose(decompressed); + decompressed = nullptr; + + /* Schedule the firmware update. */ + if(!storageOpen()) { + return OtaStorageOpenFail; + } + + // this sets the registries in RTC to load the firmware from the storage selected at the next reboot + STM32H747::writeBackupRegister(RTCBackup::DR0, 0x07AA); + STM32H747::writeBackupRegister(RTCBackup::DR1, storage); + STM32H747::writeBackupRegister(RTCBackup::DR2, data_offset); + STM32H747::writeBackupRegister(RTCBackup::DR3, _program_length); + + return Reboot; +} + +OTACloudProcessInterface::State STM32H7OTACloudProcess::reboot() { + // TODO save information about the progress reached in the ota + + // This command reboots the mcu + NVIC_SystemReset(); + + return Resume; // This won't ever be reached +} + +void STM32H7OTACloudProcess::reset() { + OTADefaultCloudProcessInterface::reset(); + + remove(UPDATE_FILE_NAME); + + storageClean(); +} + +void STM32H7OTACloudProcess::storageClean() { + DEBUG_VERBOSE(F("storage clean")); + + if(decompressed != nullptr) { + fclose(decompressed); + decompressed = nullptr; + } + + if(_fs != nullptr) { + _fs->unmount(); + delete _fs; + _fs = nullptr; + } + + if(_bd != nullptr) { + delete _bd; + _bd = nullptr; + } +} + +bool STM32H7OTACloudProcess::isOtaCapable() { + #define BOOTLOADER_ADDR (0x8000000) + uint32_t bootloader_data_offset = 0x1F000; + uint8_t* bootloader_data = (uint8_t*)(BOOTLOADER_ADDR + bootloader_data_offset); + uint8_t currentBootloaderVersion = bootloader_data[1]; + if (currentBootloaderVersion < 22) + return false; + else + return true; +} + +bool STM32H7OTACloudProcess::storageInit() { + int err_mount=1; + + if(_bd_raw_qspi == nullptr) { + _bd_raw_qspi = mbed::BlockDevice::get_default_instance(); + + if (_bd_raw_qspi->init() != QSPIF_BD_ERROR_OK) { + DEBUG_VERBOSE(F("Error: QSPI init failure.")); + return false; + } + } + + if (storage == portenta::QSPI_FLASH_FATFS) { + _fs = new mbed::FATFileSystem("fs"); + err_mount = _fs->mount(_bd_raw_qspi); + } else if (storage == portenta::QSPI_FLASH_FATFS_MBR) { + _bd = new mbed::MBRBlockDevice(_bd_raw_qspi, data_offset); + _fs = new mbed::FATFileSystem("fs"); + err_mount = _fs->mount(_bd); + } + + if (!err_mount) { + return true; + } + DEBUG_VERBOSE(F("Error while mounting the filesystem. Err = %d"), err_mount); + return false; +} + +bool STM32H7OTACloudProcess::storageOpen() { + DIR * dir = NULL; + if ((dir = opendir("/fs")) != NULL) + { + if (findProgramLength(dir, _program_length)) + { + closedir(dir); + return true; + } + closedir(dir); + } + + return false; +} + +bool findProgramLength(DIR * dir, uint32_t & program_length) { + struct dirent * entry = NULL; + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, "UPDATE.BIN") == 0) { // FIXME use constants + struct stat stat_buf; + stat("/fs/UPDATE.BIN", &stat_buf); + program_length = stat_buf.st_size; + return true; + } + } + + return false; +} + +// extern uint32_t __stext = ~0; +extern uint32_t __etext; +extern uint32_t _sdata; +extern uint32_t _edata; + +void* STM32H7OTACloudProcess::appStartAddress() { + return (void*)0x8040000; + // return &__stext; +} + +uint32_t STM32H7OTACloudProcess::appSize() { + return ((&__etext - (uint32_t*)appStartAddress()) + (&_edata - &_sdata))*sizeof(void*); +} + + +#endif // defined(BOARD_STM32H7) && OTA_ENABLED \ No newline at end of file diff --git a/src/ota/implementation/OTASTM32H7.h b/src/ota/implementation/OTASTM32H7.h index 455066e3..ac102171 100644 --- a/src/ota/implementation/OTASTM32H7.h +++ b/src/ota/implementation/OTASTM32H7.h @@ -1,23 +1,89 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + #pragma once +#include "ota/interface/OTAInterfaceDefault.h" + +#include + +#include +#include +#include +#include -#include "src/ota/interface/OTAInterface.h" +#include "WiFi.h" /* WiFi from ArduinoCore-mbed */ +#include -class STM32H7OTACloudProcess: public OTACloudProcessInterface { +#define APOTA_QSPI_FLASH_FLAG (1 << 2) +#define APOTA_SDCARD_FLAG (1 << 3) +#define APOTA_RAW_FLAG (1 << 4) +#define APOTA_FATFS_FLAG (1 << 5) +#define APOTA_LITTLEFS_FLAG (1 << 6) +#define APOTA_MBR_FLAG (1 << 7) + +namespace portenta { + enum StorageType { + QSPI_FLASH_FATFS = APOTA_QSPI_FLASH_FLAG | APOTA_FATFS_FLAG, + QSPI_FLASH_FATFS_MBR = APOTA_QSPI_FLASH_FLAG | APOTA_FATFS_FLAG | APOTA_MBR_FLAG, + SD_FATFS = APOTA_SDCARD_FLAG | APOTA_FATFS_FLAG, + SD_FATFS_MBR = APOTA_SDCARD_FLAG | APOTA_FATFS_FLAG | APOTA_MBR_FLAG, + }; +} + +class STM32H7OTACloudProcess: public OTADefaultCloudProcessInterface { public: - STM32H7OTACloudProcess(); - void update(); + STM32H7OTACloudProcess(MessageStream *ms, Client* client=nullptr); + ~STM32H7OTACloudProcess(); + void update() override; - // retrocompatibility functions used in old ota prtotocol based on properties - int otaRequest(char const * ota_url, NetworkAdapter iface); - String getOTAImageSHA256(); - bool isOTACapable(); + virtual bool isOtaCapable() override; protected: - // we start the download and decompress process - virtual State fetch(Message* msg=nullptr); + virtual OTACloudProcessInterface::State resume(Message* msg=nullptr) override; + + // we are overriding the method of startOTA in order to open the destination file for the ota download + virtual OTACloudProcessInterface::State startOTA() override; // whene the download is correctly finished we set the mcu to use the newly downloaded binary - virtual State flashOTA(Message* msg=nullptr); + virtual OTACloudProcessInterface::State flashOTA() override; // we reboot the device - virtual State reboot(Message* msg=nullptr); + virtual OTACloudProcessInterface::State reboot() override; + + // write the decompressed char buffer of the incoming ota + virtual int writeFlash(uint8_t* const buffer, size_t len) override; + + virtual void reset() override; + + void* appStartAddress(); + uint32_t appSize(); + bool appFlashOpen() { return true; }; + bool appFlashClose() { return true; }; +private: + bool storageInit(); + bool storageOpen(); + + void storageClean(); + + FILE* decompressed; + // static const char UPDATE_FILE_NAME[]; + mbed::BlockDevice* _bd_raw_qspi; + uint32_t _program_length; + + mbed::BlockDevice* _bd; + mbed::FATFileSystem* _fs; + + mbed::MBRBlockDevice* cert_bd_qspi; + mbed::FATFileSystem* cert_fs_qspi; + + const portenta::StorageType storage=portenta::QSPI_FLASH_FATFS_MBR; + const uint32_t data_offset=2; + + static const char UPDATE_FILE_NAME[]; }; diff --git a/src/ota/implementation/OTASTM32H7.ipp b/src/ota/implementation/OTASTM32H7.ipp deleted file mode 100644 index 09093a30..00000000 --- a/src/ota/implementation/OTASTM32H7.ipp +++ /dev/null @@ -1,32 +0,0 @@ -#if defined(BOARD_STM32H7) && OTA_ENABLED -#include "OTAPortentaH7.h" - -STM32H7OTACloudProcess::STM32H7OTACloudProcess() {} - -void STM32H7OTACloudProcess::update() { - OTACloudProcessInterface::update(); - watchdog_reset(); -} - -State STM32H7OTACloudProcess::fetch(Message *msg) { - -} - -State STM32H7OTACloudProcess::flashOTA(Message *msg) { - /* Schedule the firmware update. */ - if((ota_portenta_err = ota_portenta_qspi.update()) != Arduino_Portenta_OTA::Error::None) { - DEBUG_ERROR("Arduino_Portenta_OTA_QSPI::update() failed with %d", static_cast(ota_portenta_err)); - return static_cast(ota_portenta_err); - } -} - -State STM32H7OTACloudProcess::reboot(Message *msg) { - // TODO save information about the progress reached in the ota - - // This command reboots the mcu - NVIC_SystemReset(); - - return Resume; // This won't ever be reached -} - -#endif // defined(BOARD_STM32H7) && OTA_ENABLED \ No newline at end of file From 51020b00ddebd8e79693877f2966a96efb63fff6 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Thu, 29 Feb 2024 13:35:02 +0100 Subject: [PATCH 11/18] implemented rp2040 ota class --- src/ota/implementation/OTANanoRP2040.cpp | 129 +++++++++++++++++++++++ src/ota/implementation/OTANanoRP2040.h | 46 ++++++-- 2 files changed, 166 insertions(+), 9 deletions(-) diff --git a/src/ota/implementation/OTANanoRP2040.cpp b/src/ota/implementation/OTANanoRP2040.cpp index e0f6feb7..5b1266f6 100644 --- a/src/ota/implementation/OTANanoRP2040.cpp +++ b/src/ota/implementation/OTANanoRP2040.cpp @@ -1,4 +1,133 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include + #if defined(ARDUINO_NANO_RP2040_CONNECT) && OTA_ENABLED +#include #include "OTANanoRP2040.h" +#include +#include "mbed.h" +#include "utility/watchdog/Watchdog.h" + +#define SD_MOUNT_PATH "ota" +#define FULL_UPDATE_FILE_PATH "/ota/UPDATE.BIN" + +const char NANO_RP2040OTACloudProcess::UPDATE_FILE_NAME[] = FULL_UPDATE_FILE_PATH; + + +NANO_RP2040OTACloudProcess::NANO_RP2040OTACloudProcess(MessageStream *ms, Client* client) +: OTADefaultCloudProcessInterface(ms, client) +, flash((uint32_t)appStartAddress() + 0xF00000, 0x100000) // TODO make this numbers a constant +, decompressed(nullptr) +, fs(nullptr) { +} + +NANO_RP2040OTACloudProcess::~NANO_RP2040OTACloudProcess() { + close_fs(); +} + +OTACloudProcessInterface::State NANO_RP2040OTACloudProcess::resume(Message* msg) { + return OtaBegin; +} + +int NANO_RP2040OTACloudProcess::writeFlash(uint8_t* const buffer, size_t len) { + if(decompressed == nullptr) { + DEBUG_VERBOSE("writing on a file that is not open"); // FIXME change log message + return 0; + } + return fwrite(buffer, sizeof(uint8_t), len, decompressed); +} + +OTACloudProcessInterface::State NANO_RP2040OTACloudProcess::startOTA() { + int err = -1; + if ((err = flash.init()) < 0) { + DEBUG_VERBOSE("%s: flash.init() failed with %d", __FUNCTION__, err); + return OtaStorageInitFail; + } + + flash.erase((uint32_t)appStartAddress() + 0xF00000, 0x100000); + + fs = new mbed::FATFileSystem(SD_MOUNT_PATH); // FIXME can this be allocated in the stack? + if ((err = fs->reformat(&flash)) != 0) { + DEBUG_VERBOSE("%s: fs.reformat() failed with %d", __FUNCTION__, err); + return ErrorReformatFail; + } + + decompressed = fopen(UPDATE_FILE_NAME, "wb"); // TODO make this a constant + if (!decompressed) { + DEBUG_VERBOSE("%s: fopen() failed", __FUNCTION__); + fclose(decompressed); + return ErrorOpenUpdateFileFail; + } + + // we start the download here + return OTADefaultCloudProcessInterface::startOTA();; +} + + +OTACloudProcessInterface::State NANO_RP2040OTACloudProcess::flashOTA() { + int err = 0; + if((err = close_fs()) != 0) { + return ErrorUnmountFail; + } + + return Reboot; +} + +OTACloudProcessInterface::State NANO_RP2040OTACloudProcess::reboot() { + mbed_watchdog_trigger_reset(); + /* If watchdog is enabled we should not reach this point */ + NVIC_SystemReset(); + + return Resume; // This won't ever be reached +} + +void NANO_RP2040OTACloudProcess::reset() { + OTADefaultCloudProcessInterface::reset(); + + close_fs(); +} + +int NANO_RP2040OTACloudProcess::close_fs() { + int err = 0; + + if(decompressed != nullptr) { + fclose(decompressed); + decompressed = nullptr; + } + + if (fs != nullptr && (err = fs->unmount()) != 0) { + DEBUG_VERBOSE("%s: fs.unmount() failed with %d", __FUNCTION__, err); + } else { + delete fs; + fs = nullptr; + } + + return err; +} + +bool NANO_RP2040OTACloudProcess::isOtaCapable() { + return true; +} + +// extern void* __stext; +extern uint32_t __flash_binary_end; + + +void* NANO_RP2040OTACloudProcess::appStartAddress() { + // return &__flash_binary_start; + return (void*)XIP_BASE; +} +uint32_t NANO_RP2040OTACloudProcess::appSize() { + return (&__flash_binary_end - (uint32_t*)appStartAddress())*sizeof(void*); +} #endif // defined(ARDUINO_NANO_RP2040_CONNECT) && OTA_ENABLED \ No newline at end of file diff --git a/src/ota/implementation/OTANanoRP2040.h b/src/ota/implementation/OTANanoRP2040.h index 165bb1ce..dd165d27 100644 --- a/src/ota/implementation/OTANanoRP2040.h +++ b/src/ota/implementation/OTANanoRP2040.h @@ -1,23 +1,51 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + #pragma once -#include "src/ota/interface/OTAInterface.h" +#include "ota/interface/OTAInterfaceDefault.h" #include "FATFileSystem.h" #include "FlashIAPBlockDevice.h" -class NANO_RP2040OTACloudProcess: public OTACloudProcessInterface { +class NANO_RP2040OTACloudProcess: public OTADefaultCloudProcessInterface { public: - STM32H7OTACloudProcess(); + NANO_RP2040OTACloudProcess(MessageStream *ms, Client* client=nullptr); + ~NANO_RP2040OTACloudProcess(); + + virtual bool isOtaCapable() override; protected: - // we start the download and decompress process - virtual State fetch(Message* msg=nullptr); + virtual OTACloudProcessInterface::State resume(Message* msg=nullptr) override; - // when the download is completed we verify for integrity and correctness of the downloaded binary - // virtual State verifyOTA(Message* msg=nullptr); // TODO this may be performed inside download + virtual OTACloudProcessInterface::State startOTA() override; // whene the download is correctly finished we set the mcu to use the newly downloaded binary - virtual State flashOTA(Message* msg=nullptr); + virtual OTACloudProcessInterface::State flashOTA() override; // we reboot the device - virtual State reboot(Message* msg=nullptr); + virtual OTACloudProcessInterface::State reboot() override; + + // write the decompressed char buffer of the incoming ota + virtual int writeFlash(uint8_t* const buffer, size_t len) override; + + virtual void reset() override; + + void* appStartAddress(); + uint32_t appSize(); + bool appFlashOpen() { return true; }; + bool appFlashClose() { return true; }; +private: + FlashIAPBlockDevice flash; + FILE* decompressed; + mbed::FATFileSystem* fs; + static const char UPDATE_FILE_NAME[]; + + int close_fs(); }; From 94df318eee139d8ec208be61fa360bdffccd2bd5 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Thu, 29 Feb 2024 13:48:08 +0100 Subject: [PATCH 12/18] implemented unor4 ota class --- src/ota/implementation/OTAUnoR4.cpp | 163 ++++++++++++++++++++++++++++ src/ota/implementation/OTAUnoR4.h | 46 ++++++-- 2 files changed, 201 insertions(+), 8 deletions(-) diff --git a/src/ota/implementation/OTAUnoR4.cpp b/src/ota/implementation/OTAUnoR4.cpp index e44e7f65..8119c243 100644 --- a/src/ota/implementation/OTAUnoR4.cpp +++ b/src/ota/implementation/OTAUnoR4.cpp @@ -1,4 +1,167 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include + #if defined(ARDUINO_UNOR4_WIFI) && OTA_ENABLED #include "OTAUnoR4.h" +#include +#include "tls/utility/SHA256.h" +#include "fsp_common_api.h" +#include "r_flash_lp.h" +#include "WiFi.h" + +/****************************************************************************** + * DEFINES + ******************************************************************************/ + +const char UNOR4OTACloudProcess::UPDATE_FILE_NAME[] = "/update.bin"; + +static OTACloudProcessInterface::State convertUnor4ErrorToState(int error_code); + +UNOR4OTACloudProcess::UNOR4OTACloudProcess(MessageStream *ms) +: OTACloudProcessInterface(ms){ + +} + +OTACloudProcessInterface::State UNOR4OTACloudProcess::resume(Message* msg) { + return OtaBegin; +} + +OTACloudProcessInterface::State UNOR4OTACloudProcess::startOTA() { + int ota_err = OTAUpdate::OTA_ERROR_NONE; + + // Open fs for ota + if((ota_err = ota.begin(UPDATE_FILE_NAME)) != OTAUpdate::OTA_ERROR_NONE) { + DEBUG_VERBOSE("OTAUpdate::begin() failed with %d", ota_err); + return convertUnor4ErrorToState(ota_err); + } + + return Fetch; +} + +OTACloudProcessInterface::State UNOR4OTACloudProcess::fetch() { + int ota_err = OTAUpdate::OTA_ERROR_NONE; + + int const ota_download = ota.download(this->context->url,UPDATE_FILE_NAME); + if (ota_download <= 0) { + DEBUG_VERBOSE("OTAUpdate::download() failed with %d", ota_download); + return convertUnor4ErrorToState(ota_download); + } + DEBUG_VERBOSE("OTAUpdate::download() %d bytes downloaded", ota_download); + + if ((ota_err = ota.verify()) != OTAUpdate::OTA_ERROR_NONE) { + DEBUG_VERBOSE("OTAUpdate::verify() failed with %d", ota_err); + return convertUnor4ErrorToState(ota_err); + } + + return FlashOTA; +} + +OTACloudProcessInterface::State UNOR4OTACloudProcess::flashOTA() { + int ota_err = OTAUpdate::OTA_ERROR_NONE; + + /* Flash new firmware */ + if ((ota_err = ota.update(UPDATE_FILE_NAME)) != OTAUpdate::OTA_ERROR_NONE) { // This reboots the MCU + DEBUG_VERBOSE("OTAUpdate::update() failed with %d", ota_err); + return convertUnor4ErrorToState(ota_err); + } +} + +OTACloudProcessInterface::State UNOR4OTACloudProcess::reboot() { +} + +void UNOR4OTACloudProcess::reset() { +} + +bool UNOR4OTACloudProcess::isOtaCapable() { + String const fv = WiFi.firmwareVersion(); + if (fv < String("0.3.0")) { + return false; + } + return true; +} + +extern void* __ROM_Start; +extern void* __etext; +extern void* __data_end__; +extern void* __data_start__; + +constexpr void* UNOR4OTACloudProcess::appStartAddress() { return &__ROM_Start; } +uint32_t UNOR4OTACloudProcess::appSize() { + return ((&__etext - &__ROM_Start) + (&__data_end__ - &__data_start__))*sizeof(void*); +} + +bool UNOR4OTACloudProcess::appFlashOpen() { + cfg.data_flash_bgo = false; + cfg.p_callback = nullptr; + cfg.p_context = nullptr; + cfg.p_extend = nullptr; + cfg.ipl = (BSP_IRQ_DISABLED); + cfg.irq = FSP_INVALID_VECTOR; + cfg.err_ipl = (BSP_IRQ_DISABLED); + cfg.err_irq = FSP_INVALID_VECTOR; + + fsp_err_t rv = FSP_ERR_UNSUPPORTED; + + rv = R_FLASH_LP_Open(&ctrl,&cfg); + DEBUG_VERBOSE("Flash open %X", rv); + + return rv == FSP_SUCCESS; +} + +bool UNOR4OTACloudProcess::appFlashClose() { + fsp_err_t rv = FSP_ERR_UNSUPPORTED; + rv = R_FLASH_LP_Close(&ctrl); + DEBUG_VERBOSE("Flash close %X", rv); + + return rv == FSP_SUCCESS; +} + +static OTACloudProcessInterface::State convertUnor4ErrorToState(int error_code) { + switch(error_code) { + case -2: + return OTACloudProcessInterface::NoOtaStorageFail; + case -3: + return OTACloudProcessInterface::OtaStorageInitFail; + case -4: + return OTACloudProcessInterface::OtaStorageEndFail; + case -5: + return OTACloudProcessInterface::UrlParseErrorFail; + case -6: + return OTACloudProcessInterface::ServerConnectErrorFail; + case -7: + return OTACloudProcessInterface::HttpHeaderErrorFail; + case -8: + return OTACloudProcessInterface::ParseHttpHeaderFail; + case -9: + return OTACloudProcessInterface::OtaHeaderLengthFail; + case -10: + return OTACloudProcessInterface::OtaHeaderCrcFail; + case -11: + return OTACloudProcessInterface::OtaHeaderMagicNumberFail; + case -12: + return OTACloudProcessInterface::OtaDownloadFail; + case -13: + return OTACloudProcessInterface::OtaHeaderTimeoutFail; + case -14: + return OTACloudProcessInterface::HttpResponseFail; + case -25: + return OTACloudProcessInterface::LibraryFail; + case -26: + return OTACloudProcessInterface::ModemFail; + default: + DEBUG_VERBOSE("Unrecognized error code %d", error_code); + return OTACloudProcessInterface::Fail; + } +} + #endif // defined(ARDUINO_UNOR4_WIFI) && OTA_ENABLED diff --git a/src/ota/implementation/OTAUnoR4.h b/src/ota/implementation/OTAUnoR4.h index 871ea58a..28c70e2f 100644 --- a/src/ota/implementation/OTAUnoR4.h +++ b/src/ota/implementation/OTAUnoR4.h @@ -1,22 +1,52 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + #pragma once -#include "src/ota/interface/OTAInterface.h" +#include "ota/interface/OTAInterface.h" #include "OTAUpdate.h" #include "r_flash_lp.h" class UNOR4OTACloudProcess: public OTACloudProcessInterface { public: - STM32H7OTACloudProcess(); + UNOR4OTACloudProcess(MessageStream *ms); + + bool isOtaCapable() override; protected: - // we start the download and decompress process - virtual State fetch(Message* msg=nullptr); + virtual OTACloudProcessInterface::State resume(Message* msg=nullptr) override; + + // we are overriding the method of startOTA in order to download ota file on ESP32 + virtual OTACloudProcessInterface::State startOTA() override; - // when the download is completed we verify for integrity and correctness of the downloaded binary - // virtual State verifyOTA(Message* msg=nullptr); // TODO this may be performed inside download + // we start the download and decompress process + virtual OTACloudProcessInterface::State fetch() override; // whene the download is correctly finished we set the mcu to use the newly downloaded binary - virtual State flashOTA(Message* msg=nullptr); + virtual OTACloudProcessInterface::State flashOTA() override; // we reboot the device - virtual State reboot(Message* msg=nullptr); + virtual OTACloudProcessInterface::State reboot() override; + + virtual void reset() override; + + constexpr void* appStartAddress(); + uint32_t appSize(); + + bool appFlashOpen(); + bool appFlashClose(); + +public: + // used to access to flash memory for sha256 calculation + flash_lp_instance_ctrl_t ctrl; + flash_cfg_t cfg; + + OTAUpdate ota; + static const char UPDATE_FILE_NAME[]; }; From 0a811cb14d0aadbbee4329ec47d048015fa75eed Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Thu, 29 Feb 2024 14:02:00 +0100 Subject: [PATCH 13/18] implemented esp32 ota class --- src/ota/implementation/OTAEsp32.cpp | 114 ++++++++++++++++++++++++++++ src/ota/implementation/OTAEsp32.h | 41 +++++++--- 2 files changed, 146 insertions(+), 9 deletions(-) diff --git a/src/ota/implementation/OTAEsp32.cpp b/src/ota/implementation/OTAEsp32.cpp index 7ac03613..f52e6472 100644 --- a/src/ota/implementation/OTAEsp32.cpp +++ b/src/ota/implementation/OTAEsp32.cpp @@ -1,3 +1,117 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include "AIoTC_Config.h" #if defined(ARDUINO_ARCH_ESP32) && OTA_ENABLED +#include "OTAEsp32.h" +#include +#include + +ESP32OTACloudProcess::ESP32OTACloudProcess(MessageStream *ms, Client* client) +: OTADefaultCloudProcessInterface(ms), rom_partition(nullptr) { + +} + + +OTACloudProcessInterface::State ESP32OTACloudProcess::resume(Message* msg) { + return OtaBegin; +} + +OTACloudProcessInterface::State ESP32OTACloudProcess::startOTA() { + if(Update.isRunning()) { + Update.abort(); + DEBUG_VERBOSE("%s: Aborting running update", __FUNCTION__); + } + + if(!Update.begin(UPDATE_SIZE_UNKNOWN)) { + DEBUG_VERBOSE("%s: failed to initialize flash update", __FUNCTION__); + return OtaStorageInitFail; + } + + return OTADefaultCloudProcessInterface::startOTA(); +} + +OTACloudProcessInterface::State ESP32OTACloudProcess::flashOTA() { + + if (!Update.end(true)) { + DEBUG_VERBOSE("%s: Failure to apply OTA update", __FUNCTION__); + return OtaStorageEndFail; + } + + return Reboot; +} + +OTACloudProcessInterface::State ESP32OTACloudProcess::reboot() { + ESP.restart(); + + return Idle; // we won't reach this +} + +int ESP32OTACloudProcess::writeFlash(uint8_t* const buffer, size_t len) { + return Update.write(buffer, len); +} + +bool ESP32OTACloudProcess::isOtaCapable() { + const esp_partition_t * ota_0 = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL); + const esp_partition_t * ota_1 = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_1, NULL); + return ((ota_0 != nullptr) && (ota_1 != nullptr)); +} + +void* ESP32OTACloudProcess::appStartAddress() { + return nullptr; +} +uint32_t ESP32OTACloudProcess::appSize() { + return ESP.getSketchSize(); +} + +bool ESP32OTACloudProcess::appFlashOpen() { + rom_partition = esp_ota_get_running_partition(); + + if(rom_partition == nullptr) { + return false; + } + + return true; +} + +void ESP32OTACloudProcess::calculateSHA256(SHA256& sha256_calc) { + if(!appFlashOpen()) { + return; // TODO error reporting + } + + sha256_calc.begin(); + + uint8_t b[SPI_FLASH_SEC_SIZE]; + if(b == nullptr) { + DEBUG_VERBOSE("ESP32::SHA256 Not enough memory to allocate buffer"); + return; // TODO error reporting + } + + uint32_t read_bytes = 0; + uint32_t const app_size = ESP.getSketchSize(); + for(uint32_t a = rom_partition->address; 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_VERBOSE("ESP32::SHA256 Could not read data from flash"); + return; + } + sha256_calc.update(b, read_size); + a += read_size; + read_bytes += read_size; + } + + appFlashClose(); +} #endif // defined(ARDUINO_ARCH_ESP32) && OTA_ENABLED \ No newline at end of file diff --git a/src/ota/implementation/OTAEsp32.h b/src/ota/implementation/OTAEsp32.h index 8654d2c7..b904308e 100644 --- a/src/ota/implementation/OTAEsp32.h +++ b/src/ota/implementation/OTAEsp32.h @@ -1,20 +1,43 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + #pragma once -#include "src/ota/interface/OTAInterface.h" +#include "ota/interface/OTAInterfaceDefault.h" -class ESP32OTACloudProcess: public OTACloudProcessInterface { +class ESP32OTACloudProcess: public OTADefaultCloudProcessInterface { public: - STM32H7OTACloudProcess(); + ESP32OTACloudProcess(MessageStream *ms, Client* client=nullptr); + + virtual bool isOtaCapable() override; protected: - // we start the download and decompress process - virtual State fetch(Message* msg=nullptr); + virtual OTACloudProcessInterface::State resume(Message* msg=nullptr) override; - // when the download is completed we verify for integrity and correctness of the downloaded binary - // virtual State verifyOTA(Message* msg=nullptr); // TODO this may be performed inside download + // we are overriding the method of startOTA in order to download ota file on ESP32 + virtual OTACloudProcessInterface::State startOTA() override; // whene the download is correctly finished we set the mcu to use the newly downloaded binary - virtual State flashOTA(Message* msg=nullptr); + virtual State flashOTA() override; // we reboot the device - virtual State reboot(Message* msg=nullptr); + virtual State reboot() override; + + // write the decompressed char buffer of the incoming ota + virtual int writeFlash(uint8_t* const buffer, size_t len) override; + + void* appStartAddress(); + uint32_t appSize(); + bool appFlashOpen(); + bool appFlashClose() { return true; }; + + void calculateSHA256(SHA256&) override; +private: + const esp_partition_t *rom_partition; }; From aa78ef8ffc6a12f41aa7eded9789ad31b001b4ef Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Thu, 29 Feb 2024 15:07:20 +0100 Subject: [PATCH 14/18] implemented samd ota class --- src/ota/implementation/OTASamd.cpp | 93 ++++++++++++++++++++++++++++++ src/ota/implementation/OTASamd.h | 47 ++++++++++++--- 2 files changed, 132 insertions(+), 8 deletions(-) diff --git a/src/ota/implementation/OTASamd.cpp b/src/ota/implementation/OTASamd.cpp index 1dc307f4..bc919449 100644 --- a/src/ota/implementation/OTASamd.cpp +++ b/src/ota/implementation/OTASamd.cpp @@ -1,4 +1,97 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include + #if defined(ARDUINO_ARCH_SAMD) && OTA_ENABLED #include "OTASamd.h" +#include +#if OTA_STORAGE_SNU +# include +# include /* WiFiStorage */ +#endif + +SAMDOTACloudProcess::SAMDOTACloudProcess(MessageStream *ms) +: OTACloudProcessInterface(ms){ + +} + +OTACloudProcessInterface::State SAMDOTACloudProcess::resume(Message* msg) { + return OtaBegin; +} + +OTACloudProcessInterface::State SAMDOTACloudProcess::startOTA() { + reset(); + return Fetch; +} + +OTACloudProcessInterface::State SAMDOTACloudProcess::fetch() { +#if OTA_STORAGE_SNU + uint8_t nina_ota_err_code = 0; + if (!WiFiStorage.downloadOTA(this->context->url, &nina_ota_err_code)) { + DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s error download to nina: %d", __FUNCTION__, nina_ota_err_code); + switch(static_cast(nina_ota_err_code)) { + case ninaOTAError::Open: + return ErrorOpenUpdateFileFail; + case ninaOTAError::Length: + return OtaDownloadFail; + case ninaOTAError::CRC: + return OtaHeaderCrcFail; + case ninaOTAError::Rename: + return ErrorRenameFail; + default: + return OtaDownloadFail; + } + } +#endif // OTA_STORAGE_SNU + + return FlashOTA; +} + +OTACloudProcessInterface::State SAMDOTACloudProcess::flashOTA() { + return Reboot; +} + +OTACloudProcessInterface::State SAMDOTACloudProcess::reboot() { + NVIC_SystemReset(); +} + +void SAMDOTACloudProcess::reset() { +#if OTA_STORAGE_SNU + WiFiStorage.remove("/fs/UPDATE.BIN.LZSS"); + WiFiStorage.remove("/fs/UPDATE.BIN.LZSS.TMP"); +#endif // OTA_STORAGE_SNU +} + +bool SAMDOTACloudProcess::isOtaCapable() { +#if OTA_STORAGE_SNU + if (strcmp(WiFi.firmwareVersion(), "1.4.1") < 0) { + DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s In order to be ready for cloud OTA, NINA firmware needs to be >= 1.4.1, current %s", __FUNCTION__, WiFi.firmwareVersion()); + return false; + } else { + return true; + } +#endif + return false; +} + +extern void* __text_start__; +extern void* __etext; +extern void* __data_end__; +extern void* __data_start__; + +void* SAMDOTACloudProcess::appStartAddress() { return &__text_start__; } + +uint32_t SAMDOTACloudProcess::appSize() { + return ((&__etext - &__text_start__) + (&__data_end__ - &__data_start__))*sizeof(void*); +} + #endif // defined(ARDUINO_ARCH_SAMD) && OTA_ENABLED \ No newline at end of file diff --git a/src/ota/implementation/OTASamd.h b/src/ota/implementation/OTASamd.h index 3c248dd1..3f448c77 100644 --- a/src/ota/implementation/OTASamd.h +++ b/src/ota/implementation/OTASamd.h @@ -1,21 +1,52 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + #pragma once -#include "src/ota/interface/OTAInterface.h" +#include "ota/interface/OTAInterface.h" #include class SAMDOTACloudProcess: public OTACloudProcessInterface { public: - STM32H7OTACloudProcess(); + SAMDOTACloudProcess(MessageStream *ms); + + virtual bool isOtaCapable() override; protected: - // we start the download and decompress process - virtual State fetch(Message* msg=nullptr); + virtual OTACloudProcessInterface::State resume(Message* msg=nullptr) override; - // when the download is completed we verify for integrity and correctness of the downloaded binary - // virtual State verifyOTA(Message* msg=nullptr); // TODO this may be performed inside download + // we are overriding the method of startOTA in order to download ota file on ESP32 + virtual OTACloudProcessInterface::State startOTA() override; + + // we start the download and decompress process + virtual OTACloudProcessInterface::State fetch() override; // whene the download is correctly finished we set the mcu to use the newly downloaded binary - virtual State flashOTA(Message* msg=nullptr); + virtual OTACloudProcessInterface::State flashOTA(); // we reboot the device - virtual State reboot(Message* msg=nullptr); + virtual OTACloudProcessInterface::State reboot(); + + virtual void reset() override; + + void* appStartAddress(); + uint32_t appSize(); + + bool appFlashOpen() { return true; } + bool appFlashClose() { return true; } + +private: + enum class ninaOTAError : int { + None = 0, + Open = 1, + Length = 2, + CRC = 3, + Rename = 4, + }; }; From bfeb4b0b6c320f455d7d4051982a1434daf65984 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Mon, 18 Mar 2024 14:49:24 +0100 Subject: [PATCH 15/18] disabling ota on mkrgsm1400 --- src/AIoTC_Config.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AIoTC_Config.h b/src/AIoTC_Config.h index 26bc377a..e889bbdd 100644 --- a/src/AIoTC_Config.h +++ b/src/AIoTC_Config.h @@ -65,7 +65,7 @@ #endif #ifdef ARDUINO_SAMD_MKRGSM1400 - #define OTA_STORAGE_SSU (1) + #define OTA_STORAGE_SSU (1) // OTA_STORAGE_SSU is not implemented yet in OTASamd #else #define OTA_STORAGE_SSU (0) #endif @@ -80,7 +80,7 @@ #define OTA_STORAGE_ESP (1) #endif -#if (OTA_STORAGE_SFU || OTA_STORAGE_SSU || OTA_STORAGE_SNU || OTA_STORAGE_PORTENTA_QSPI || OTA_STORAGE_ESP) +#if (OTA_STORAGE_SFU || OTA_STORAGE_SNU || OTA_STORAGE_PORTENTA_QSPI || OTA_STORAGE_ESP) #define OTA_ENABLED (1) #else #define OTA_ENABLED (0) From a33cd85ce6cfa2edf4a4382aea129f0268f121ab Mon Sep 17 00:00:00 2001 From: pennam Date: Fri, 15 Mar 2024 11:47:55 +0100 Subject: [PATCH 16/18] ArduinoIoTCloud integration --- src/ArduinoIoTCloudDevice.cpp | 1 - src/ArduinoIoTCloudTCP.cpp | 179 +++++++++------------------------- src/ArduinoIoTCloudTCP.h | 61 +++--------- 3 files changed, 58 insertions(+), 183 deletions(-) diff --git a/src/ArduinoIoTCloudDevice.cpp b/src/ArduinoIoTCloudDevice.cpp index 09151f4d..53fcb74d 100644 --- a/src/ArduinoIoTCloudDevice.cpp +++ b/src/ArduinoIoTCloudDevice.cpp @@ -135,7 +135,6 @@ ArduinoCloudDevice::State ArduinoCloudDevice::handleConnected() { } return State::SendCapabilities; } - return State::Connected; } diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index 6e38a896..687cdddf 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -22,22 +22,11 @@ #include #ifdef HAS_TCP -#include - -#if defined(BOARD_HAS_SECRET_KEY) - #include "tls/AIoTCUPCert.h" -#endif -#ifdef BOARD_HAS_ECCX08 - #include "tls/BearSSLTrustAnchors.h" -#endif - -#if defined(BOARD_HAS_SE050) || defined(BOARD_HAS_SOFTSE) - #include "tls/AIoTCSSCert.h" -#endif +#include #if OTA_ENABLED - #include "utility/ota/OTA.h" + #include "ota/OTA.h" #endif #include @@ -67,26 +56,16 @@ ArduinoIoTCloudTCP::ArduinoIoTCloudTCP() , _mqtt_data_buf{0} , _mqtt_data_len{0} , _mqtt_data_request_retransmit{false} -#ifdef BOARD_HAS_ECCX08 -, _sslClient(nullptr, ArduinoIoTCloudTrustAnchor, ArduinoIoTCloudTrustAnchor_NUM, getTime) -#endif #ifdef BOARD_HAS_SECRET_KEY , _password("") #endif , _mqttClient{nullptr} -, _deviceTopicOut("") -, _deviceTopicIn("") , _messageTopicOut("") , _messageTopicIn("") , _dataTopicOut("") , _dataTopicIn("") #if OTA_ENABLED -, _ota_cap{false} -, _ota_error{static_cast(OTAError::None)} -, _ota_img_sha256{"Inv."} -, _ota_url{""} -, _ota_req{false} -, _ask_user_before_executing_ota{false} +, _ota(&_message_stream) , _get_ota_confirmation{nullptr} #endif /* OTA_ENABLED */ { @@ -107,8 +86,16 @@ int ArduinoIoTCloudTCP::begin(ConnectionHandler & connection, bool const enable_ _brokerPort = brokerPort; #endif + /* Setup broker TLS client */ + _brokerClient.begin(connection); + +#if OTA_ENABLED + /* Setup OTA TLS client */ + _otaClient.begin(connection); +#endif + /* Setup TimeService */ - _time_service.begin(&connection); + _time_service.begin(_connection); /* Setup retry timers */ _connection_attempt.begin(AIOT_CONFIG_RECONNECTION_RETRY_DELAY_ms, AIOT_CONFIG_MAX_RECONNECTION_RETRY_DELAY_ms); @@ -148,33 +135,19 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not read device certificate.", __FUNCTION__); return 0; } - _sslClient.setEccSlot(static_cast(SElementArduinoCloudSlot::Key), _cert.bytes(), _cert.length()); + _brokerClient.setEccSlot(static_cast(SElementArduinoCloudSlot::Key), _cert.bytes(), _cert.length()); + #if OTA_ENABLED + _otaClient.setEccSlot(static_cast(SElementArduinoCloudSlot::Key), _cert.bytes(), _cert.length()); + #endif #endif #endif + #if defined(BOARD_HAS_SECRET_KEY) } #endif -#if defined(BOARD_HAS_OFFLOADED_ECCX08) - -#elif defined(BOARD_HAS_ECCX08) - _sslClient.setClient(_connection->getClient()); -#elif defined(ARDUINO_PORTENTA_C33) - _sslClient.setClient(_connection->getClient()); - _sslClient.setCACert(AIoTSSCert); -#elif defined(ARDUINO_NICLA_VISION) - _sslClient.appendCustomCACert(AIoTSSCert); -#elif defined(ARDUINO_EDGE_CONTROL) - _sslClient.appendCustomCACert(AIoTUPCert); -#elif defined(ARDUINO_UNOR4_WIFI) - -#elif defined(ARDUINO_ARCH_ESP32) - _sslClient.setCACertBundle(x509_crt_bundle); -#elif defined(ARDUINO_ARCH_ESP8266) - _sslClient.setInsecure(); -#endif + _mqttClient.setClient(_brokerClient); - _mqttClient.setClient(_sslClient); #ifdef BOARD_HAS_SECRET_KEY if(_password.length()) { @@ -187,32 +160,19 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, _mqttClient.setConnectionTimeout(1500); _mqttClient.setId(getDeviceId().c_str()); - _deviceTopicOut = getTopic_deviceout(); - _deviceTopicIn = getTopic_devicein(); - _messageTopicIn = getTopic_messagein(); _messageTopicOut = getTopic_messageout(); + _messageTopicIn = getTopic_messagein(); _thing.begin(); _device.begin(); -#if OTA_ENABLED - Property* p; - p = new CloudWrapperBool(_ota_cap); - addPropertyToContainer(_device.getPropertyContainer(), *p, "OTA_CAP", Permission::Read, -1); - p = new CloudWrapperInt(_ota_error); - addPropertyToContainer(_device.getPropertyContainer(), *p, "OTA_ERROR", Permission::Read, -1); - p = new CloudWrapperString(_ota_img_sha256); - addPropertyToContainer(_device.getPropertyContainer(), *p, "OTA_SHA256", Permission::Read, -1); - p = new CloudWrapperString(_ota_url); - addPropertyToContainer(_device.getPropertyContainer(), *p, "OTA_URL", Permission::ReadWrite, -1); - p = new CloudWrapperBool(_ota_req); - addPropertyToContainer(_device.getPropertyContainer(), *p, "OTA_REQ", Permission::ReadWrite, -1); - - _ota_cap = OTA::isCapable(); - - _ota_img_sha256 = OTA::getImageSHA256(); - DEBUG_VERBOSE("SHA256: HASH(%d) = %s", strlen(_ota_img_sha256.c_str()), _ota_img_sha256.c_str()); -#endif // OTA_ENABLED +#if OTA_ENABLED && !defined(OFFLOADED_DOWNLOAD) + _ota.setClient(&_otaClient); +#endif // OTA_ENABLED && !defined(OFFLOADED_DOWNLOAD) + +#if OTA_ENABLED && defined(OTA_BASIC_AUTH) + _ota.setAuthentication(getDeviceId().c_str(), _password.c_str()); +#endif // OTA_ENABLED && !defined(OFFLOADED_DOWNLOAD) && defined(OTA_BASIC_AUTH) #ifdef BOARD_HAS_OFFLOADED_ECCX08 if (String(WiFi.firmwareVersion()) < String("1.4.4")) { @@ -323,9 +283,6 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_ConnectMqttBroker() /* Subscribe to message topic to receive commands */ _mqttClient.subscribe(_messageTopicIn); - /* Temoporarly subscribe to device topic to receive OTA properties */ - _mqttClient.subscribe(_deviceTopicIn); - /* Reconfigure timers for next state */ _connection_attempt.begin(AIOT_CONFIG_DEVICE_TOPIC_SUBSCRIBE_RETRY_DELAY_ms, AIOT_CONFIG_MAX_DEVICE_TOPIC_SUBSCRIBE_RETRY_DELAY_ms); @@ -360,50 +317,24 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Connected() /* Call CloudDevice process to get configuration */ _device.update(); - if (_device.isAttached()) { - /* Call CloudThing process to synchronize properties */ - _thing.update(); - } - -#if OTA_ENABLED - if (_device.connected()) { - handle_OTARequest(); +#if OTA_ENABLED + if(_get_ota_confirmation != nullptr && + _ota.getState() == OTACloudProcessInterface::State::OtaAvailable && + _get_ota_confirmation()) { + _ota.approveOta(); } -#endif /* OTA_ENABLED */ - return State::Connected; -} + _ota.update(); +#endif // OTA_ENABLED -#if OTA_ENABLED -void ArduinoIoTCloudTCP::handle_OTARequest() { - /* Request a OTA download if the hidden property - * OTA request has been set. - */ - if (_ota_req) - { - bool const ota_execution_allowed_by_user = (_get_ota_confirmation != nullptr && _get_ota_confirmation()); - bool const perform_ota_now = ota_execution_allowed_by_user || !_ask_user_before_executing_ota; - if (perform_ota_now) { - /* Clear the error flag. */ - _ota_error = static_cast(OTAError::None); - /* Clear the request flag. */ - _ota_req = false; - /* Transmit the cleared request flags to the cloud. */ - sendDevicePropertyToCloud("OTA_REQ"); - /* Call member function to handle OTA request. */ - _ota_error = OTA::onRequest(_ota_url, _connection->getInterface()); - /* If something fails send the OTA error to the cloud */ - sendDevicePropertyToCloud("OTA_ERROR"); - } + if (_device.isAttached()) { + /* Call CloudThing process to synchronize properties */ + _thing.update(); } - /* Check if we have received the OTA_URL property and provide - * echo to the cloud. - */ - sendDevicePropertyToCloud("OTA_URL"); + return State::Connected; } -#endif /* OTA_ENABLED */ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Disconnect() { @@ -441,11 +372,6 @@ void ArduinoIoTCloudTCP::handleMessage(int length) bytes[i] = _mqttClient.read(); } - /* Topic for OTA properties and device configuration */ - if (_deviceTopicIn == topic) { - CBORDecoder::decode(_device.getPropertyContainer(), (uint8_t*)bytes, length); - } - /* Topic for user input data */ if (_dataTopicIn == topic) { CBORDecoder::decode(_thing.getPropertyContainer(), (uint8_t*)bytes, length); @@ -502,6 +428,14 @@ void ArduinoIoTCloudTCP::handleMessage(int length) } break; +#if OTA_ENABLED + case CommandId::OtaUpdateCmdDownId: + { + DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s [%d] ota update received", __FUNCTION__, millis()); + _ota.handleMessage((Message*)&command); + } +#endif + default: break; } @@ -522,14 +456,6 @@ void ArduinoIoTCloudTCP::sendMessage(Message * msg) _thing.getPropertyContainerIndex()); break; -#if OTA_ENABLED - case DeviceBeginCmdId: - sendDevicePropertyToCloud("OTA_CAP"); - sendDevicePropertyToCloud("OTA_ERROR"); - sendDevicePropertyToCloud("OTA_SHA256"); - break; -#endif - default: break; } @@ -562,21 +488,6 @@ void ArduinoIoTCloudTCP::sendPropertyContainerToCloud(String const topic, Proper } } -#if OTA_ENABLED -void ArduinoIoTCloudTCP::sendDevicePropertyToCloud(String const name) -{ - PropertyContainer temp_device_property_container; - unsigned int last_device_property_index = 0; - - Property* p = getProperty(this->_device.getPropertyContainer(), name); - if(p != nullptr) - { - addPropertyToContainer(temp_device_property_container, *p, p->name(), p->isWriteableByCloud() ? Permission::ReadWrite : Permission::Read); - sendPropertyContainerToCloud(_deviceTopicOut, temp_device_property_container, last_device_property_index); - } -} -#endif - void ArduinoIoTCloudTCP::attachThing(String thingId) { _thing_id = thingId; diff --git a/src/ArduinoIoTCloudTCP.h b/src/ArduinoIoTCloudTCP.h index 7f08c3ce..29dfc754 100644 --- a/src/ArduinoIoTCloudTCP.h +++ b/src/ArduinoIoTCloudTCP.h @@ -36,24 +36,11 @@ #endif #endif -#if defined(BOARD_HAS_OFFLOADED_ECCX08) - #include "WiFiSSLClient.h" -#elif defined(BOARD_HAS_ECCX08) - #include "tls/BearSSLClient.h" -#elif defined(ARDUINO_PORTENTA_C33) - #include -#elif defined(ARDUINO_NICLA_VISION) - #include -#elif defined(ARDUINO_EDGE_CONTROL) - #include -#elif defined(ARDUINO_UNOR4_WIFI) - #include -#elif defined(BOARD_ESP) - #include -#endif +#include +#include #if OTA_ENABLED -#include +#include #endif #include "cbor/MessageDecoder.h" @@ -107,9 +94,13 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass */ void onOTARequestCb(onOTARequestCallbackFunc cb) { _get_ota_confirmation = cb; - _ask_user_before_executing_ota = true; + + if(_get_ota_confirmation) { + _ota.setOtaPolicies(OTACloudProcessInterface::ApprovalRequired); + } else { + _ota.setOtaPolicies(OTACloudProcessInterface::None); + } } - void handle_OTARequest(); #endif private: @@ -147,44 +138,21 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass #endif #endif -#if defined(BOARD_HAS_OFFLOADED_ECCX08) - WiFiBearSSLClient _sslClient; -#elif defined(BOARD_HAS_ECCX08) - BearSSLClient _sslClient; -#elif defined(ARDUINO_PORTENTA_C33) - SSLClient _sslClient; -#elif defined(ARDUINO_NICLA_VISION) - WiFiSSLSE050Client _sslClient; -#elif defined(ARDUINO_EDGE_CONTROL) - GSMSSLClient _sslClient; -#elif defined(ARDUINO_UNOR4_WIFI) - WiFiSSLClient _sslClient; -#elif defined(BOARD_ESP) - WiFiClientSecure _sslClient; -#endif - + TLSClientMqtt _brokerClient; MqttClient _mqttClient; - String _deviceTopicOut; - String _deviceTopicIn; String _messageTopicOut; String _messageTopicIn; String _dataTopicOut; String _dataTopicIn; + #if OTA_ENABLED - bool _ota_cap; - int _ota_error; - String _ota_img_sha256; - String _ota_url; - bool _ota_req; - bool _ask_user_before_executing_ota; + TLSClientOta _otaClient; + ArduinoCloudOTA _ota; onOTARequestCallbackFunc _get_ota_confirmation; #endif /* OTA_ENABLED */ - inline String getTopic_deviceout() { return String("/a/d/" + getDeviceId() + "/e/o");} - inline String getTopic_devicein () { return String("/a/d/" + getDeviceId() + "/e/i");} - inline String getTopic_messageout() { return String("/a/d/" + getDeviceId() + "/c/up");} inline String getTopic_messagein () { return String("/a/d/" + getDeviceId() + "/c/dw");} @@ -206,9 +174,6 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass void detachThing(); int write(String const topic, byte const data[], int const length); -#if OTA_ENABLED - void sendDevicePropertyToCloud(String const name); -#endif }; /****************************************************************************** From 613eae25e276e2be0b1b536b275134bef4fda657 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 24 Apr 2024 14:34:26 +0200 Subject: [PATCH 17/18] Ota default interface: making the download run at least a configurable amount of time --- src/ota/interface/OTAInterfaceDefault.cpp | 24 +++++++++++++---------- src/ota/interface/OTAInterfaceDefault.h | 4 ++++ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/ota/interface/OTAInterfaceDefault.cpp b/src/ota/interface/OTAInterfaceDefault.cpp index de698e66..d96442bc 100644 --- a/src/ota/interface/OTAInterfaceDefault.cpp +++ b/src/ota/interface/OTAInterfaceDefault.cpp @@ -91,20 +91,24 @@ OTACloudProcessInterface::State OTADefaultCloudProcessInterface::startOTA() { OTACloudProcessInterface::State OTADefaultCloudProcessInterface::fetch() { OTACloudProcessInterface::State res = Fetch; int http_res = 0; + uint32_t start = millis(); - if(http_client->available() == 0) { - goto exit; - } + do { + if(http_client->available() == 0) { + goto exit; + } - http_res = http_client->read(context->buffer, context->buf_len); + http_res = http_client->read(context->buffer, context->buf_len); - if(http_res < 0) { - DEBUG_VERBOSE("OTA ERROR: Download read error %d", http_res); - res = OtaDownloadFail; - goto exit; - } + if(http_res < 0) { + DEBUG_VERBOSE("OTA ERROR: Download read error %d", http_res); + res = OtaDownloadFail; + goto exit; + } - parseOta(context->buffer, http_res); + parseOta(context->buffer, http_res); + } while((context->downloadState == OtaDownloadFile || context->downloadState == OtaDownloadHeader) && + millis() - start < downloadTime); // TODO verify that the information present in the ota header match the info in context if(context->downloadState == OtaDownloadCompleted) { diff --git a/src/ota/interface/OTAInterfaceDefault.h b/src/ota/interface/OTAInterfaceDefault.h index ae68d53b..8f7aaf18 100644 --- a/src/ota/interface/OTAInterfaceDefault.h +++ b/src/ota/interface/OTAInterfaceDefault.h @@ -49,6 +49,10 @@ class OTADefaultCloudProcessInterface: public OTACloudProcessInterface { const char *username, *password; + // The amount of time that each iteration of Fetch has to take at least + // This mitigate the issues arising from tasks run in main loop that are using all the computing time + static constexpr uint32_t downloadTime = 100; + enum OTADownloadState: uint8_t { OtaDownloadHeader, OtaDownloadFile, From 249bae3723b12fc4741f776f8aa926ca96b61835 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 24 Apr 2024 15:02:39 +0200 Subject: [PATCH 18/18] Ota interface Default: adding download progress status report --- src/ota/interface/OTAInterfaceDefault.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ota/interface/OTAInterfaceDefault.cpp b/src/ota/interface/OTAInterfaceDefault.cpp index d96442bc..d39ec0f2 100644 --- a/src/ota/interface/OTAInterfaceDefault.cpp +++ b/src/ota/interface/OTAInterfaceDefault.cpp @@ -182,11 +182,10 @@ void OTADefaultCloudProcessInterface::parseOta(uint8_t* buffer, size_t buf_len) cursor += buf_len - (cursor-buffer); context->downloadedSize += (cursor-buffer); - if((millis() - context->lastReportTime) > 2000) { // Report the download progress each X millisecond + if((millis() - context->lastReportTime) > 10000) { // Report the download progress each X millisecond DEBUG_VERBOSE("OTA Download Progress %d/%d", context->downloadedSize, http_client->contentLength()); - // FIXME the following line enables the report for download progress, it breaks - // reportStatus(context->downloadedSize); + reportStatus(context->downloadedSize); context->lastReportTime = millis(); }