From d542ea1f70c464b33a955ab1c56620ad0f18824c Mon Sep 17 00:00:00 2001
From: "Zachary J. Fields" <zachary_fields@yahoo.com>
Date: Wed, 14 Aug 2024 09:25:44 -0500
Subject: [PATCH 1/2] feat: Notecard OTA Support

---
 src/AIoTC_Config.h                         |  68 +--
 src/ArduinoIoTCloudNotecard.cpp            |   9 +
 src/ArduinoIoTCloudNotecard.h              |  29 ++
 src/ota/implementation/OTAEsp32.h          |   4 +
 src/ota/implementation/OTANanoRP2040.h     |   4 +
 src/ota/implementation/OTASTM32H7.h        |   7 +-
 src/ota/implementation/OTASamd.h           |   3 +-
 src/ota/interface/OTAInterface.h           |   2 +
 src/ota/interface/OTAInterfaceDefault.cpp  |  28 +-
 src/ota/interface/OTAInterfaceDefault.h    |   2 +-
 src/ota/interface/OTAInterfaceNotecard.cpp | 488 +++++++++++++++++++++
 src/ota/interface/OTAInterfaceNotecard.h   | 105 +++++
 src/tls/bearssl/dec32be.c                  |   2 +-
 src/tls/bearssl/enc32be.c                  |   2 +-
 src/tls/bearssl/sha2small.c                |   2 +-
 15 files changed, 702 insertions(+), 53 deletions(-)
 create mode 100644 src/ota/interface/OTAInterfaceNotecard.cpp
 create mode 100644 src/ota/interface/OTAInterfaceNotecard.h

diff --git a/src/AIoTC_Config.h b/src/AIoTC_Config.h
index 891580133..45217eb61 100644
--- a/src/AIoTC_Config.h
+++ b/src/AIoTC_Config.h
@@ -60,40 +60,6 @@
 
 #if !defined(HAS_NOTECARD)
 
-#if defined(ARDUINO_SAMD_MKRWIFI1010) || defined(ARDUINO_SAMD_NANO_33_IOT)
-  #define OTA_STORAGE_SNU         (1)
-#else
-  #define OTA_STORAGE_SNU         (0)
-#endif
-
-#if defined(ARDUINO_NANO_RP2040_CONNECT)
-  #define OTA_STORAGE_SFU         (1)
-#else
-  #define OTA_STORAGE_SFU         (0)
-#endif
-
-#ifdef ARDUINO_SAMD_MKRGSM1400
-  #define OTA_STORAGE_SSU         (1) // OTA_STORAGE_SSU is not implemented yet in OTASamd
-#else
-  #define OTA_STORAGE_SSU         (0)
-#endif
-
-#if defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_NICLA_VISION) || defined(ARDUINO_OPTA) || defined(ARDUINO_GIGA)
-  #define OTA_STORAGE_PORTENTA_QSPI   (1)
-#else
-  #define OTA_STORAGE_PORTENTA_QSPI   (0)
-#endif
-
-#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_UNOR4_WIFI)
-  #define OTA_STORAGE_ESP         (1)
-#endif
-
-#if (OTA_STORAGE_SFU || OTA_STORAGE_SNU || OTA_STORAGE_PORTENTA_QSPI || OTA_STORAGE_ESP)
-  #define OTA_ENABLED             (1)
-#else
-  #define OTA_ENABLED             (0)
-#endif
-
 #if defined(ARDUINO_SAMD_MKRGSM1400) || defined(ARDUINO_SAMD_MKR1000) ||   \
   defined(ARDUINO_SAMD_MKRNB1500) || defined(ARDUINO_PORTENTA_H7_M7)      ||   \
   defined (ARDUINO_NANO_RP2040_CONNECT) || defined(ARDUINO_OPTA) || \
@@ -143,6 +109,40 @@
 
 #endif // HAS_NOTECARD
 
+#if defined(ARDUINO_SAMD_MKRWIFI1010) || defined(ARDUINO_SAMD_NANO_33_IOT)
+  #define OTA_STORAGE_SNU         (1)
+#else
+  #define OTA_STORAGE_SNU         (0)
+#endif
+
+#if defined(ARDUINO_NANO_RP2040_CONNECT)
+  #define OTA_STORAGE_SFU         (1)
+#else
+  #define OTA_STORAGE_SFU         (0)
+#endif
+
+#ifdef ARDUINO_SAMD_MKRGSM1400
+  #define OTA_STORAGE_SSU         (1) // OTA_STORAGE_SSU is not implemented yet in OTASamd
+#else
+  #define OTA_STORAGE_SSU         (0)
+#endif
+
+#if defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_NICLA_VISION) || defined(ARDUINO_OPTA) || defined(ARDUINO_GIGA)
+  #define OTA_STORAGE_PORTENTA_QSPI   (1)
+#else
+  #define OTA_STORAGE_PORTENTA_QSPI   (0)
+#endif
+
+#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_UNOR4_WIFI)
+  #define OTA_STORAGE_ESP         (1)
+#endif
+
+#if (OTA_STORAGE_SFU || OTA_STORAGE_SNU || OTA_STORAGE_PORTENTA_QSPI || OTA_STORAGE_ESP)
+  #define OTA_ENABLED             (1)
+#else
+  #define OTA_ENABLED             (0)
+#endif
+
 #if defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_NICLA_VISION) || defined(ARDUINO_OPTA) || defined(ARDUINO_GIGA)
   #define BEAR_SSL_CLIENT_IBUF_SIZE (16384 + 325) // Allows download from storage API
   #define BOARD_STM32H7
diff --git a/src/ArduinoIoTCloudNotecard.cpp b/src/ArduinoIoTCloudNotecard.cpp
index aeab0b767..753a33103 100644
--- a/src/ArduinoIoTCloudNotecard.cpp
+++ b/src/ArduinoIoTCloudNotecard.cpp
@@ -66,6 +66,10 @@ ArduinoIoTCloudNotecard::ArduinoIoTCloudNotecard()
   ,_notecard_polling_interval_ms{DEFAULT_READ_INTERVAL_MS}
   ,_interrupt_pin{-1}
   ,_data_available{false}
+#if OTA_ENABLED
+  ,_ota(&_message_stream)
+  ,_get_ota_confirmation{nullptr}
+#endif
 {
 
 }
