From f1f728e252c6abb487f8b3ac95851b26545cbd67 Mon Sep 17 00:00:00 2001 From: Emil Muratov Date: Wed, 28 Sep 2022 23:13:13 +0300 Subject: [PATCH 1/2] add flashz lib sources --- src/flashz.cpp | 312 +++++++++++++++++++++++++++++++++++++++++++++++++ src/flashz.hpp | 226 +++++++++++++++++++++++++++++++++++ 2 files changed, 538 insertions(+) create mode 100644 src/flashz.cpp create mode 100644 src/flashz.hpp diff --git a/src/flashz.cpp b/src/flashz.cpp new file mode 100644 index 0000000..b69e1aa --- /dev/null +++ b/src/flashz.cpp @@ -0,0 +1,312 @@ +/* + ESP32-FlashZ library + + This code implements a library for ESP32-xx family chips and provides an + ability to upload zlib compressed firmware images during OTA updates. + + It derives from Arduino's UpdaterClass and uses in-ROM miniz decompressor to inflate + libz compressed data during firmware flashing process + + Copyright (C) Emil Muratov, 2022 + GitHub: https://github.com/vortigont/esp32-flashz + + Lib code based on esptool's implementation https://github.com/espressif/esptool/ + so it inherits it's GPL-2.0 license + + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License version 2 for more details. + * + * You should have received a copy of the GNU General Public License version 2 + * along with this library; if not, get one at + * https://opensource.org/licenses/GPL-2.0 + */ + +#include "flashz.hpp" +#include "esp_task_wdt.h" + +#ifdef ARDUINO +#include "esp32-hal-log.h" +#else +#include "esp_log.h" +#endif + +// ESP32 log tag +static const char *TAG __attribute__((unused)) = "FLASHZ"; + +#define INFLATOR_STREAM_BUFF_SIZE 128 +#define INFLATOR_STREAM_DELAY_MS 5 +#define INFLATOR_STREAM_DELAY_CTR 50 + + +// Inflator class implementation +bool Inflator::init(){ + rdy = false; + + if (!dictBuff) + dictBuff = (uint8_t *)malloc(sizeof(uint8_t) * TINFL_LZ_DICT_SIZE); + + if (!dictBuff) + return false; // OOM + + if (!m_decomp) + m_decomp = new tinfl_decompressor; + + if (!m_decomp){ + delete dictBuff; + dictBuff = nullptr; + return false; // OOM + } + + reset(); + rdy = true; + return rdy; +} + +void Inflator::reset(){ + if (m_decomp) + tinfl_init(m_decomp); + + dict_free = TINFL_LZ_DICT_SIZE; + dict_begin = dict_offset = 0; + + avail_in = total_in = total_out = 0; + + decomp_status = TINFL_STATUS_NEEDS_MORE_INPUT; + decomp_flags = TINFL_FLAG_PARSE_ZLIB_HEADER; // compressed stream MUST have a proper zlib header +} + +void Inflator::end(){ + rdy = false; + delete m_decomp; + m_decomp = nullptr; + delete dictBuff; + dictBuff = nullptr; +} + +int Inflator::inflate(bool final){ + if (!next_in) + return MZ_STREAM_ERROR; + + if (!dict_free) + return MZ_NEED_DICT; + + if (final) + decomp_flags &= ~TINFL_FLAG_HAS_MORE_INPUT; + else + decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; + + size_t in_bytes = avail_in, out_bytes = dict_free; + + // decompress as may input as available or as long as free dict space is available + decomp_status = tinfl_decompress(m_decomp, next_in, &in_bytes, dictBuff, dictBuff + dict_offset, &out_bytes, decomp_flags); + + next_in += in_bytes; // advance the input buffer pointer to the number of consumed bytes + avail_in -= in_bytes; // decrement input buffer counter + total_in += in_bytes; // increment total input cntr + total_out += out_bytes; // increment total output cntr + + dict_offset = (dict_offset + out_bytes) & (TINFL_LZ_DICT_SIZE - 1); + dict_free -= out_bytes; + + if (decomp_status < 0) + return MZ_DATA_ERROR; /* Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). */ + + if ((decomp_status == TINFL_STATUS_NEEDS_MORE_INPUT) && final ) /* if deflator need more input and we demand it's a final call, than something must be wrong with a stream */ + return MZ_STREAM_ERROR; + + if (decomp_status == TINFL_STATUS_HAS_MORE_OUTPUT) /* if deflator can't fit more data to the output buf, than need to flush a buff */ + return MZ_NEED_DICT; + + //return MZ_OK; // this should be the only + return ((decomp_status == TINFL_STATUS_DONE) && (final)) ? MZ_STREAM_END : MZ_OK; +}; + +int Inflator::inflate_block_to_cb(const uint8_t* inBuff, size_t len, inflate_cb_t callback, bool final, size_t chunk_size){ + if (!rdy) + return MZ_BUF_ERROR; // inflator not initialized + + next_in = inBuff; + avail_in = len; + + decomp_flags &= ~( TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF ); // use internal ring buffer for decompression + + for (;;){ + unsigned int _to = total_out; + int err = inflate(final); // inflate as much in-data as possible + + if (err < 0){ + ESP_LOGW(TAG, "inflate failure - MZ_ERR: %d, inflate status: %d", err, decomp_status); + return err; // exit on any error + } + + size_t deco_data_len = (dict_offset - dict_begin) & (TINFL_LZ_DICT_SIZE - 1); + //ESP_LOGD(TAG, "+inflate chunk - mz_err:%d, ddl:%d, dfree:%u, tin:%u, tout:%u", err, deco_data_len, dict_free, total_in, total_out); + + if (!dict_offset && !dict_begin && total_out > _to) + deco_data_len = TINFL_LZ_DICT_SIZE; // jackpot - a full dict worth of data + + /** + * call the callback if: + * - no free space in dict + * - accumulated data in dict is >= prefered chunk size + * - it's a final input chunk + */ + if (!dict_free || deco_data_len >= chunk_size || final){ + //ESP_LOGD(TAG, "dict stat: dfree:%u, ddl:%u, end:%u, tin:%d, tout:%d", dict_free, deco_data_len, final, total_in, total_out); + + /** + * iterate the callback while: + * - no space in dict + * - have data in dict and it's a final chunk + * - have data in dict >= prefered chunk size + * + */ + while (!dict_free || (final && (bool)deco_data_len) || (deco_data_len >= chunk_size)){ + ESP_LOGD(TAG, "CB - idx:%u, head:%u, dbgn:%u, dend:%u, ddatalen:%u, avin:%u, tin:%u, tout:%u, fin:%d", total_out, dictBuff, dict_begin, dict_offset, deco_data_len, avail_in, total_in, total_out, final); // && (err == MZ_STREAM_END) + + // callback can consume only a portion of data from dict + size_t consumed = callback(total_out - deco_data_len, dictBuff + dict_begin, deco_data_len, final && err == MZ_STREAM_END); + + if (!consumed || consumed > deco_data_len) // it's an error not to consume or consume too much of dict data + return MZ_ERRNO; + + // clear the dict if all the data has been consumed so far + if (consumed == deco_data_len){ + dict_free = TINFL_LZ_DICT_SIZE; + dict_offset = 0; + dict_begin = 0; + } else { + dict_begin = (dict_begin+consumed) & (TINFL_LZ_DICT_SIZE - 1); // offset deco data pointer in dict + } + + deco_data_len -= consumed; + } + } + + // if we are done with this chunk of input, than quit + if (!avail_in || err == MZ_STREAM_END) + return err; + + esp_task_wdt_reset(); // feed the dog, flashing highly compressed data (like almost empty FS image) could trigger WDT + + // go another inflate round + } +} + + +int Inflator::inflate_stream_to_cb(Stream &data, int size, inflate_cb_t callback, size_t chunk_size){ + uint8_t buff[INFLATOR_STREAM_BUFF_SIZE]; // stream buffer + + int ctr = INFLATOR_STREAM_DELAY_CTR; // stream wait/retry counter + do { + size_t available = data.available(); + if (!available && size){ + delay(INFLATOR_STREAM_DELAY_MS); + if (--ctr) + continue; + else + return MZ_STREAM_ERROR; + } + ctr = INFLATOR_STREAM_DELAY_CTR; + + // fill the buff from a stream + int len = data.readBytes(buff, (available > sizeof(buff)) ? sizeof(buff) : available); + + // inflate buff + int err = inflate_block_to_cb(buff, len, callback, (len == size), chunk_size); + if (err < 0){ + //ESP_LOGI(TAG, "compressed buff: %02X%02X%02X%02X%02X%02X", buff[0], buff[1], buff[2], buff[3], buff[4], buff[5]); + return err; + } + + size -= len; + } while(size > 0); + + //ESP_LOGW(TAG, "inflate stream %d bytes left", size); + return size ? MZ_STREAM_ERROR : MZ_STREAM_END; +} + +void Inflator::getstat(deco_stat_t &stat){ + stat.in_bytes = total_in; + stat.out_bytes = total_out; +} + + + +/** FlashZ Class implementation **/ + +bool FlashZ::beginz(size_t size, int command, int ledPin, uint8_t ledOn, const char *label){ + if (!deco.init()) // allocate Inflator memory + return false; + + mode_z = true; + return begin(size, command, ledPin, ledOn, label); +} + +size_t FlashZ::writez(const uint8_t *data, size_t len, bool final){ + if (!mode_z) + return write((uint8_t*)data, len); // this cast to (uint8_t*) is a very dirty hack, but Arduino's Updater lib is missing constness on data pointer + + int err = deco.inflate_block_to_cb(data, len, [this](size_t i, const uint8_t* d, size_t s, bool f) -> int { return flash_cb(i, d, s, f); }, final); + + if (err >= MZ_OK) // intermediate or last chunk, ok + return len; + + ESP_LOGE(TAG, "Inflate ERROR: %d", err); + + return 0; // deco error, assume that no data has been written, signal to the caller that something is wrong +} + +void FlashZ::abortz(){ + abort(); + deco.end(); + mode_z = false; +} + +bool FlashZ::endz(bool evenIfRemaining){ + deco.end(); + mode_z = false; + return end(evenIfRemaining); +} + +int FlashZ::flash_cb(size_t index, const uint8_t* data, size_t size, bool final){ + if (!size) + return 0; + + size_t len; + if (final){ + len = size; + } else { + // try to align writes to flash sector size + len = size <= SPI_FLASH_SEC_SIZE ? size : size - (size % SPI_FLASH_SEC_SIZE); + } + size_t _w = write((uint8_t*)data, len); // this cast to (uint8_t*) is a very dirty hack, but Arduino's Updater lib is missing constness on data pointer + if (_w != len){ + //ESP_LOGI(TAG, "magic: %02X%02X%02X%02X%02X%02X", data[0], data[1], data[2], data[3], data[4], data[5]); + ESP_LOGE(TAG, "ERROR, flashed %d of %d bytes chunk, err: %s!", _w, len, errorString()); + return 0; // if written size is less than requested, consider it as a fatal error, since I can't determine proccessed delated size + } + + ESP_LOGI(TAG, "flashed %u bytes", _w); + + return _w; +} + +size_t FlashZ::writezStream(Stream &data, size_t len){ + if (!mode_z) + return writeStream(data); + + int err = deco.inflate_stream_to_cb(data, len, [this](size_t i, const uint8_t* d, size_t s, bool f) -> int { return flash_cb(i, d, s, f); }); + + ESP_LOGI(TAG, "inflate stream err status: %d", err); + + deco_stat_t s; + deco.getstat(s); + return s.in_bytes; +} diff --git a/src/flashz.hpp b/src/flashz.hpp new file mode 100644 index 0000000..bfa8b13 --- /dev/null +++ b/src/flashz.hpp @@ -0,0 +1,226 @@ +/* + ESP32-FlashZ library + + This code implements a library for ESP32-xx family chips and provides an + ability to upload zlib compressed firmware images during OTA updates. + + It derives from Arduino's UpdaterClass and uses in-ROM miniz decompressor to inflate + libz compressed data during firmware flashing process + + Copyright (C) Emil Muratov, 2022 + GitHub: https://github.com/vortigont/esp32-flashz + + Lib code based on esptool's implementation https://github.com/espressif/esptool/ + so it inherits it's GPL-2.0 license + + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License version 2 for more details. + * + * You should have received a copy of the GNU General Public License version 2 + * along with this library; if not, get one at + * https://opensource.org/licenses/GPL-2.0 + */ + + +#pragma once +#include +#include +#include + +#ifndef ESP_IMAGE_HEADER_MAGIC +#define ESP_IMAGE_HEADER_MAGIC 0xE9 +#endif +#define GZ_HEADER 0x1F +#define ZLIB_HEADER 0x78 + +#define FLASH_CHUNK_SIZE 2*SPI_FLASH_SEC_SIZE // SPI NOR erase sector size is 4096 bytes, so let's take 2 sectors + +// same defines as in miniz.h, excluded in Arduino (todo: add some guards here) +/* Return status codes. MZ_PARAM_ERROR is non-standard. */ +enum +{ + MZ_OK = 0, + MZ_STREAM_END = 1, + MZ_NEED_DICT = 2, + MZ_ERRNO = -1, + MZ_STREAM_ERROR = -2, + MZ_DATA_ERROR = -3, + MZ_MEM_ERROR = -4, + MZ_BUF_ERROR = -5, + MZ_VERSION_ERROR = -6, + MZ_PARAM_ERROR = -10000 +}; + +struct deco_stat_t { + size_t in_bytes; + size_t out_bytes; +}; + + +// inflator callback type +typedef std::function inflate_cb_t; + + + +class Inflator { + bool rdy = 0; /* ready flag, depends on success mem alloc */ + + // stream control vars + const uint8_t *next_in; /* pointer to next byte to read */ + unsigned int avail_in; /* number of bytes available at next_in */ + unsigned int total_in; /* total number of input bytes consumed so far */ + unsigned int total_out; /* total number of inflated output bytes */ + size_t dict_begin, dict_offset, dict_free; /* output dictionary offset pointer and free space counter */ + + // deflator struct + tinfl_decompressor *m_decomp = nullptr; + + // dictionary buff + int decomp_flags; + tinfl_status decomp_status; + uint8_t* dictBuff = nullptr; // heap buffer for deflated dict data + + int inflate(bool final = false); + + +public: + + ~Inflator(){ end(); } + + /** + * @brief Intialize inflator + * allocate mem structs and buffers + * + * @return true + * @return false + */ + bool init(); + + /** + * @brief reset inflator to initial state + * Inflator must be initialized + * + */ + void reset(); + + /** + * @brief end up inflator and dealloc all memory + * + */ + void end(); + + void getstat(deco_stat_t &stat); + + /** + * @brief inflate input buffer into internal dict an call the callback function on inflated data + * by default callback is called only when output dict is full (32k), so it might skip a call if input block + * has not enough input data to inflate dict buffer. Param chunk_size sets _prefered_ buffer size for callback. + * It's OK to consume any amount of bytes via callback except 0. If callback returns 0 than it means an error state + * for callback and signal to abort the Inflator. + * + * @param inBuff - pointer to block of compressed data + * @param len - buffer length + * @param callback - callback function + * @param chunk_size - prefered chunk size for callback + * @return int - MZ_* exit code + */ + int inflate_block_to_cb(const uint8_t* inBuff, size_t len, inflate_cb_t callback, bool final = false, size_t chunk_size = TINFL_LZ_DICT_SIZE); + + + int inflate_stream_to_cb(Stream &data, int size, inflate_cb_t callback, size_t chunk_size = TINFL_LZ_DICT_SIZE); +}; + + +/** + * @brief FlashZ class derives from Arduino's UpdateClass and provides additional methods + * to transparently flash libz (zz) compressed images. ESP32 does not (yet) support native compressed images + * flashing via eboot as in ESP8266. So this class just decompresses input data on the fly and flashes + * inflated image inplace, same way as esptool does + * + */ +class FlashZ : public UpdateClass { + + FlashZ(){}; // hidden c-tor + ~FlashZ(){}; // hidden d-tor + + //deco_stat_t stat; + bool mode_z = false; // need to keep mode state for async writez() calls + Inflator deco; + + /** + * @brief callback for inflator + * writes inflated firmware chunk to flash + * + */ + int flash_cb(size_t index, const uint8_t* data, size_t size, bool final); //> inflate_cb_t + + public: + // this is a singleton, no copy's + FlashZ(const FlashZ&) = delete; + FlashZ& operator=(const FlashZ &) = delete; + FlashZ(FlashZ &&) = delete; + FlashZ & operator=(FlashZ &&) = delete; + + static FlashZ& getInstance(){ + static FlashZ flashz; + return flashz; + } + + /** + * @brief initilize Inflator structs and UpdaterClass + * + * @return true on success + * @return false on Inflator mem allocation error or flash free space error + */ + bool beginz(size_t size=UPDATE_SIZE_UNKNOWN, int command = U_FLASH, int ledPin = -1, uint8_t ledOn = LOW, const char *label = NULL); + + /** + * @brief Writes a buffer to the flash and increments the address + * Returns the amount of processed compressed bytes. Decompressed written size is usually larger + * returns zero in case of any decompression error + * + * @param data + * @param len + * @return processed bytes + */ + size_t writez(const uint8_t *data, size_t len, bool final); + + /** + * @brief Read zlib compressed data from stream, decompress and write it to flash + * size of the stream must be known in order to signal zlib inflator last chunk + * + * @param data Stream object, usually data from a tcp socket + * @param len total length of compressed data to read from stream + * @return size_t number of bytes processed from a stream + */ + size_t writezStream(Stream &data, size_t len); + + /** + * @brief abort running inflator and flash update process + * also releases inflator memory + */ + void abortz(); + + /** + * @brief release inflator memory and run UpdateClass.end() + * returns status of end() call + * + * @return true + * @return false + */ + bool endz(bool evenIfRemaining = true); + + /** + * @brief request stat data from the inflator + * return amount of input/inflated bytes processed so far + * + * @param stat stat structure to update with data + */ + void getstat(deco_stat_t &stat){ deco.getstat(stat); }; +}; \ No newline at end of file From 1d09b6773b2cad6c607744839d267054847cec5a Mon Sep 17 00:00:00 2001 From: Emil Muratov Date: Wed, 28 Sep 2022 23:15:52 +0300 Subject: [PATCH 2/2] POC: implement compressed updates via flashz lib proof of concept - transparent decompression support for zlib compressed images sign+compression is not (yet) supported --- src/esp32FOTA.cpp | 86 ++++++++++++++++++++++++++++++----------------- src/esp32FOTA.hpp | 8 +++-- 2 files changed, 61 insertions(+), 33 deletions(-) diff --git a/src/esp32FOTA.cpp b/src/esp32FOTA.cpp index ce8b9d1..8feb646 100644 --- a/src/esp32FOTA.cpp +++ b/src/esp32FOTA.cpp @@ -36,11 +36,13 @@ #include "mbedtls/md.h" #include "mbedtls/md_internal.h" #include "esp_ota_ops.h" +#include "flashz.cpp" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#define FW_SIGNATURE_LENGTH 512 SemverClass::SemverClass( const char* version ) { @@ -114,9 +116,9 @@ esp32FOTA::esp32FOTA( FOTAConfig_t cfg ) } -esp32FOTA::esp32FOTA(String firmwareType, int firmwareVersion, bool validate, bool allow_insecure_https) +esp32FOTA::esp32FOTA(const char* firmwareType, int firmwareVersion, bool validate, bool allow_insecure_https) { - _cfg.name = firmwareType.c_str(); + _cfg.name = firmwareType; _cfg.sem = SemverClass( firmwareVersion ); _cfg.check_sig = validate; _cfg.unsafe = allow_insecure_https; @@ -126,12 +128,12 @@ esp32FOTA::esp32FOTA(String firmwareType, int firmwareVersion, bool validate, bo } -esp32FOTA::esp32FOTA(String firmwareType, String firmwareSemanticVersion, bool validate, bool allow_insecure_https) +esp32FOTA::esp32FOTA(const char* firmwareType, const char* firmwareSemanticVersion, bool validate, bool allow_insecure_https) { - _cfg.name = firmwareType.c_str(); + _cfg.name = firmwareType; _cfg.check_sig = validate; _cfg.unsafe = allow_insecure_https; - _cfg.sem = SemverClass( firmwareSemanticVersion.c_str() ); + _cfg.sem = SemverClass( firmwareSemanticVersion ); setupCryptoAssets(); debugSemVer("Current firmware version", _cfg.sem.ver() ); @@ -297,8 +299,8 @@ bool esp32FOTA::execOTA() bool esp32FOTA::execOTA( int partition, bool restart_after ) { - String UpdateURL = ""; - String PartitionLabel = ""; + String UpdateURL((char *)0); + String PartitionLabel((char *)0); switch( partition ) { case U_SPIFFS: // spiffs/littlefs/fatfs partition @@ -317,7 +319,7 @@ bool esp32FOTA::execOTA( int partition, bool restart_after ) break; } - size_t contentLength = 0; + int contentLength = 0; bool isValidContentType = false; const char* rootcastr = nullptr; @@ -368,7 +370,8 @@ bool esp32FOTA::execOTA( int partition, bool restart_after ) int httpCode = http.GET(); if( httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY ) { - contentLength = http.header( "Content-Length" ).toInt(); + //contentLength = http.header( "Content-Length" ).toInt(); + contentLength = http.getSize(); String contentType = http.header( "Content-type" ); if( contentType == "application/octet-stream" ) { isValidContentType = true; @@ -421,52 +424,77 @@ bool esp32FOTA::execOTA( int partition, bool restart_after ) log_d("contentLength : %i, isValidContentType : %s", contentLength, String(isValidContentType)); - if( _cfg.check_sig && contentLength != UPDATE_SIZE_UNKNOWN ) { + Stream *stream = http.getStreamPtr(); + if (!stream){ + http.end(); + log_d("bad http stream"); + return false; + } + + bool mode_z = (stream->peek() == ZLIB_HEADER); // check if we get a zlib compressed image + size_t fwsize = mode_z ? UPDATE_SIZE_UNKNOWN : contentLength; // fw_size is unknown if we have a compressed image + + if (_cfg.check_sig && mode_z){ + http.end(); + log_d("compressed'n'signed image is not (yet) supported"); + return false; + } + + if( _cfg.check_sig && contentLength > FW_SIGNATURE_LENGTH) { // If firmware is signed, extract signature and decrease content-length by 512 bytes for signature - contentLength -= 512; + fwsize -= 512; } + // Check if there is enough available space on the partition to perform the Update - bool canBegin = Update.begin( contentLength, partition ); + bool canBegin = mode_z ? FlashZ::getInstance().beginz(fwsize, partition) : FlashZ::getInstance().begin(fwsize, partition); if( !canBegin ) { Serial.println("Not enough space to begin OTA, partition size mismatch?"); http.end(); + if (mode_z) FlashZ::getInstance().abortz(); // release inflator if( onUpdateBeginFail ) onUpdateBeginFail( partition ); return false; } if( onOTAProgress ) { - Update.onProgress( onOTAProgress ); + FlashZ::getInstance().onProgress( onOTAProgress ); } else { - Update.onProgress( [](size_t progress, size_t size) { + FlashZ::getInstance().onProgress( [](size_t progress, size_t size) { if( progress >= size ) Serial.println(); else if( progress > 0) Serial.print("."); }); } - Stream& stream = http.getStream(); - + // todo: this is a waste of stack mem unsigned char signature[512]; if( _cfg.check_sig ) { - stream.readBytes( signature, 512 ); + stream->readBytes( signature, 512 ); } Serial.printf("Begin %s OTA. This may take 2 - 5 mins to complete. Things might be quiet for a while.. Patience!\n", partition==U_FLASH?"Firmware":"Filesystem"); // Some activity may appear in the Serial monitor during the update (depends on Update.onProgress). // This may take 2 - 5mins to complete - size_t written = Update.writeStream( stream ); + size_t written = mode_z ? FlashZ::getInstance().writezStream(*stream, contentLength) : FlashZ::getInstance().writeStream(*stream); + http.end(); + stream = nullptr; - if ( written == contentLength) { - Serial.println("Written : " + String(written) + " successfully"); - } else if ( contentLength == UPDATE_SIZE_UNKNOWN ) { + if (fwsize == UPDATE_SIZE_UNKNOWN) // match compressed fw size to responce length + fwsize = contentLength; + + if ( written == fwsize) { Serial.println("Written : " + String(written) + " successfully"); - contentLength = written; // populate value as it was unknown until now +// } else if ( contentLength == UPDATE_SIZE_UNKNOWN ) { // this is impossible +// Serial.println("Written : " + String(written) + " successfully"); +// contentLength = written; // populate value as it was unknown until now } else { Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Premature end of stream?"); - contentLength = written; // flatten value to prevent overflow when checking signature + //contentLength = written; // flatten value to prevent overflow when checking signature + //fwsize = written; + FlashZ::getInstance().abortz(); // this should be an error actually if we wrote less than provided content size + return false; } - if (!Update.end()) { - Serial.println("An Update Error Occurred. Error #: " + String(Update.getError())); + if (!FlashZ::getInstance().endz()) { + Serial.println("An Update Error Occurred. Error #: " + String(FlashZ::getInstance().getError())); // #define UPDATE_ERROR_OK (0) // #define UPDATE_ERROR_WRITE (1) // #define UPDATE_ERROR_ERASE (2) @@ -483,8 +511,6 @@ bool esp32FOTA::execOTA( int partition, bool restart_after ) return false; } - http.end(); - if( onUpdateEnd ) onUpdateEnd( partition ); if( _cfg.check_sig ) { // check signature @@ -538,7 +564,7 @@ bool esp32FOTA::execOTA( int partition, bool restart_after ) } } //Serial.println("OTA Update complete!"); - if (Update.isFinished()) { + if (FlashZ::getInstance().isFinished()) { if( onUpdateFinished ) onUpdateFinished( partition, restart_after ); @@ -580,8 +606,8 @@ void esp32FOTA::getPartition( int update_partition ) bool esp32FOTA::checkJSONManifest(JsonVariant doc) { if(strcmp(doc["type"].as(), _cfg.name) != 0) { - log_d("Payload type in manifest %s doesn't match current firmware %s", doc["type"].as(), _cfg.name ); - log_d("Doesn't match type: %s", _cfg.name ); + log_d("Payload type in manifest '%s' doesn't match current firmware '%s'", doc["type"].as(), _cfg.name ); + log_d("Doesn't match type: '%s'", _cfg.name ); return false; // Move to the next entry in the manifest } log_i("Payload type in manifest %s matches current firmware %s", doc["type"].as(), _cfg.name ); diff --git a/src/esp32FOTA.hpp b/src/esp32FOTA.hpp index b7e8aa7..f955ed3 100644 --- a/src/esp32FOTA.hpp +++ b/src/esp32FOTA.hpp @@ -148,8 +148,10 @@ class esp32FOTA esp32FOTA(); esp32FOTA( FOTAConfig_t cfg ); - esp32FOTA(String firwmareType, int firwmareVersion, bool validate = false, bool allow_insecure_https = false ); - esp32FOTA(String firwmareType, String firmwareSemanticVersion, bool validate = false, bool allow_insecure_https = false ); + esp32FOTA(const char* firwmareType, int firwmareVersion, bool validate = false, bool allow_insecure_https = false ); + esp32FOTA(const String &firwmareType, int firwmareVersion, bool validate = false, bool allow_insecure_https = false ) : esp32FOTA(firwmareType.c_str(), firwmareVersion, validate, allow_insecure_https){}; + esp32FOTA(const char* firwmareType, const char* firmwareSemanticVersion, bool validate = false, bool allow_insecure_https = false ); + esp32FOTA(const String &firwmareType, const String &firmwareSemanticVersion, bool validate = false, bool allow_insecure_https = false ) : esp32FOTA(firwmareType.c_str(), firmwareSemanticVersion.c_str(), validate, allow_insecure_https){}; ~esp32FOTA(); template void setPubKey( T* asset ) { _cfg.pub_key = (CryptoAsset*)asset; _cfg.check_sig = true; } @@ -164,7 +166,7 @@ class esp32FOTA int getPayloadVersion(); void getPayloadVersion(char * version_string); - void setManifestURL( String manifest_url ) { _cfg.manifest_url = manifest_url.c_str(); } + void setManifestURL( const String &manifest_url ) { _cfg.manifest_url = manifest_url.c_str(); } void useDeviceId( bool use=true ) { _cfg.use_device_id = use; } bool validate_sig( const esp_partition_t* partition, unsigned char *signature, uint32_t firmware_size );