@@ -99,6 +103,11 @@ int ArduinoIoTCloudNotecard::begin(ConnectionHandler &connection_, int interrupt
   // Begin the Notecard time service
   _time_service.begin(&connection_);
 
+#if OTA_ENABLED
+  // Configure the OTA interface
+  _ota.setConnection(&connection_);
+#endif
+
   // Setup retry timers
   _connection_attempt.begin(AIOT_CONFIG_RECONNECTION_RETRY_DELAY_ms, AIOT_CONFIG_MAX_RECONNECTION_RETRY_DELAY_ms);
 
diff --git a/src/ArduinoIoTCloudNotecard.h b/src/ArduinoIoTCloudNotecard.h
index d43147c28..ed1220af5 100644
--- a/src/ArduinoIoTCloudNotecard.h
+++ b/src/ArduinoIoTCloudNotecard.h
@@ -19,6 +19,10 @@
 #include "ArduinoIoTCloudThing.h"
 #include "ArduinoIoTCloudDevice.h"
 
+#if OTA_ENABLED
+#include "ota/OTA.h"
+#endif /* OTA_ENABLED */
+
 /******************************************************************************
  * DEFINES
  ******************************************************************************/
@@ -33,6 +37,10 @@
  * TYPEDEF
  ******************************************************************************/
 
+#if OTA_ENABLED
+typedef bool (*onOTARequestCallbackFunc)(void);
+#endif /* OTA_ENABLED */
+
 /******************************************************************************
  * CLASS DECLARATION
  ******************************************************************************/
@@ -82,6 +90,22 @@ class ArduinoIoTCloudNotecard : public ArduinoIoTCloudClass
      */
     inline void setNotecardPollingInterval(uint32_t interval_ms) { _notecard_polling_interval_ms = ((interval_ms < 250) ? 250 : interval_ms); }
 
+#if OTA_ENABLED
+    /* The callback is triggered when the OTA is initiated and it gets executed until _ota_req flag is cleared.
+     * It should return true when the OTA can be applied or false otherwise.
+     * See example ArduinoIoTCloud-DeferredOTA.ino
+     */
+    inline void onOTARequestCb(onOTARequestCallbackFunc cb) {
+      _get_ota_confirmation = cb;
+
+      if(_get_ota_confirmation) {
+        _ota.setOtaPolicies(OTACloudProcessInterface::ApprovalRequired);
+      } else {
+        _ota.setOtaPolicies(OTACloudProcessInterface::None);
+      }
+    }
+#endif /* OTA_ENABLED */
+
   private:
 
     enum class State
@@ -104,6 +128,11 @@ class ArduinoIoTCloudNotecard : public ArduinoIoTCloudClass
     int _interrupt_pin;
     volatile bool _data_available;
 
+#if OTA_ENABLED
+    ArduinoCloudOTA _ota;
+    onOTARequestCallbackFunc _get_ota_confirmation;
+#endif /* OTA_ENABLED */
+
     inline virtual PropertyContainer &getThingPropertyContainer() override { return _thing.getPropertyContainer(); }
 
     State handle_ConnectPhy();
diff --git a/src/ota/implementation/OTAEsp32.h b/src/ota/implementation/OTAEsp32.h
index b904308e8..1edf62f25 100644
--- a/src/ota/implementation/OTAEsp32.h
+++ b/src/ota/implementation/OTAEsp32.h
@@ -10,7 +10,11 @@
 
 #pragma once
 
+#ifndef HAS_NOTECARD
 #include "ota/interface/OTAInterfaceDefault.h"
+#else
+#include "ota/interface/OTAInterfaceNotecard.h"
+#endif
 
 class ESP32OTACloudProcess: public OTADefaultCloudProcessInterface {
 public:
diff --git a/src/ota/implementation/OTANanoRP2040.h b/src/ota/implementation/OTANanoRP2040.h
index dd165d27e..5cea251da 100644
--- a/src/ota/implementation/OTANanoRP2040.h
+++ b/src/ota/implementation/OTANanoRP2040.h
@@ -10,7 +10,11 @@
 
 #pragma once
 
+#ifndef HAS_NOTECARD
 #include "ota/interface/OTAInterfaceDefault.h"
+#else
+#include "ota/interface/OTAInterfaceNotecard.h"
+#endif
 
 #include "FATFileSystem.h"
 #include "FlashIAPBlockDevice.h"
diff --git a/src/ota/implementation/OTASTM32H7.h b/src/ota/implementation/OTASTM32H7.h
index 0b56f20fe..2718959e5 100644
--- a/src/ota/implementation/OTASTM32H7.h
+++ b/src/ota/implementation/OTASTM32H7.h
@@ -9,7 +9,12 @@
 */
 
 #pragma once
+
+#ifndef HAS_NOTECARD
 #include "ota/interface/OTAInterfaceDefault.h"
+#else
+#include "ota/interface/OTAInterfaceNotecard.h"
+#endif
 
 #include <QSPIFBlockDevice.h>
 
@@ -47,7 +52,7 @@ class STM32H7OTACloudProcess: public OTADefaultCloudProcessInterface {
   // 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
+  // when the download is correctly finished we set the mcu to use the newly downloaded binary
   virtual OTACloudProcessInterface::State flashOTA() override;
 
   // we reboot the device
diff --git a/src/ota/implementation/OTASamd.h b/src/ota/implementation/OTASamd.h
index 3f448c779..c029f15c6 100644
--- a/src/ota/implementation/OTASamd.h
+++ b/src/ota/implementation/OTASamd.h
@@ -10,9 +10,10 @@
 
 #pragma once
 
-#include "ota/interface/OTAInterface.h"
 #include <Arduino_DebugUtils.h>
 
+#include "ota/interface/OTAInterface.h"
+
 class SAMDOTACloudProcess: public OTACloudProcessInterface {
 public:
   SAMDOTACloudProcess(MessageStream *ms);
diff --git a/src/ota/interface/OTAInterface.h b/src/ota/interface/OTAInterface.h
index a62b7cb21..7aa2e12ca 100644
--- a/src/ota/interface/OTAInterface.h
+++ b/src/ota/interface/OTAInterface.h
@@ -26,6 +26,7 @@
 /******************************************************************************
  * CLASS DECLARATION
  ******************************************************************************/
+class ConnectionHandler;  // Forward declaration
 
 class OTACloudProcessInterface: public CloudProcess {
 public:
@@ -86,6 +87,7 @@ class OTACloudProcessInterface: public CloudProcess {
   virtual void handleMessage(Message*);
   // virtual CloudProcess::State getState();
   // virtual void hook(State s, void* action);
+  inline virtual void setConnection(ConnectionHandler * connection) { (void)connection; }
   virtual void update() { handleMessage(nullptr); }
 
   inline void approveOta()                      { policies |= Approved; }
diff --git a/src/ota/interface/OTAInterfaceDefault.cpp b/src/ota/interface/OTAInterfaceDefault.cpp
index 4f68d774c..fb61276d2 100644
--- a/src/ota/interface/OTAInterfaceDefault.cpp
+++ b/src/ota/interface/OTAInterfaceDefault.cpp
@@ -9,7 +9,7 @@
 */
 #include <AIoTC_Config.h>
 
-#if OTA_ENABLED && ! defined(OFFLOADED_DOWNLOAD)
+#if OTA_ENABLED && ! defined(OFFLOADED_DOWNLOAD) && ! defined(HAS_NOTECARD)
 #include "OTAInterfaceDefault.h"
 #include "../OTA.h"
 
@@ -32,6 +32,8 @@ OTACloudProcessInterface::State OTADefaultCloudProcessInterface::startOTA() {
   assert(OTACloudProcessInterface::context != nullptr);
   assert(context == nullptr);
 
+  DEBUG_DEBUG("OTADefaultCloudProcessInterface::%s initializing download from \"%s\"", __FUNCTION__, OTACloudProcessInterface::context->url);
+
   context = new Context(
     OTACloudProcessInterface::context->url,
     [this](uint8_t c) {
@@ -58,27 +60,27 @@ OTACloudProcessInterface::State OTADefaultCloudProcessInterface::startOTA() {
   http_client->endRequest();
 
   if(res == HTTP_ERROR_CONNECTION_FAILED) {
-    DEBUG_VERBOSE("OTA ERROR: http client error connecting to server \"%s:%d\"",
+    DEBUG_ERROR("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);
+    DEBUG_ERROR("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);
+    DEBUG_ERROR("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);
+    DEBUG_ERROR("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
+  // 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");
+    DEBUG_ERROR("OTA ERROR: The response header doesn't contain \"ContentLength\" field");
     return HttpHeaderErrorFail;
   }
 
@@ -107,7 +109,7 @@ OTACloudProcessInterface::State OTADefaultCloudProcessInterface::fetch() {
     http_res = http_client->read(context->buffer, context->buf_len);
 
     if(http_res < 0) {
-      DEBUG_VERBOSE("OTA ERROR: Download read error %d", http_res);
+      DEBUG_ERROR("OTA ERROR: Download read error %d", http_res);
       res = OtaDownloadFail;
       goto exit;
     }
@@ -115,7 +117,7 @@ OTACloudProcessInterface::State OTADefaultCloudProcessInterface::fetch() {
     parseOta(context->buffer, http_res);
 
     if(context->writeError) {
-      DEBUG_VERBOSE("OTA ERROR: File write error");
+      DEBUG_ERROR("OTA ERROR: File write error");
       res = ErrorWriteUpdateFileFail;
       goto exit;
     }
@@ -130,17 +132,17 @@ OTACloudProcessInterface::State OTADefaultCloudProcessInterface::fetch() {
     // validate CRC
     context->calculatedCrc32 ^= 0xFFFFFFFF; // finalize CRC
     if(context->header.header.crc32 == context->calculatedCrc32) {
-      DEBUG_VERBOSE("Ota download completed successfully");
+      DEBUG_DEBUG("OTADefaultCloudProcessInterface::%s OTA download completed successfully", __FUNCTION__);
       res = FlashOTA;
     } else {
       res = OtaHeaderCrcFail;
     }
   } else if(context->downloadState == OtaDownloadError) {
-    DEBUG_VERBOSE("OTA ERROR: OtaDownloadError");
+    DEBUG_ERROR("OTA ERROR: OtaDownloadError");
 
     res = OtaDownloadFail;
   } else if(context->downloadState == OtaDownloadMagicNumberMismatch) {
-    DEBUG_VERBOSE("OTA ERROR: Magic number mismatch");
+    DEBUG_ERROR("OTA ERROR: Magic number mismatch");
     res = OtaHeaderMagicNumberFail;
   }
 
@@ -196,7 +198,7 @@ void OTADefaultCloudProcessInterface::parseOta(uint8_t* buffer, size_t buf_len)
       context->downloadedSize += (cursor-buffer);
 
       if((millis() - context->lastReportTime) > 10000) { // Report the download progress each X millisecond
-        DEBUG_VERBOSE("OTA Download Progress %d/%d", context->downloadedSize, contentLength);
+        DEBUG_VERBOSE("OTADefaultCloudProcessInterface::%s [%d] OTA Download Progress %d/%d", __FUNCTION__, ::millis(), context->downloadedSize, contentLength);
 
         reportStatus(context->downloadedSize);
         context->lastReportTime = millis();
diff --git a/src/ota/interface/OTAInterfaceDefault.h b/src/ota/interface/OTAInterfaceDefault.h
index 95384817f..7fa3567e3 100644
--- a/src/ota/interface/OTAInterfaceDefault.h
+++ b/src/ota/interface/OTAInterfaceDefault.h
@@ -11,7 +11,7 @@
 
 #include <AIoTC_Config.h>
 
-#if OTA_ENABLED && ! defined(OFFLOADED_DOWNLOAD)
+#if OTA_ENABLED && ! defined(OFFLOADED_DOWNLOAD) && ! defined(HAS_NOTECARD)
 #include <Arduino.h>
 
 #include <ArduinoHttpClient.h>
diff --git a/src/ota/interface/OTAInterfaceNotecard.cpp b/src/ota/interface/OTAInterfaceNotecard.cpp
new file mode 100644
index 000000000..5edf44d5b
--- /dev/null
+++ b/src/ota/interface/OTAInterfaceNotecard.cpp
@@ -0,0 +1,488 @@
+/*
+  This file is part of the ArduinoIoTCloud library.
+
+  Copyright 2024 Blues (http://www.blues.com/)
+
+  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 "AIoTC_Config.h"
+
+#if defined(HAS_NOTECARD)
+#if OTA_ENABLED && not defined(OFFLOADED_DOWNLOAD)
+
+#include "OTAInterfaceNotecard.h"
+
+#include <Notecard.h>
+
+#include "ota/OTA.h"
+
+/******************************************************************************
+ * DEFINES
+ ******************************************************************************/
+
+#define OTA_DEFAULT_SEGMENT_SIZE 32768
+#define OTA_XFER_MAX_TRIES 3
+
+/******************************************************************************
+ * CONSTANTS
+ ******************************************************************************/
+
+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
+};
+
+/******************************************************************************
+ * LOCAL MODULE FUNCTIONS
+ ******************************************************************************/
+
+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;
+}
+
+/******************************************************************************
+ * CTOR/DTOR
+ ******************************************************************************/
+
+OTADefaultCloudProcessInterface::OTADefaultCloudProcessInterface(MessageStream *ms, Client* client)
+: OTACloudProcessInterface(ms)
+, _connection(nullptr)
+, _context(nullptr)
+{
+  (void)client;  // Unnecessary for this implementation
+}
+
+OTADefaultCloudProcessInterface::~OTADefaultCloudProcessInterface() {
+  reset();
+}
+
+OTADefaultCloudProcessInterface::Context::Context(
+  const char* url, std::function<void(uint8_t)> putc)
+    : parsed_url(url)
+    , downloadState(OtaDownloadHeader)
+    , calculatedCrc32(0xFFFFFFFF)
+    , headerCopiedBytes(0)
+    , decodedSize(0)
+    , downloadedSize(0)
+    , writeError(false)
+    , chunk(0)
+    , cloud_max(OTA_DEFAULT_SEGMENT_SIZE)
+    , cloud_offset(0)
+    , cloud_response(206)
+    , cloud_total(0)
+    , cobs(0)
+    , length(0)
+    , offset(0)
+    , segment(0)
+    , try_count(0)
+    , decoder(putc)
+    , max_xfer_len(0) { }
+
+/******************************************************************************
+ * PUBLIC MEMBER FUNCTIONS
+ ******************************************************************************/
+
+OTACloudProcessInterface::State OTADefaultCloudProcessInterface::startOTA() {
+  assert(OTACloudProcessInterface::context != nullptr);
+  assert(_context == nullptr);
+
+  OTACloudProcessInterface::State result;
+
+  DEBUG_DEBUG("OTADefaultCloudProcessInterface::%s initializing download from \"%s\"", __FUNCTION__, OTACloudProcessInterface::context->url);
+
+  // Clear the Notecard binary store
+  const uint32_t max_storage = clearNotecardBinaryStore();
+
+  // Establish the context
+  _context = new Context(
+    OTACloudProcessInterface::context->url,
+    [this](uint8_t c) {
+      if (this->writeFlash(&c, 1) != 1) {
+        this->_context->writeError = true;
+      }
+    }
+  );
+  _context->cloud_max = ((max_storage < OTA_DEFAULT_SEGMENT_SIZE) ? max_storage : OTA_DEFAULT_SEGMENT_SIZE);
+
+  // The OTA context operates on a fixed-size buffer (`OTA_CTX_BUF_LEN`), so
+  // we need to ensure the encoded data we download does not overrun the buffer.
+  // Both `offset` and `length` are based on the decoded data size, so we must
+  // use `NoteBinaryCodecMaxDecodedLength()` to determine the maximum length of
+  // the data we may safely request.
+  _context->max_xfer_len = NoteBinaryCodecMaxDecodedLength(OTA_CTX_BUF_LEN);
+
+  // Validate the context
+  if (!_context->cloud_max) {
+    DEBUG_ERROR("OTA ERROR: Notecard binary store does not have available space");
+    result = OTACloudProcessInterface::State::OtaDownloadFail;
+  } else if (strcmp(_context->parsed_url.schema(), "https")) {
+    DEBUG_ERROR("OTA ERROR: Invalid URL schema");
+    result = OTACloudProcessInterface::State::UrlParseErrorFail;
+  } else {
+    result = OTACloudProcessInterface::State::Fetch;
+  }
+
+  return result;
+}
+
+OTACloudProcessInterface::State OTADefaultCloudProcessInterface::fetch() {
+  OTACloudProcessInterface::State result;
+
+  const bool bytes_to_process = (_context->offset < _context->length);
+  const bool download_complete = (_context->downloadState == OtaDownloadCompleted);
+  if (!bytes_to_process && !download_complete) {
+    // Prepare for the next segment
+    clearNotecardBinaryStore();
+    _context->cloud_offset += _context->offset;
+
+    // Download the binary to the Notecard
+    size_t http_response_status_code = downloadBinaryToNotecardFromPath(_context->parsed_url.path());
+    if ((200 == http_response_status_code) || (206 == http_response_status_code)) {
+      _context->downloadedSize = (_context->cloud_offset + _context->length);
+      DEBUG_INFO("OTA: Downloaded %u of %u bytes (%u%% complete)", _context->downloadedSize, _context->cloud_total, ((_context->downloadedSize * 100) / _context->cloud_total));
+      reportStatus(_context->downloadedSize);
+      result = OTACloudProcessInterface::State::Fetch;
+    } else {
+      DEBUG_ERROR("OTA ERROR: Failed to download binary to Notecard from OTA server");
+      result = OTACloudProcessInterface::State::OtaDownloadFail;
+    }
+  } else if (bytes_to_process) {
+    // Transfer binary from Notecard into context buffer
+    const uint32_t context_buffer_len = loadContextFromNotecardBinaryStore();
+    if (_context->downloadState == OtaDownloadError || _context->downloadState == OtaDownloadMagicNumberMismatch) {
+      DEBUG_ERROR("OTA ERROR: Failed to transfer binary from Notecard");
+      result = OTACloudProcessInterface::State::OtaDownloadFail;
+    } else {
+      // Write bytes from the context buffer into device flash.
+      parseOta(_context->buffer, context_buffer_len);
+      if (_context->downloadState == OtaDownloadError) {
+        result = OTACloudProcessInterface::State::OtaDownloadFail;
+      } else if (_context->downloadState == OtaDownloadMagicNumberMismatch) {
+        result = OTACloudProcessInterface::State::OtaHeaderMagicNumberFail;
+      } else if (_context->writeError) {
+        DEBUG_ERROR("OTA ERROR: Failed to write binary to device flash");
+        result = OTACloudProcessInterface::State::ErrorWriteUpdateFileFail;
+      } else {
+        result = OTACloudProcessInterface::State::Fetch;
+      }
+    }
+  } else if (download_complete) {
+    // Validate CRC
+    _context->calculatedCrc32 ^= 0xFFFFFFFF; // finalize CRC
+    if (_context->header.header.crc32 == _context->calculatedCrc32) {
+      DEBUG_INFO("OTA: OTA binary passed CRC validation");
+      result = OTACloudProcessInterface::State::FlashOTA;
+    } else {
+      DEBUG_ERROR("OTA ERROR: OTA binary failed CRC validation");
+      result = OTACloudProcessInterface::State::OtaHeaderCrcFail;
+    }
+  } else {
+    // It is not possible to reach this point
+    DEBUG_VERBOSE("OTADefaultCloudProcessInterface::%s [%d] ERROR! Hacking too much time!", __FUNCTION__, ::millis());
+    result = OTACloudProcessInterface::State::OtaDownloadFail;
+  }
+
+  return result;
+}
+
+void OTADefaultCloudProcessInterface::reset() {
+  // free the context pointer
+  if(_context != nullptr) {
+    delete _context;
+    _context = nullptr;
+  }
+  clearNotecardBinaryStore();
+  DEBUG_DEBUG("OTADefaultCloudProcessInterface::%s complete", __FUNCTION__);
+}
+
+/******************************************************************************
+ * PRIVATE SUPPORT FUNCTIONS
+ ******************************************************************************/
+
+uint32_t OTADefaultCloudProcessInterface::clearNotecardBinaryStore (void) {
+  uint32_t result;
+
+  const Notecard &notecard = reinterpret_cast<NotecardConnectionHandler *>(_connection)->getNotecard();
+
+  if (J *req = notecard.newRequest("card.binary")) {
+    JAddBoolToObject(req, "delete", true);
+    if (J *rsp = notecard.requestAndResponse(req)) {
+      // Check the response for errors
+      if (notecard.responseError(rsp)) {
+        const char *err = JGetString(rsp, "err");
+        DEBUG_ERROR("%s\n", err);
+        result = 0;
+      } else {
+        result = static_cast<uint32_t>(JGetInt(rsp, "max"));
+        DEBUG_DEBUG("OTADefaultCloudProcessInterface::%s emptied Notecard binary store; %d bytes available", __FUNCTION__, result);
+      }
+      notecard.deleteResponse(rsp);
+    } else {
+      DEBUG_ERROR("OTA ERROR: Failed to receive response from Notecard");
+      result = 0;
+    }
+  } else {
+    DEBUG_ERROR("OTA ERROR: Failed to allocate request: card.binary");
+    result = 0;
+  }
+
+  return result;
+}
+
+size_t OTADefaultCloudProcessInterface::downloadBinaryToNotecardFromPath (const char *bin_path_) {
+  ssize_t result;
+
+  const Notecard &notecard = reinterpret_cast<NotecardConnectionHandler *>(_connection)->getNotecard();
+
+  /** Due to the space constraints of the Notecard, the binary file cannot be
+   * downloaded in a single request, and segmentation of the binary is required.
+   * As a result, the application remains respoonsive, and there is no need to
+   * implement the watchdog feature of the `ArduinoIoTCloud` library.
+   */
+
+  DEBUG_DEBUG("OTADefaultCloudProcessInterface::%s downloading segment %u (range %u to %u) from path \"%s\"...", __FUNCTION__, ++_context->segment, _context->cloud_offset, (_context->cloud_offset + _context->cloud_max), _context->parsed_url.path());
+
+  if (J *req = notecard.newRequest("web.get")) {
+    JAddStringToObject(req, "route", "arduino-iot-cloud.ota");
+    JAddStringToObject(req, "name", bin_path_);
+    JAddStringToObject(req, "content", "application/octet-stream");
+    JAddBoolToObject(req, "binary", true);
+    JAddIntToObject(req, "offset", _context->cloud_offset);
+    JAddIntToObject(req, "max", _context->cloud_max);
+    JAddIntToObject(req, "seconds", 600);
+    if (J *rsp = notecard.requestAndResponse(req)) {
+      // Check the response for errors
+      if (notecard.responseError(rsp)) {
+        const char *err = JGetString(rsp, "err");
+        DEBUG_ERROR("%s\n", err);
+        result = 0;
+      } else {
+        _context->chunk = 0;
+        _context->cloud_total = static_cast<uint32_t>(JGetInt(rsp, "total"));
+        _context->cobs = static_cast<uint32_t>(JGetInt(rsp, "cobs"));
+        _context->length = static_cast<uint32_t>(JGetInt(rsp, "length"));
+        _context->offset = 0;
+        _context->cloud_response = static_cast<uint32_t>(JGetInt(rsp, "result"));
+        DEBUG_DEBUG("OTADefaultCloudProcessInterface::%s captured context | cloud_max: %u cloud_offset: %u cloud_response: %u cloud_total: %u cobs: %u length: %u offset: %u", __FUNCTION__, _context->cloud_max, _context->cloud_offset, _context->cloud_response, _context->cloud_total, _context->cobs, _context->length, _context->offset);
+        if (200 != _context->cloud_response && 206 != _context->cloud_response) {
+          _context->downloadState = OtaDownloadError;
+          DEBUG_ERROR("OTA ERROR: Received error response code: %u", _context->cloud_response);
+          const size_t http_response_msg_len = NoteBinaryCodecMaxEncodedLength(_context->length);
+          char * http_response_msg = (char *)malloc(http_response_msg_len);
+          if (http_response_msg == nullptr) {
+            DEBUG_ERROR("OTA ERROR: Failed to allocate memory for error message");
+          } else if (NoteBinaryStoreReceive(reinterpret_cast<uint8_t *>(http_response_msg), http_response_msg_len, _context->offset, _context->length)) {
+            DEBUG_ERROR("OTA ERROR: Failed to retrieve error message from Notecard binary store");
+          } else {
+            http_response_msg[_context->length] = '\0';
+            DEBUG_ERROR("OTA ERROR: %s", http_response_msg);
+          }
+          free(http_response_msg);
+          clearNotecardBinaryStore();
+        } else {
+          DEBUG_DEBUG("OTADefaultCloudProcessInterface::%s downloaded %u cobs encoded bytes from OTA server", __FUNCTION__, _context->cobs);
+        }
+        result = _context->cloud_response;
+      }
+      notecard.deleteResponse(rsp);
+    } else {
+      DEBUG_ERROR("OTA ERROR: Failed to receive response from Notecard");
+      result = 0;
+    }
+  } else {
+    DEBUG_ERROR("OTA ERROR: Failed to allocate request: web.get");
+    result = 0;
+  }
+
+  return result;
+}
+
+size_t OTADefaultCloudProcessInterface::loadContextFromNotecardBinaryStore (void) {
+  size_t result;
+
+  ++_context->try_count;
+  const uint32_t bytes_remaining = (_context->length - _context->offset);
+  const uint32_t rx_len = (( bytes_remaining > _context->max_xfer_len)
+                            ? _context->max_xfer_len
+                            : bytes_remaining);
+
+  // Transfer the chunk into the context buffer
+  DEBUG_DEBUG("OTADefaultCloudProcessInterface::%s transferring chunk %u of segment %u...", __FUNCTION__, ++_context->chunk, _context->segment);
+  if (NoteBinaryStoreReceive(_context->buffer, OTA_CTX_BUF_LEN, _context->offset, rx_len)) {
+    --_context->chunk;
+    DEBUG_WARNING("OTA WARNING: Failed to receive chunk from Notecard binary store");
+    // Retry the chunk for max retries
+    if (_context->try_count <= OTA_XFER_MAX_TRIES) {
+      DEBUG_INFO("OTA: Will attempt to retry the chunk transfer %d more times", (OTA_XFER_MAX_TRIES - _context->try_count));
+    } else {
+      _context->downloadState = OtaDownloadError;
+      DEBUG_ERROR("OTA ERROR: Failed to receive chunk from Notecard binary store");
+    }
+    result = 0;
+  } else {
+    // Clear try count
+    _context->try_count = 0;
+
+    // Update the offset
+    _context->offset += rx_len;
+
+    DEBUG_DEBUG("OTADefaultCloudProcessInterface::%s transferred %u of %u bytes (segment %u%% complete)", __FUNCTION__, _context->offset, _context->length, ((_context->offset * 100) / _context->length));
+    result = rx_len;
+
+    // Log for the sake of curiosity
+    DEBUG_VERBOSE("OTADefaultCloudProcessInterface::%s [%d] *** Decoded Binary Data ***", __FUNCTION__, ::millis());
+    for (size_t i = 0 ; i < rx_len ; i += 4) {
+      if ((i + 4) <= rx_len) {
+        DEBUG_VERBOSE("OTADefaultCloudProcessInterface::%s [%d] %02X %02X %02X %02X", __FUNCTION__, ::millis(), _context->buffer[i], _context->buffer[(i + 1)], _context->buffer[(i + 2)], _context->buffer[(i + 3)]);
+      } else if ((i + 3) == rx_len) {
+        DEBUG_VERBOSE("OTADefaultCloudProcessInterface::%s [%d] %02X %02X %02X --", __FUNCTION__, ::millis(), _context->buffer[i], _context->buffer[(i + 1)], _context->buffer[(i + 2)]);
+      } else if ((i + 2) == rx_len) {
+        DEBUG_VERBOSE("OTADefaultCloudProcessInterface::%s [%d] %02X %02X -- --", __FUNCTION__, ::millis(), _context->buffer[i], _context->buffer[(i + 1)]);
+      } else {
+        DEBUG_VERBOSE("OTADefaultCloudProcessInterface::%s [%d] %02X -- -- --", __FUNCTION__, ::millis(), _context->buffer[i]);
+      }
+    }
+    DEBUG_VERBOSE("OTADefaultCloudProcessInterface::%s [%d] *** Decoded Binary Data ***", __FUNCTION__, ::millis());
+  }
+
+  return result;
+}
+
+void OTADefaultCloudProcessInterface::parseOta(uint8_t * buffer_, size_t buf_len_) {
+  assert(_context != nullptr); // This should never fail
+
+  for (uint8_t *cursor = buffer_ ; cursor < (buffer_ + buf_len_) ; ) {
+    switch(_context->downloadState) {
+      case OtaDownloadHeader: {
+        DEBUG_VERBOSE("OTADefaultCloudProcessInterface::%s [%d] parsing OTA header...", __FUNCTION__, ::millis());
+        const uint32_t header_bytes_remaining = (sizeof(_context->header.buf) - _context->headerCopiedBytes);
+        const bool more_header_bytes = (buf_len_ < header_bytes_remaining);
+        const uint32_t copied = (more_header_bytes ? buf_len_ : header_bytes_remaining);
+        memcpy((_context->header.buf + _context->headerCopiedBytes), cursor, copied);
+        cursor += copied;
+        _context->headerCopiedBytes += copied;
+
+        if (more_header_bytes) {
+          DEBUG_DEBUG("OTADefaultCloudProcessInterface::%s copied %u of %u header bytes", __FUNCTION__, _context->headerCopiedBytes, sizeof(_context->header.buf));
+          _context->downloadState = OtaDownloadHeader;
+        } else if (sizeof(_context->header.buf) == _context->headerCopiedBytes) {
+          _context->calculatedCrc32 = crc_update(
+            _context->calculatedCrc32,
+            &(_context->header.header.magic_number),
+            (sizeof(_context->header) - offsetof(ota::OTAHeader, header.magic_number))
+          );
+
+          // Validate the number of bytes to be downloaded
+          const uint32_t legacy_excluded_bytes = (sizeof(_context->header.header.len) + sizeof(_context->header.header.crc32));
+          if ((_context->header.header.len + legacy_excluded_bytes) != _context->cloud_total) {
+            DEBUG_ERROR("OTA ERROR: OTA binary length mismatch (header: (%u + %u) != actual: %u)", _context->header.header.len, legacy_excluded_bytes, _context->cloud_total);
+            _context->downloadState = OtaDownloadError;
+            return;
+          }
+
+          // Validate the magic number
+          if (_context->header.header.magic_number != OtaMagicNumber) {
+            DEBUG_ERROR("OTA ERROR: Magic number mismatch");
+            _context->downloadState = OtaDownloadMagicNumberMismatch;
+            return;
+          }
+
+          DEBUG_DEBUG("OTADefaultCloudProcessInterface::%s parsed OTA Header | len: %u crc32: %u magic_number: 0x%08X hdr_version | header_version: %u compression: %s signature: %s spare: %u payload_target: %u payload_major: %u payload_minor: %u payload_patch: %u payload_build_num: %u", __FUNCTION__, _context->header.header.len, _context->header.header.crc32, _context->header.header.magic_number, _context->header.header.hdr_version.field.header_version, (_context->header.header.hdr_version.field.compression ? "true" : "false"), (_context->header.header.hdr_version.field.signature ? "true" : "false"), _context->header.header.hdr_version.field.spare, _context->header.header.hdr_version.field.payload_target, _context->header.header.hdr_version.field.payload_major, _context->header.header.hdr_version.field.payload_minor, _context->header.header.hdr_version.field.payload_patch, _context->header.header.hdr_version.field.payload_build_num);
+          _context->downloadState = OtaDownloadFile;
+        } else {
+          DEBUG_ERROR("OTA ERROR: Failed to parse OTA header");
+          _context->downloadState = OtaDownloadError;
+        }
+        break;
+      }
+      case OtaDownloadFile: {
+        DEBUG_VERBOSE("OTADefaultCloudProcessInterface::%s [%d] parsing OTA file...", __FUNCTION__, ::millis());
+
+        // Decompress and write to flash
+        const uint32_t header_bytes = (cursor - buffer_);
+        if (!_context->decoder.decompress(cursor, (buf_len_ - header_bytes))) {
+          DEBUG_DEBUG("OTADefaultCloudProcessInterface::%s completed data decompression", __FUNCTION__);
+        }
+
+        _context->calculatedCrc32 = crc_update(
+          _context->calculatedCrc32,
+          cursor,
+          (buf_len_ - header_bytes)
+        );
+
+        // Advance the cursor
+        const uint32_t decoded_bytes = (buf_len_ - header_bytes);
+        cursor += decoded_bytes;
+        _context->decodedSize += decoded_bytes;
+        DEBUG_VERBOSE("OTADefaultCloudProcessInterface::%s [%d] decoded and wrote %d of %d bytes to flash", __FUNCTION__, ::millis(), _context->decodedSize, (_context->cloud_total - _context->headerCopiedBytes));
+
+        // Verify there are no more bytes available once the download has completed
+        const bool download_complete = (_context->downloadedSize == _context->cloud_total);
+        const bool transfer_complete = (_context->offset == _context->length);
+        if (_context->downloadedSize > _context->cloud_total) {
+          DEBUG_ERROR("OTA ERROR: Downloaded more bytes than expected");
+          _context->downloadState = OtaDownloadError;
+        } else if (download_complete && transfer_complete) {
+          _context->downloadState = OtaDownloadCompleted;
+        }
+        break;
+      }
+      case OtaDownloadCompleted:
+        DEBUG_VERBOSE("OTADefaultCloudProcessInterface::%s [%d] download complete - exiting - no data to parse", __FUNCTION__, ::millis());
+        return;
+      default:
+        DEBUG_ERROR("OTA ERROR: invalid download state: %d", _context->downloadState);
+        _context->downloadState = OtaDownloadError;
+        return;
+    }
+  }
+}
+
+#endif // OTA_ENABLED && not defined(OFFLOADED_DOWNLOAD)
+#endif // HAS_NOTECARD
diff --git a/src/ota/interface/OTAInterfaceNotecard.h b/src/ota/interface/OTAInterfaceNotecard.h
new file mode 100644
index 000000000..dd4a2ddd3
--- /dev/null
+++ b/src/ota/interface/OTAInterfaceNotecard.h
@@ -0,0 +1,105 @@
+/*
+  This file is part of the ArduinoIoTCloud library.
+
+  Copyright 2024 Blues (http://www.blues.com/)
+
+  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 OTA_INTERFACE_NOTECARD_H_
+#define OTA_INTERFACE_NOTECARD_H_
+
+/******************************************************************************
+ * INCLUDE
+ ******************************************************************************/
+
+#include <Arduino_ConnectionHandler.h>
+#include <ArduinoHttpClient.h>
+#include <URLParser.h>
+
+#include "interfaces/MessageStream.h"
+#include "ota/interface/OTAInterface.h"
+#include "utility/lzss/lzss.h"
+
+/******************************************************************************
+ * DEFINES
+ ******************************************************************************/
+
+#define OTA_CTX_BUF_LEN 1024
+
+/******************************************************************************
+ * CLASS DECLARATION
+ ******************************************************************************/
+
+/**
+ * @brief The OTADefaultCloudProcessInterface class
+ *
+ * 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
+ * Notecard.
+ */
+class OTADefaultCloudProcessInterface: public OTACloudProcessInterface {
+  public:
+    OTADefaultCloudProcessInterface(MessageStream *ms, Client* client=nullptr);
+    virtual ~OTADefaultCloudProcessInterface();
+
+    inline virtual void setConnection(ConnectionHandler * connection) override { _connection = connection; }
+
+  protected:
+    virtual State startOTA() override;
+    virtual State fetch() override;
+    virtual void reset() override;
+    virtual int writeFlash(uint8_t* const buffer, size_t len) = 0;
+
+  private:
+    enum OTADownloadState: uint8_t {
+      OtaDownloadHeader,
+      OtaDownloadFile,
+      OtaDownloadCompleted,
+      OtaDownloadMagicNumberMismatch,
+      OtaDownloadError
+    };
+
+    uint32_t clearNotecardBinaryStore (void);
+    size_t downloadBinaryToNotecardFromPath (const char *bin_path);
+    size_t loadContextFromNotecardBinaryStore (void);
+    void parseOta(uint8_t* buffer, size_t buf_len);
+
+    ConnectionHandler * _connection;
+
+  protected:
+    struct Context {
+      Context(const char* url, std::function<void(uint8_t)> putc);
+
+      ParsedUrl         parsed_url;
+      ota::OTAHeader    header;
+      OTADownloadState  downloadState;
+      uint32_t          calculatedCrc32;
+      uint32_t          headerCopiedBytes;
+      uint32_t          decodedSize;
+      uint32_t          downloadedSize;
+      bool              writeError;
+
+      // Notecard binary store information
+      uint32_t          chunk;
+      uint32_t          cloud_max;
+      uint32_t          cloud_offset;
+      uint32_t          cloud_response;
+      uint32_t          cloud_total;
+      uint32_t          cobs;
+      uint32_t          length;
+      uint32_t          offset;
+      uint32_t          segment;
+      uint32_t          try_count;
+
+      // LZSS decoder
+      LZSSDecoder       decoder;
+
+      size_t max_xfer_len;
+      uint8_t buffer[OTA_CTX_BUF_LEN];
+    } *_context;
+};
+
+#endif /* OTA_INTERFACE_NOTECARD_H_ */
diff --git a/src/tls/bearssl/dec32be.c b/src/tls/bearssl/dec32be.c
index 733381229..249b179a6 100644
--- a/src/tls/bearssl/dec32be.c
+++ b/src/tls/bearssl/dec32be.c
@@ -25,7 +25,7 @@
 #include <AIoTC_Config.h>
 #if defined(BOARD_HAS_ECCX08) || defined(BOARD_HAS_OFFLOADED_ECCX08) || \
     defined(BOARD_HAS_SE050) || defined(ARDUINO_ARCH_ESP32) || \
-	defined(ARDUINO_UNOR4_WIFI)
+    defined(ARDUINO_UNOR4_WIFI) || defined(HAS_NOTECARD)
 
 #include "inner.h"
 
diff --git a/src/tls/bearssl/enc32be.c b/src/tls/bearssl/enc32be.c
index 26fa3cee1..cc41d1d4c 100644
--- a/src/tls/bearssl/enc32be.c
+++ b/src/tls/bearssl/enc32be.c
@@ -25,7 +25,7 @@
 #include <AIoTC_Config.h>
 #if defined(BOARD_HAS_ECCX08) || defined(BOARD_HAS_OFFLOADED_ECCX08) || \
     defined(BOARD_HAS_SE050) || defined(ARDUINO_ARCH_ESP32) || \
-	defined(ARDUINO_UNOR4_WIFI)
+    defined(ARDUINO_UNOR4_WIFI) || defined(HAS_NOTECARD)
 
 #include "inner.h"
 
diff --git a/src/tls/bearssl/sha2small.c b/src/tls/bearssl/sha2small.c
index 04cc71f81..fdd797dc2 100644
--- a/src/tls/bearssl/sha2small.c
+++ b/src/tls/bearssl/sha2small.c
@@ -25,7 +25,7 @@
 #include <AIoTC_Config.h>
 #if defined(BOARD_HAS_ECCX08) || defined(BOARD_HAS_OFFLOADED_ECCX08) || \
     defined(BOARD_HAS_SE050) || defined(ARDUINO_ARCH_ESP32) || \
-	defined(ARDUINO_UNOR4_WIFI)
+    defined(ARDUINO_UNOR4_WIFI) || defined(HAS_NOTECARD)
 
 #include "inner.h"
 

From 3626b2763385022fafb7977f1bb0308197ec7ac0 Mon Sep 17 00:00:00 2001
From: "Zachary J. Fields" <zachary_fields@yahoo.com>
Date: Thu, 12 Sep 2024 12:07:41 -0500
Subject: [PATCH 2/2] fix: OTA build targets

---
 src/AIoTC_Config.h                       | 18 +++++++++++-------
 src/ota/interface/OTAInterface.h         |  2 --
 src/ota/interface/OTAInterfaceNotecard.h |  2 +-
 3 files changed, 12 insertions(+), 10 deletions(-)

diff --git a/src/AIoTC_Config.h b/src/AIoTC_Config.h
index 45217eb61..b93ce67d1 100644
--- a/src/AIoTC_Config.h
+++ b/src/AIoTC_Config.h
@@ -107,18 +107,14 @@
   #define BOARD_HAS_SECURE_ELEMENT
 #endif
 
-#endif // HAS_NOTECARD
-
 #if defined(ARDUINO_SAMD_MKRWIFI1010) || defined(ARDUINO_SAMD_NANO_33_IOT)
   #define OTA_STORAGE_SNU         (1)
 #else
   #define OTA_STORAGE_SNU         (0)
 #endif
 
-#if defined(ARDUINO_NANO_RP2040_CONNECT)
-  #define OTA_STORAGE_SFU         (1)
-#else
-  #define OTA_STORAGE_SFU         (0)
+#if defined(ARDUINO_UNOR4_WIFI)
+  #define OTA_STORAGE_ESP         (1)
 #endif
 
 #ifdef ARDUINO_SAMD_MKRGSM1400
@@ -127,13 +123,21 @@
   #define OTA_STORAGE_SSU         (0)
 #endif
 
+#endif // !defined(HAS_NOTECARD)
+
+#if defined(ARDUINO_NANO_RP2040_CONNECT)
+  #define OTA_STORAGE_SFU         (1)
+#else
+  #define OTA_STORAGE_SFU         (0)
+#endif
+
 #if defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_NICLA_VISION) || defined(ARDUINO_OPTA) || defined(ARDUINO_GIGA)
   #define OTA_STORAGE_PORTENTA_QSPI   (1)
 #else
   #define OTA_STORAGE_PORTENTA_QSPI   (0)
 #endif
 
-#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_UNOR4_WIFI)
+#if defined(ARDUINO_ARCH_ESP32)
   #define OTA_STORAGE_ESP         (1)
 #endif
 
diff --git a/src/ota/interface/OTAInterface.h b/src/ota/interface/OTAInterface.h
index 7aa2e12ca..a62b7cb21 100644
--- a/src/ota/interface/OTAInterface.h
+++ b/src/ota/interface/OTAInterface.h
@@ -26,7 +26,6 @@
 /******************************************************************************
  * CLASS DECLARATION
  ******************************************************************************/
-class ConnectionHandler;  // Forward declaration
 
 class OTACloudProcessInterface: public CloudProcess {
 public:
@@ -87,7 +86,6 @@ class OTACloudProcessInterface: public CloudProcess {
   virtual void handleMessage(Message*);
   // virtual CloudProcess::State getState();
   // virtual void hook(State s, void* action);
-  inline virtual void setConnection(ConnectionHandler * connection) { (void)connection; }
   virtual void update() { handleMessage(nullptr); }
 
   inline void approveOta()                      { policies |= Approved; }
diff --git a/src/ota/interface/OTAInterfaceNotecard.h b/src/ota/interface/OTAInterfaceNotecard.h
index dd4a2ddd3..c4b3ce756 100644
--- a/src/ota/interface/OTAInterfaceNotecard.h
+++ b/src/ota/interface/OTAInterfaceNotecard.h
@@ -45,7 +45,7 @@ class OTADefaultCloudProcessInterface: public OTACloudProcessInterface {
     OTADefaultCloudProcessInterface(MessageStream *ms, Client* client=nullptr);
     virtual ~OTADefaultCloudProcessInterface();
 
-    inline virtual void setConnection(ConnectionHandler * connection) override { _connection = connection; }
+    inline void setConnection(ConnectionHandler * connection) { _connection = connection; }
 
   protected:
     virtual State startOTA() override;