From 059b318bd71f88cc28c5f5c6f1f7ea9cddf528a3 Mon Sep 17 00:00:00 2001 From: pennam Date: Tue, 9 Apr 2024 12:26:49 +0200 Subject: [PATCH 01/48] Move properties containers inside class implementation --- src/ArduinoIoTCloud.cpp | 11 +++++------ src/ArduinoIoTCloud.h | 8 +++----- src/ArduinoIoTCloudLPWAN.cpp | 2 ++ src/ArduinoIoTCloudLPWAN.h | 5 +++++ src/ArduinoIoTCloudTCP.cpp | 3 +++ src/ArduinoIoTCloudTCP.h | 5 +++++ 6 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/ArduinoIoTCloud.cpp b/src/ArduinoIoTCloud.cpp index 84f08f780..ec2d48267 100644 --- a/src/ArduinoIoTCloud.cpp +++ b/src/ArduinoIoTCloud.cpp @@ -27,7 +27,6 @@ ArduinoIoTCloudClass::ArduinoIoTCloudClass() : _connection{nullptr} -, _last_checked_property_index{0} , _time_service(TimeService) , _thing_id{""} , _thing_id_property{nullptr} @@ -44,12 +43,12 @@ ArduinoIoTCloudClass::ArduinoIoTCloudClass() void ArduinoIoTCloudClass::push() { - requestUpdateForAllProperties(_thing_property_container); + requestUpdateForAllProperties(getThingPropertyContainer()); } bool ArduinoIoTCloudClass::setTimestamp(String const & prop_name, unsigned long const timestamp) { - Property * p = getProperty(_thing_property_container, prop_name); + Property * p = getProperty(getThingPropertyContainer(), prop_name); if (p == nullptr) return false; @@ -118,7 +117,7 @@ Property& ArduinoIoTCloudClass::addPropertyReal(String& property, String name, i } Property& ArduinoIoTCloudClass::addPropertyReal(Property& property, String name, int tag, Permission const permission) { - return addPropertyToContainer(_thing_property_container, property, name, permission, tag); + return addPropertyToContainer(getThingPropertyContainer(), property, name, permission, tag); } /* The following methods are deprecated but still used for non-LoRa boards */ @@ -195,9 +194,9 @@ void ArduinoIoTCloudClass::addPropertyRealInternal(Property& property, String na } if (seconds == ON_CHANGE) { - addPropertyToContainer(_thing_property_container, property, name, permission, tag).publishOnChange(minDelta, Property::DEFAULT_MIN_TIME_BETWEEN_UPDATES_MILLIS).onUpdate(fn).onSync(synFn); + addPropertyToContainer(getThingPropertyContainer(), property, name, permission, tag).publishOnChange(minDelta, Property::DEFAULT_MIN_TIME_BETWEEN_UPDATES_MILLIS).onUpdate(fn).onSync(synFn); } else { - addPropertyToContainer(_thing_property_container, property, name, permission, tag).publishEvery(seconds).onUpdate(fn).onSync(synFn); + addPropertyToContainer(getThingPropertyContainer(), property, name, permission, tag).publishEvery(seconds).onUpdate(fn).onSync(synFn); } } diff --git a/src/ArduinoIoTCloud.h b/src/ArduinoIoTCloud.h index 7436c0baf..2c5bc4fec 100644 --- a/src/ArduinoIoTCloud.h +++ b/src/ArduinoIoTCloud.h @@ -105,7 +105,7 @@ class ArduinoIoTCloudClass #define addProperty( v, ...) addPropertyReal(v, #v, __VA_ARGS__) - /* The following methods are used for non-LoRa boards which can use the + /* The following methods are used for non-LoRa boards which can use the * name of the property to identify a given property within a CBOR message. */ @@ -146,9 +146,6 @@ class ArduinoIoTCloudClass protected: ConnectionHandler * _connection; - PropertyContainer _device_property_container; - PropertyContainer _thing_property_container; - unsigned int _last_checked_property_index; TimeServiceClass & _time_service; String _thing_id; Property * _thing_id_property; @@ -158,8 +155,9 @@ class ArduinoIoTCloudClass private: - void addPropertyRealInternal(Property& property, String name, int tag, permissionType permission_type = READWRITE, long seconds = ON_CHANGE, void(*fn)(void) = NULL, float minDelta = 0.0f, void(*synFn)(Property & property) = CLOUD_WINS); + virtual PropertyContainer &getThingPropertyContainer() = 0; + void addPropertyRealInternal(Property& property, String name, int tag, permissionType permission_type = READWRITE, long seconds = ON_CHANGE, void(*fn)(void) = NULL, float minDelta = 0.0f, void(*synFn)(Property & property) = CLOUD_WINS); String _device_id; OnCloudEventCallback _cloud_event_callback[3]; }; diff --git a/src/ArduinoIoTCloudLPWAN.cpp b/src/ArduinoIoTCloudLPWAN.cpp index 0f96d36db..4cfc4f96e 100644 --- a/src/ArduinoIoTCloudLPWAN.cpp +++ b/src/ArduinoIoTCloudLPWAN.cpp @@ -51,6 +51,8 @@ ArduinoIoTCloudLPWAN::ArduinoIoTCloudLPWAN() , _retryEnable{false} , _maxNumRetry{5} , _intervalRetry{1000} +, _thing_property_container{0} +, _last_checked_property_index{0} { } diff --git a/src/ArduinoIoTCloudLPWAN.h b/src/ArduinoIoTCloudLPWAN.h index 8d1f42d28..46c9e242d 100644 --- a/src/ArduinoIoTCloudLPWAN.h +++ b/src/ArduinoIoTCloudLPWAN.h @@ -49,6 +49,8 @@ class ArduinoIoTCloudLPWAN : public ArduinoIoTCloudClass inline void setMaxRetry (int val) { _maxNumRetry = val; } inline void setIntervalRetry(long val) { _intervalRetry = val; } + inline PropertyContainer &getThingPropertyContainer() { return _thing_property_container; } + private: @@ -64,6 +66,9 @@ class ArduinoIoTCloudLPWAN : public ArduinoIoTCloudClass int _maxNumRetry; long _intervalRetry; + PropertyContainer _thing_property_container; + unsigned int _last_checked_property_index; + State handle_ConnectPhy(); State handle_SyncTime(); State handle_Connected(); diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index 794be824c..7457eb8d0 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -60,6 +60,9 @@ unsigned long getTime() ArduinoIoTCloudTCP::ArduinoIoTCloudTCP() : _state{State::ConnectPhy} , _connection_attempt(0,0) +, _device_property_container{0} +, _thing_property_container{0} +, _last_checked_property_index{0} , _tz_offset{0} , _tz_offset_property{nullptr} , _tz_dst_until{0} diff --git a/src/ArduinoIoTCloudTCP.h b/src/ArduinoIoTCloudTCP.h index 9b5ffa6c0..bf8396b48 100644 --- a/src/ArduinoIoTCloudTCP.h +++ b/src/ArduinoIoTCloudTCP.h @@ -91,6 +91,8 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass inline String getBrokerAddress() const { return _brokerAddress; } inline uint16_t getBrokerPort () const { return _brokerPort; } + inline PropertyContainer &getThingPropertyContainer() { return _thing_property_container; } + #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. @@ -123,6 +125,9 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass State _state; TimedAttempt _connection_attempt; + PropertyContainer _device_property_container; + PropertyContainer _thing_property_container; + unsigned int _last_checked_property_index; int _tz_offset; Property * _tz_offset_property; From 6bc1aa8e402b7b93fbe5571bd2c048ebb5f13fa7 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Mon, 5 Feb 2024 09:12:28 +0100 Subject: [PATCH 02/48] Add commands definitions --- src/message/Commands.h | 50 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/message/Commands.h diff --git a/src/message/Commands.h b/src/message/Commands.h new file mode 100644 index 000000000..0f9cdac20 --- /dev/null +++ b/src/message/Commands.h @@ -0,0 +1,50 @@ +/* + 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 + +/****************************************************************************** + * TYPEDEF + ******************************************************************************/ + +enum CommandId : uint16_t { + + /* Device commands */ + DeviceBeginCmdId, + ThingBeginCmdId, + ThingUpdateCmdId, + DeviceRegisteredCmdId, + DeviceAttachedCmdId, + DeviceDetachedCmdId, + + /* Thing commands */ + LastValuesBeginCmdId, + LastValuesUpdateCmdId, + PropertiesUpdateCmdId, + + /* Generic commands */ + ResetCmdId, + + /* Unknown command id */ + UnknownCmdId +}; + +struct Command { + CommandId id; +}; + +typedef Command Message; From 220c04e9aba73b25ce03e7d747000bb186a420f0 Mon Sep 17 00:00:00 2001 From: pennam Date: Thu, 7 Mar 2024 12:29:11 +0100 Subject: [PATCH 03/48] Add MessageStream interface --- src/interfaces/MessageStream.h | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/interfaces/MessageStream.h diff --git a/src/interfaces/MessageStream.h b/src/interfaces/MessageStream.h new file mode 100644 index 000000000..ac2b18ac9 --- /dev/null +++ b/src/interfaces/MessageStream.h @@ -0,0 +1,40 @@ +/* + 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 + +using upstreamFunction = std::function; + +/****************************************************************************** + * CLASS DECLARATION + ******************************************************************************/ + +class MessageStream { +public: + MessageStream(upstreamFunction upstream): upstream(upstream) {} + + /** + * Send message upstream + * @param m: message to send + */ + virtual inline void sendUpstream(Message* m) { + upstream(m); + } + +private: + upstreamFunction upstream; +}; From 2294fd76189ad857fa642ebf4b68af2fdfdf2cf7 Mon Sep 17 00:00:00 2001 From: pennam Date: Thu, 7 Mar 2024 12:27:26 +0100 Subject: [PATCH 04/48] Add CloudProcess interface --- src/interfaces/CloudProcess.h | 56 +++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/interfaces/CloudProcess.h diff --git a/src/interfaces/CloudProcess.h b/src/interfaces/CloudProcess.h new file mode 100644 index 000000000..7462e867f --- /dev/null +++ b/src/interfaces/CloudProcess.h @@ -0,0 +1,56 @@ +/* + 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/. +*/ + +#ifndef ARDUINO_IOT_CLOUD_PROCESS +#define ARDUINO_IOT_CLOUD_PROCESS + +/****************************************************************************** + * INCLUDES + ******************************************************************************/ + +#include +#include +#include +#include + +/****************************************************************************** + * CLASS DECLARATION + ******************************************************************************/ + +class CloudProcess { +public: + CloudProcess(MessageStream* stream): stream(stream) {} + + /** + * Abstract method that is called whenever a message comes from Message stream + * @param m: the incoming message + */ + virtual void handleMessage(Message* m) = 0; + + /** + * Abstract method that is called to update the FSM of the CloudProcess + */ + virtual void update() = 0; + +protected: + /** + * Used by a derived class to send a message to the underlying messageStream + * @param msg: the message to send + */ + void deliver(Message* msg) { + assert(stream != nullptr); + stream->sendUpstream(msg); + } + +private: + MessageStream* stream; +}; + +#endif /* ARDUINO_IOT_CLOUD_PROCESS */ From 7d266e8cb10c8127680933ae181acb1169ed172a Mon Sep 17 00:00:00 2001 From: pennam Date: Thu, 11 Apr 2024 10:25:54 +0200 Subject: [PATCH 05/48] Add ArduinoCloudThing --- src/ArduinoIoTCloudThing.cpp | 172 +++++++++++++++++++++++++++++++++++ src/ArduinoIoTCloudThing.h | 69 ++++++++++++++ 2 files changed, 241 insertions(+) create mode 100644 src/ArduinoIoTCloudThing.cpp create mode 100644 src/ArduinoIoTCloudThing.h diff --git a/src/ArduinoIoTCloudThing.cpp b/src/ArduinoIoTCloudThing.cpp new file mode 100644 index 000000000..6ea6dd904 --- /dev/null +++ b/src/ArduinoIoTCloudThing.cpp @@ -0,0 +1,172 @@ +/* + This file is part of the Arduino_SecureElement 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 + +#ifdef HAS_TCP + +#include "ArduinoIoTCloudThing.h" +#include "interfaces/CloudProcess.h" +#include "property/types/CloudWrapperInt.h" +#include "property/types/CloudWrapperUnsignedInt.h" + +/****************************************************************************** + * CTOR/DTOR + ******************************************************************************/ +ArduinoCloudThing::ArduinoCloudThing(MessageStream* ms) +: CloudProcess(ms), +_state{State::Init}, +_syncAttempt(0, 0), +_propertyContainer(0), +_propertyContainerIndex(0), +_utcOffset(0), +_utcOffsetProperty(nullptr), +_utcOffsetExpireTime(0), +_utcOffsetExpireTimeProperty(nullptr) { +} + +/****************************************************************************** + * PUBLIC MEMBER FUNCTIONS + ******************************************************************************/ + +void ArduinoCloudThing::begin() { + Property* property; + + property = new CloudWrapperInt(_utcOffset); + _utcOffsetProperty = &addPropertyToContainer(getPropertyContainer(), + *property, + "tz_offset", + Permission::ReadWrite, -1); + _utcOffsetProperty->writeOnDemand(); + property = new CloudWrapperUnsignedInt(_utcOffsetExpireTime); + _utcOffsetExpireTimeProperty = &addPropertyToContainer(getPropertyContainer(), + *property, + "tz_dst_until", + Permission::ReadWrite, -1); + _utcOffsetExpireTimeProperty->writeOnDemand(); +} + +void ArduinoCloudThing::update() { + /* Run through the state machine. */ + State nextState = _state; + switch (_state) { + case State::Init: nextState = handleInit(); break; + case State::RequestLastValues: nextState = handleRequestLastValues(); break; + case State::Connected: nextState = handleConnected(); break; + case State::Disconnect: nextState = handleDisconnect(); break; + } + + /* Handle external events */ + switch (_command) { + case LastValuesUpdateCmdId: + if (_state == State::RequestLastValues) { + DEBUG_VERBOSE("CloudThing::%s Thing is synced", __FUNCTION__); + nextState = State::Connected; + } + break; + + /* We have received a reset command */ + case ResetCmdId: + nextState = State::Init; + break; + + default: + break; + } + + _command = UnknownCmdId; + _state = nextState; +} + +int ArduinoCloudThing::connected() { + return _state > State::Disconnect ? 1 : 0; +} + +void ArduinoCloudThing::handleMessage(Message* m) { + _command = UnknownCmdId; + if (m != nullptr) { + _command = m->id; + } +} + +ArduinoCloudThing::State ArduinoCloudThing::handleInit() { + _syncAttempt.begin(AIOT_CONFIG_TIMEOUT_FOR_LASTVALUES_SYNC_ms); + return State::RequestLastValues; +} + +ArduinoCloudThing::State ArduinoCloudThing::handleRequestLastValues() { + /* Check whether or not we need to send a new request. */ + if (_syncAttempt.isRetry() && !_syncAttempt.isExpired()) { + return State::RequestLastValues; + } + + /* Track the number of times a get-last-values request was sent to the cloud. + * If no data is received within a certain number of retry-requests it's a + * better strategy to disconnect and re-establish connection from the ground up. + */ + if (_syncAttempt.getRetryCount() > AIOT_CONFIG_LASTVALUES_SYNC_MAX_RETRY_CNT) { + return State::Disconnect; + } + + _syncAttempt.retry(); + + /* Send message upstream to inform infrastructure we need to request thing + * last values + */ + DEBUG_VERBOSE("CloudThing::%s not int sync. %d next sync request in %d ms", + __FUNCTION__, _syncAttempt.getRetryCount(), _syncAttempt.getWaitTime()); + Message message = { LastValuesBeginCmdId }; + deliver(&message); + + return State::RequestLastValues; +} + +ArduinoCloudThing::State ArduinoCloudThing::handleConnected() { + /* Check if a primitive property wrapper is locally changed. + * This function requires an existing time service which in + * turn requires an established connection. Not having that + * leads to a wrong time set in the time service which inhibits + * the connection from being established due to a wrong data + * in the reconstructed certificate. + */ + updateTimestampOnLocallyChangedProperties(getPropertyContainer()); + + /* Configure Time service with timezone data: + * _utcOffset [offset + dst] + * _utcOffsetExpireTime [posix timestamp until _utcOffset is valid] + */ + if (_utcOffsetProperty->isDifferentFromCloud() || + _utcOffsetExpireTimeProperty->isDifferentFromCloud()) { + _utcOffsetProperty->fromCloudToLocal(); + _utcOffsetExpireTimeProperty->fromCloudToLocal(); + TimeService.setTimeZoneData(_utcOffset, _utcOffsetExpireTime); + } + + /* Check if any property needs encoding and send them to the cloud */ + Message message = { PropertiesUpdateCmdId }; + deliver(&message); + + if (getTime() > _utcOffsetExpireTime) { + return State::RequestLastValues; + } + + return State::Connected; +} + +ArduinoCloudThing::State ArduinoCloudThing::handleDisconnect() { + return State::Disconnect; +} + +#endif /* HAS_TCP */ diff --git a/src/ArduinoIoTCloudThing.h b/src/ArduinoIoTCloudThing.h new file mode 100644 index 000000000..de52bc002 --- /dev/null +++ b/src/ArduinoIoTCloudThing.h @@ -0,0 +1,69 @@ +/* + This file is part of the Arduino_SecureElement 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/. +*/ + + +#ifndef ARDUINO_IOT_CLOUD_THING_H +#define ARDUINO_IOT_CLOUD_THING_H + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include "interfaces/CloudProcess.h" +#include "utility/time/TimedAttempt.h" +#include "property/PropertyContainer.h" + +/****************************************************************************** + * CLASS DECLARATION + ******************************************************************************/ + +class ArduinoCloudThing : public CloudProcess { +public: + + ArduinoCloudThing(MessageStream *stream); + virtual void update() override; + virtual void handleMessage(Message *m) override; + + virtual void begin(); + virtual int connected(); + + inline PropertyContainer &getPropertyContainer() { + return _propertyContainer; + }; + inline unsigned int &getPropertyContainerIndex() { + return _propertyContainerIndex; + } + +private: + + enum class State { + Disconnect, + Init, + RequestLastValues, + Connected, + }; + + State _state; + CommandId _command; + TimedAttempt _syncAttempt; + PropertyContainer _propertyContainer; + unsigned int _propertyContainerIndex; + int _utcOffset; + Property *_utcOffsetProperty; + unsigned int _utcOffsetExpireTime; + Property *_utcOffsetExpireTimeProperty; + + State handleInit(); + State handleRequestLastValues(); + State handleConnected(); + State handleDisconnect(); +}; + +#endif /* ARDUINO_IOT_CLOUD_THING_H */ From ef6b3a6208c6cab1d4750d7275e1a68f90dc12e8 Mon Sep 17 00:00:00 2001 From: pennam Date: Wed, 28 Feb 2024 11:34:00 +0100 Subject: [PATCH 06/48] TimeService: make isTimeValid public --- src/utility/time/TimeService.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utility/time/TimeService.h b/src/utility/time/TimeService.h index 6e6af6bb5..794ec344d 100644 --- a/src/utility/time/TimeService.h +++ b/src/utility/time/TimeService.h @@ -56,6 +56,8 @@ class TimeServiceClass */ static unsigned long getTimeFromString(const String& input); + static bool isTimeValid(unsigned long const time); + private: ConnectionHandler * _con_hdl; @@ -74,7 +76,6 @@ class TimeServiceClass void initRTC(); void setRTC(unsigned long time); unsigned long getRTC(); - static bool isTimeValid(unsigned long const time); static bool isTimeZoneOffsetValid(long const offset); }; From 20831f5328e40a70d3dff2735c1b4beeb312081d Mon Sep 17 00:00:00 2001 From: pennam Date: Wed, 28 Feb 2024 12:23:03 +0100 Subject: [PATCH 07/48] ArduinoIoTCloudTCP: Use isTimeValid() to check NTP time --- src/ArduinoIoTCloudTCP.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index 7457eb8d0..fead2f602 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -328,12 +328,13 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_ConnectPhy() ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_SyncTime() { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-variable" - unsigned long const internal_posix_time = _time_service.getTime(); -#pragma GCC diagnostic pop - DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s internal clock configured to posix timestamp %d", __FUNCTION__, internal_posix_time); - return State::ConnectMqttBroker; + if (TimeServiceClass::isTimeValid(getTime())) + { + DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s internal clock configured to posix timestamp %d", __FUNCTION__, getTime()); + return State::ConnectMqttBroker; + } + + return State::ConnectPhy; } ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_ConnectMqttBroker() From 0d69bbb05d14b29276f1b06b366ce427eecf7ccb Mon Sep 17 00:00:00 2001 From: pennam Date: Thu, 11 Apr 2024 10:26:07 +0200 Subject: [PATCH 08/48] ArduinoIoTCloudTCP: adapt state machine to use Thing process --- src/ArduinoIoTCloud.cpp | 1 - src/ArduinoIoTCloud.h | 1 - src/ArduinoIoTCloudTCP.cpp | 122 +++++++++++++------------------------ src/ArduinoIoTCloudTCP.h | 18 ++---- 4 files changed, 50 insertions(+), 92 deletions(-) diff --git a/src/ArduinoIoTCloud.cpp b/src/ArduinoIoTCloud.cpp index ec2d48267..0603b6759 100644 --- a/src/ArduinoIoTCloud.cpp +++ b/src/ArduinoIoTCloud.cpp @@ -29,7 +29,6 @@ ArduinoIoTCloudClass::ArduinoIoTCloudClass() : _connection{nullptr} , _time_service(TimeService) , _thing_id{""} -, _thing_id_property{nullptr} , _lib_version{AIOT_CONFIG_LIB_VERSION} , _device_id{""} , _cloud_event_callback{nullptr} diff --git a/src/ArduinoIoTCloud.h b/src/ArduinoIoTCloud.h index 2c5bc4fec..7c0bcc5f9 100644 --- a/src/ArduinoIoTCloud.h +++ b/src/ArduinoIoTCloud.h @@ -148,7 +148,6 @@ class ArduinoIoTCloudClass ConnectionHandler * _connection; TimeServiceClass & _time_service; String _thing_id; - Property * _thing_id_property; String _lib_version; void execCloudEventCallback(ArduinoIoTCloudEvent const event); diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index fead2f602..e06e218b4 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -60,13 +60,10 @@ unsigned long getTime() ArduinoIoTCloudTCP::ArduinoIoTCloudTCP() : _state{State::ConnectPhy} , _connection_attempt(0,0) +, _message_stream(std::bind(&ArduinoIoTCloudTCP::sendMessage, this, std::placeholders::_1)) +, _thing(&_message_stream) +, _thing_id_property{nullptr} , _device_property_container{0} -, _thing_property_container{0} -, _last_checked_property_index{0} -, _tz_offset{0} -, _tz_offset_property{nullptr} -, _tz_dst_until{0} -, _tz_dst_until_property{nullptr} , _mqtt_data_buf{0} , _mqtt_data_len{0} , _mqtt_data_request_retransmit{false} @@ -214,10 +211,8 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, #endif /* OTA_ENABLED */ p = new CloudWrapperString(_thing_id); _thing_id_property = &addPropertyToContainer(_device_property_container, *p, "thing_id", Permission::ReadWrite, -1).writeOnDemand(); - p = new CloudWrapperInt(_tz_offset); - _tz_offset_property = &addPropertyToContainer(_thing_property_container, *p, "tz_offset", Permission::ReadWrite, -1).writeOnDemand(); - p = new CloudWrapperUnsignedInt(_tz_dst_until); - _tz_dst_until_property = &addPropertyToContainer(_thing_property_container, *p, "tz_dst_until", Permission::ReadWrite, -1).writeOnDemand(); + + _thing.begin(); #if OTA_ENABLED _ota_cap = OTA::isCapable(); @@ -274,7 +269,6 @@ void ArduinoIoTCloudTCP::update() case State::SubscribeDeviceTopic: next_state = handle_SubscribeDeviceTopic(); break; case State::CheckDeviceConfig: next_state = handle_CheckDeviceConfig(); break; case State::SubscribeThingTopics: next_state = handle_SubscribeThingTopics(); break; - case State::RequestLastValues: next_state = handle_RequestLastValues(); break; case State::Connected: next_state = handle_Connected(); break; case State::Disconnect: next_state = handle_Disconnect(); break; } @@ -478,38 +472,12 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_SubscribeThingTopics() /* Successfully subscribed to thing topics, reconfigure timers for next state and go on */ _connection_attempt.begin(AIOT_CONFIG_TIMEOUT_FOR_LASTVALUES_SYNC_ms); - return State::RequestLastValues; -} - -ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_RequestLastValues() -{ - if (!_mqttClient.connected() || _thing_id_property->isDifferentFromCloud()) - { - return State::Disconnect; - } - - /* Check whether or not we need to send a new request. */ - if (_connection_attempt.isRetry() && !_connection_attempt.isExpired()) - return State::RequestLastValues; - - /* Track the number of times a get-last-values request was sent to the cloud. - * If no data is received within a certain number of retry-requests it's a better - * strategy to disconnect and re-establish connection from the ground up. - */ - if (_connection_attempt.getRetryCount() > AIOT_CONFIG_LASTVALUES_SYNC_MAX_RETRY_CNT) - { - return State::Disconnect; - } - - _connection_attempt.retry(); - requestLastValue(); - DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s [%d] last values requested", __FUNCTION__, _time_service.getTime()); - return State::RequestLastValues; + return State::Connected; } ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Connected() { - if (!_mqttClient.connected()) + if (!_mqttClient.connected() || _thing_id_property->isDifferentFromCloud() || !_thing.connected()) { /* The last message was definitely lost, trigger a retransmit. */ _mqtt_data_request_retransmit = true; @@ -518,20 +486,6 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Connected() /* We are connected so let's to our stuff here. */ else { - if (_thing_id_property->isDifferentFromCloud()) - { - return State::Disconnect; - } - - /* Check if a primitive property wrapper is locally changed. - * This function requires an existing time service which in - * turn requires an established connection. Not having that - * leads to a wrong time set in the time service which inhibits - * the connection from being established due to a wrong data - * in the reconstructed certificate. - */ - updateTimestampOnLocallyChangedProperties(_thing_property_container); - /* Retransmit data in case there was a lost transaction due * to phy layer or MQTT connectivity loss. */ @@ -540,27 +494,11 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Connected() _mqtt_data_request_retransmit = false; } - /* Configure Time service with timezone data: - * _tz_offset [offset + dst] - * _tz_dst_until [posix timestamp until _tz_offset is valid] - */ - if (_tz_offset_property->isDifferentFromCloud() || _tz_dst_until_property->isDifferentFromCloud()) { - _tz_offset_property->fromCloudToLocal(); - _tz_dst_until_property->fromCloudToLocal(); - _time_service.setTimeZoneData(_tz_offset, _tz_dst_until); - } + /* Call CloudThing process to synchronize properties */ + _thing.update(); - /* Check if any properties need encoding and send them to - * the cloud if necessary. - */ - sendThingPropertiesToCloud(); + return State::Connected; - unsigned long const internal_posix_time = _time_service.getTime(); - if (internal_posix_time < _tz_dst_until) { - return State::Connected; - } else { - return State::RequestLastValues; - } } } @@ -605,9 +543,15 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Disconnect() /* TODO add device topic */ _mqttClient.stop(); } + + Message message = { ResetCmdId }; + _thing.handleMessage(&message); + DEBUG_INFO("Disconnected from Arduino IoT Cloud"); execCloudEventCallback(ArduinoIoTCloudEvent::DISCONNECT); + updateThingTopics(); + /* Setup timer for broker connection and restart */ _connection_attempt.begin(AIOT_CONFIG_RECONNECTION_RETRY_DELAY_ms, AIOT_CONFIG_MAX_RECONNECTION_RETRY_DELAY_ms); return State::ConnectPhy; @@ -631,25 +575,47 @@ void ArduinoIoTCloudTCP::handleMessage(int length) /* Topic for OTA properties and device configuration */ if (_deviceTopicIn == topic) { CBORDecoder::decode(_device_property_container, (uint8_t*)bytes, length); - _state = State::CheckDeviceConfig; + if (_thing_id_property->isDifferentFromCloud() && (_thing_id.length() != 0)) { + _state = State::Disconnect; + } else { + _state = State::CheckDeviceConfig; + } } /* Topic for user input data */ if (_dataTopicIn == topic) { - CBORDecoder::decode(_thing_property_container, (uint8_t*)bytes, length); + CBORDecoder::decode(_thing.getPropertyContainer(), (uint8_t*)bytes, length); } /* Topic for sync Thing last values on connect */ - if ((_shadowTopicIn == topic) && (_state == State::RequestLastValues)) + if (_shadowTopicIn == topic) { DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s [%d] last values received", __FUNCTION__, millis()); - CBORDecoder::decode(_thing_property_container, (uint8_t*)bytes, length, true); - _time_service.setTimeZoneData(_tz_offset, _tz_dst_until); + CBORDecoder::decode(_thing.getPropertyContainer(), (uint8_t*)bytes, length, true); + Message message = { LastValuesUpdateCmdId }; + _thing.handleMessage(&message); execCloudEventCallback(ArduinoIoTCloudEvent::SYNC); _state = State::Connected; } } +void ArduinoIoTCloudTCP::sendMessage(Message * msg) +{ + switch (msg->id) + { + case PropertiesUpdateCmdId: + sendThingPropertiesToCloud(); + break; + + case LastValuesBeginCmdId: + requestLastValue(); + break; + + default: + break; + } +} + void ArduinoIoTCloudTCP::sendPropertyContainerToCloud(String const topic, PropertyContainer & property_container, unsigned int & current_property_index) { int bytes_encoded = 0; @@ -670,7 +636,7 @@ void ArduinoIoTCloudTCP::sendPropertyContainerToCloud(String const topic, Proper void ArduinoIoTCloudTCP::sendThingPropertiesToCloud() { - sendPropertyContainerToCloud(_dataTopicOut, _thing_property_container, _last_checked_property_index); + sendPropertyContainerToCloud(_dataTopicOut, _thing.getPropertyContainer(), _thing.getPropertyContainerIndex()); } void ArduinoIoTCloudTCP::sendDevicePropertiesToCloud() diff --git a/src/ArduinoIoTCloudTCP.h b/src/ArduinoIoTCloudTCP.h index bf8396b48..3d23e56a1 100644 --- a/src/ArduinoIoTCloudTCP.h +++ b/src/ArduinoIoTCloudTCP.h @@ -25,7 +25,7 @@ #include #include #include -#include +#include #if defined(BOARD_HAS_SECURE_ELEMENT) #include @@ -75,7 +75,6 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass ArduinoIoTCloudTCP(); virtual ~ArduinoIoTCloudTCP() { } - virtual void update () override; virtual int connected () override; virtual void printDebugInfo() override; @@ -91,7 +90,7 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass inline String getBrokerAddress() const { return _brokerAddress; } inline uint16_t getBrokerPort () const { return _brokerPort; } - inline PropertyContainer &getThingPropertyContainer() { return _thing_property_container; } + inline PropertyContainer &getThingPropertyContainer() { return _thing.getPropertyContainer(); } #if OTA_ENABLED /* The callback is triggered when the OTA is initiated and it gets executed until _ota_req flag is cleared. @@ -118,21 +117,16 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass SubscribeDeviceTopic, CheckDeviceConfig, SubscribeThingTopics, - RequestLastValues, Connected, Disconnect, }; State _state; TimedAttempt _connection_attempt; + MessageStream _message_stream; + ArduinoCloudThing _thing; + Property * _thing_id_property; PropertyContainer _device_property_container; - PropertyContainer _thing_property_container; - unsigned int _last_checked_property_index; - - int _tz_offset; - Property * _tz_offset_property; - unsigned int _tz_dst_until; - Property * _tz_dst_until_property; String _brokerAddress; uint16_t _brokerPort; @@ -200,12 +194,12 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass State handle_CheckDeviceConfig(); State handle_SubscribeDeviceTopic(); State handle_SubscribeThingTopics(); - State handle_RequestLastValues(); State handle_Connected(); State handle_Disconnect(); static void onMessage(int length); void handleMessage(int length); + void sendMessage(Message * msg); void sendPropertyContainerToCloud(String const topic, PropertyContainer & property_container, unsigned int & current_property_index); void sendThingPropertiesToCloud(); void sendDevicePropertiesToCloud(); From af140542e15871fe5bcc52c66e1cbb72d20a7dc8 Mon Sep 17 00:00:00 2001 From: pennam Date: Thu, 11 Apr 2024 17:27:55 +0200 Subject: [PATCH 09/48] Add ArduinoCloudDevice --- src/ArduinoIoTCloudDevice.cpp | 144 ++++++++++++++++++++++++++++++++++ src/ArduinoIoTCloudDevice.h | 70 +++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 src/ArduinoIoTCloudDevice.cpp create mode 100644 src/ArduinoIoTCloudDevice.h diff --git a/src/ArduinoIoTCloudDevice.cpp b/src/ArduinoIoTCloudDevice.cpp new file mode 100644 index 000000000..f72b65588 --- /dev/null +++ b/src/ArduinoIoTCloudDevice.cpp @@ -0,0 +1,144 @@ +/* + 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 + +#ifdef HAS_TCP + +#include "ArduinoIoTCloudDevice.h" +#include "interfaces/CloudProcess.h" + +/****************************************************************************** + CTOR/DTOR + ******************************************************************************/ +ArduinoCloudDevice::ArduinoCloudDevice(MessageStream *ms) +: CloudProcess(ms), +_state{State::Init}, +_attachAttempt(0, 0), +_attached(false), +_registered(false) { +} + +void ArduinoCloudDevice::begin() { + _attachAttempt.begin(AIOT_CONFIG_DEVICE_TOPIC_SUBSCRIBE_RETRY_DELAY_ms, + AIOT_CONFIG_MAX_DEVICE_TOPIC_SUBSCRIBE_RETRY_DELAY_ms); +} + +void ArduinoCloudDevice::update() { + /* Run through the state machine. */ + State nextState = _state; + switch (_state) { + case State::Init: nextState = handleInit(); break; + case State::SendCapabilities: nextState = handleSendCapabilities(); break; + case State::Connected: nextState = handleConnected(); break; + case State::Disconnected: nextState = handleDisconnected(); break; + } + + /* Handle external events */ + switch (_command) { + case DeviceAttachedCmdId: + _attached = true; + _registered = true; + DEBUG_VERBOSE("CloudDevice::%s Device is attached", __FUNCTION__); + nextState = State::Connected; + break; + + case DeviceDetachedCmdId: + _attached = false; + _registered = false; + nextState = State::Init; + break; + + case DeviceRegisteredCmdId: + _registered = true; + nextState = State::Connected; + break; + + /* We have received a reset command */ + case ResetCmdId: + nextState = State::Init; + break; + + default: + break; + } + + _command = UnknownCmdId; + _state = nextState; +} + +int ArduinoCloudDevice::connected() { + return _state != State::Disconnected ? 1 : 0; +} + +void ArduinoCloudDevice::handleMessage(Message *m) { + _command = UnknownCmdId; + if (m != nullptr) { + _command = m->id; + } +} + +ArduinoCloudDevice::State ArduinoCloudDevice::handleInit() { + /* Reset attempt struct for the nex retry after disconnection */ + _attachAttempt.begin(AIOT_CONFIG_DEVICE_TOPIC_SUBSCRIBE_RETRY_DELAY_ms, + AIOT_CONFIG_MAX_DEVICE_TOPIC_SUBSCRIBE_RETRY_DELAY_ms); + + _attached = false; + _registered = false; + + return State::SendCapabilities; +} + +ArduinoCloudDevice::State ArduinoCloudDevice::handleSendCapabilities() { + /* Sends device capabilities message */ + Message message = { DeviceBeginCmdId }; + deliver(&message); + + /* Subscribe to device topic to request */ + message = { ThingBeginCmdId }; + deliver(&message); + + /* No device configuration received. Wait: 4s -> 8s -> 16s -> 32s -> 32s ...*/ + _attachAttempt.retry(); + DEBUG_VERBOSE("CloudDevice::%s not attached. %d next configuration request in %d ms", + __FUNCTION__, _attachAttempt.getRetryCount(), _attachAttempt.getWaitTime()); + return State::Connected; +} + +ArduinoCloudDevice::State ArduinoCloudDevice::handleConnected() { + /* Max retry than disconnect */ + if (_attachAttempt.getRetryCount() > AIOT_CONFIG_DEVICE_TOPIC_MAX_RETRY_CNT) { + return State::Disconnected; + } + + if (!_attached && _attachAttempt.isExpired()) { + if (_registered) { + /* Device configuration received, but invalid thing_id. Do not increase + * counter, but recompute delay. + * Wait: 4s -> 80s -> 160s -> 320s -> 640s -> 1280s -> 1280s ... + */ + _attachAttempt.reconfigure(AIOT_CONFIG_DEVICE_TOPIC_ATTACH_RETRY_DELAY_ms, + AIOT_CONFIG_MAX_DEVICE_TOPIC_ATTACH_RETRY_DELAY_ms); + } + return State::SendCapabilities; + } + + return State::Connected; +} + +ArduinoCloudDevice::State ArduinoCloudDevice::handleDisconnected() { + return State::Disconnected; +} + +#endif /* HAS_TCP */ diff --git a/src/ArduinoIoTCloudDevice.h b/src/ArduinoIoTCloudDevice.h new file mode 100644 index 000000000..294acfde5 --- /dev/null +++ b/src/ArduinoIoTCloudDevice.h @@ -0,0 +1,70 @@ +/* + This file is part of the Arduino_SecureElement 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/. +*/ + +#ifndef ARDUINO_IOT_CLOUD_DEVICE_H +#define ARDUINO_IOT_CLOUD_DEVICE_H + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include "interfaces/CloudProcess.h" +#include "utility/time/TimedAttempt.h" +#include "property/PropertyContainer.h" + +/****************************************************************************** + * CLASS DECLARATION + ******************************************************************************/ + +class ArduinoCloudDevice : public CloudProcess { +public: + + ArduinoCloudDevice(MessageStream* stream); + virtual void update() override; + virtual void handleMessage(Message* m) override; + + virtual void begin(); + virtual int connected(); + + inline PropertyContainer &getPropertyContainer() { + return _propertyContainer; + }; + inline unsigned int &getPropertyContainerIndex() { + return _propertyContainerIndex; + } + inline bool isAttached() { + return _attached; + }; + + +private: + + enum class State { + Disconnected, + Init, + SendCapabilities, + Connected, + }; + + State _state; + CommandId _command; + TimedAttempt _attachAttempt; + PropertyContainer _propertyContainer; + unsigned int _propertyContainerIndex; + bool _attached; + bool _registered; + + State handleInit(); + State handleSendCapabilities(); + State handleConnected(); + State handleDisconnected(); +}; + +#endif /* ARDUINO_IOT_CLOUD_DEVICE_H */ From 8420fdebccbfb1a556faac63cfbbab851095f748 Mon Sep 17 00:00:00 2001 From: pennam Date: Tue, 16 Apr 2024 11:34:01 +0200 Subject: [PATCH 10/48] ArduinoIoTCloudTCP: adapt state machine to use Device process --- src/ArduinoIoTCloudTCP.cpp | 328 +++++++++++++++---------------------- src/ArduinoIoTCloudTCP.h | 22 ++- 2 files changed, 140 insertions(+), 210 deletions(-) diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index e06e218b4..e9ccd4a94 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -63,7 +63,7 @@ ArduinoIoTCloudTCP::ArduinoIoTCloudTCP() , _message_stream(std::bind(&ArduinoIoTCloudTCP::sendMessage, this, std::placeholders::_1)) , _thing(&_message_stream) , _thing_id_property{nullptr} -, _device_property_container{0} +, _device(&_message_stream) , _mqtt_data_buf{0} , _mqtt_data_len{0} , _mqtt_data_request_retransmit{false} @@ -120,11 +120,6 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, _brokerAddress = brokerAddress; _brokerPort = brokerPort; -#if OTA_ENABLED - _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 defined(BOARD_HAS_SECRET_KEY) /* If board is not configured for username and password login */ if(!_password.length()) @@ -196,27 +191,30 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, Property* p; p = new CloudWrapperString(_lib_version); - addPropertyToContainer(_device_property_container, *p, "LIB_VERSION", Permission::Read, -1); -#if OTA_ENABLED + addPropertyToContainer(_device.getPropertyContainer(), *p, "LIB_VERSION", Permission::Read, -1); + p = new CloudWrapperString(_thing_id); + _thing_id_property = &addPropertyToContainer(_device.getPropertyContainer(), *p, "thing_id", Permission::ReadWrite, -1).writeOnDemand(); + + _thing.begin(); + _device.begin(); + +#if OTA_ENABLED p = new CloudWrapperBool(_ota_cap); - addPropertyToContainer(_device_property_container, *p, "OTA_CAP", Permission::Read, -1); + addPropertyToContainer(_device.getPropertyContainer(), *p, "OTA_CAP", Permission::Read, -1); p = new CloudWrapperInt(_ota_error); - addPropertyToContainer(_device_property_container, *p, "OTA_ERROR", Permission::Read, -1); + addPropertyToContainer(_device.getPropertyContainer(), *p, "OTA_ERROR", Permission::Read, -1); p = new CloudWrapperString(_ota_img_sha256); - addPropertyToContainer(_device_property_container, *p, "OTA_SHA256", Permission::Read, -1); + addPropertyToContainer(_device.getPropertyContainer(), *p, "OTA_SHA256", Permission::Read, -1); p = new CloudWrapperString(_ota_url); - addPropertyToContainer(_device_property_container, *p, "OTA_URL", Permission::ReadWrite, -1); + addPropertyToContainer(_device.getPropertyContainer(), *p, "OTA_URL", Permission::ReadWrite, -1); p = new CloudWrapperBool(_ota_req); - addPropertyToContainer(_device_property_container, *p, "OTA_REQ", Permission::ReadWrite, -1); -#endif /* OTA_ENABLED */ - p = new CloudWrapperString(_thing_id); - _thing_id_property = &addPropertyToContainer(_device_property_container, *p, "thing_id", Permission::ReadWrite, -1).writeOnDemand(); - - _thing.begin(); + addPropertyToContainer(_device.getPropertyContainer(), *p, "OTA_REQ", Permission::ReadWrite, -1); -#if OTA_ENABLED _ota_cap = OTA::isCapable(); -#endif + + _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 #ifdef BOARD_HAS_OFFLOADED_ECCX08 if (String(WiFi.firmwareVersion()) < String("1.4.4")) { @@ -257,7 +255,6 @@ void ArduinoIoTCloudTCP::update() watchdog_reset(); #endif - /* Run through the state machine. */ State next_state = _state; switch (_state) @@ -265,23 +262,13 @@ void ArduinoIoTCloudTCP::update() case State::ConnectPhy: next_state = handle_ConnectPhy(); break; case State::SyncTime: next_state = handle_SyncTime(); break; case State::ConnectMqttBroker: next_state = handle_ConnectMqttBroker(); break; - case State::SendDeviceProperties: next_state = handle_SendDeviceProperties(); break; - case State::SubscribeDeviceTopic: next_state = handle_SubscribeDeviceTopic(); break; - case State::CheckDeviceConfig: next_state = handle_CheckDeviceConfig(); break; - case State::SubscribeThingTopics: next_state = handle_SubscribeThingTopics(); break; case State::Connected: next_state = handle_Connected(); break; case State::Disconnect: next_state = handle_Disconnect(); break; } _state = next_state; -#if OTA_ENABLED - if (_state > State::SubscribeDeviceTopic && _state <= State::Connected) { - handle_OTARequest(); - } -#endif /* OTA_ENABLED */ - /* This watchdog feed is actually needed only by the RP2040 Connect because its - * maximum watchdog window is 8389 ms; despite this we feed it for all + * maximum watchdog window is 8389 ms; despite this we feed it for all * supported ARCH to keep code aligned. */ #if defined (ARDUINO_ARCH_SAMD) || defined (ARDUINO_ARCH_MBED) @@ -338,7 +325,7 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_ConnectMqttBroker() DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s connected to %s:%d", __FUNCTION__, _brokerAddress.c_str(), _brokerPort); /* 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); - return State::SendDeviceProperties; + return State::Connected; } /* Can't connect to the broker. Wait: 2s -> 4s -> 8s -> 16s -> 32s -> 32s ... */ @@ -350,158 +337,38 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_ConnectMqttBroker() return State::ConnectPhy; } -ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_SendDeviceProperties() -{ - if (!_mqttClient.connected()) - { - return State::Disconnect; - } - - DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s announce device to the Cloud %d", __FUNCTION__, _time_service.getTime()); - /* TODO check if write fails */ - sendDevicePropertiesToCloud(); - return State::SubscribeDeviceTopic; -} - -ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_SubscribeDeviceTopic() -{ - if (!_mqttClient.connected()) - { - return State::Disconnect; - } - - if (_connection_attempt.isRetry() && !_connection_attempt.isExpired()) - return State::SubscribeDeviceTopic; - - if (_connection_attempt.isRetry()) - { - /* Configuration not received or device not attached to a valid thing. Try to resubscribe */ - DEBUG_ERROR("ArduinoIoTCloudTCP::%s device waiting for valid thing_id %d", __FUNCTION__, _time_service.getTime()); - } - - DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s request device configuration %d", __FUNCTION__, _time_service.getTime()); - - if (!_mqttClient.subscribe(_deviceTopicIn)) - { - /* If device_id is wrong the board can't connect to the broker so this condition - * should never happen. - */ - DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not subscribe to %s", __FUNCTION__, _deviceTopicIn.c_str()); - } - - /* Max retry than disconnect */ - if (_connection_attempt.getRetryCount() > AIOT_CONFIG_DEVICE_TOPIC_MAX_RETRY_CNT) - { - return State::Disconnect; - } - - /* No device configuration received. Wait: 4s -> 8s -> 16s -> 32s -> 32s ...*/ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-variable" - unsigned long const subscribe_retry_delay = _connection_attempt.retry(); -#pragma GCC diagnostic pop - DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s %d next configuration request in %d ms", __FUNCTION__, _connection_attempt.getRetryCount(), subscribe_retry_delay); - - return State::SubscribeDeviceTopic; -} - -ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_CheckDeviceConfig() -{ - if (!_mqttClient.connected()) - { - return State::Disconnect; - } - - updateThingTopics(); - - if (_thing_id.length() == 0) - { - /* Device configuration received, but invalid thing_id. Do not increase counter, but recompute delay. - * Device not attached. Wait: 40s -> 80s -> 160s -> 320s -> 640s -> 1280s -> 1280s ... - */ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-variable" - unsigned long const attach_retry_delay = _connection_attempt.reconfigure(AIOT_CONFIG_DEVICE_TOPIC_ATTACH_RETRY_DELAY_ms, AIOT_CONFIG_MAX_DEVICE_TOPIC_ATTACH_RETRY_DELAY_ms); -#pragma GCC diagnostic pop - DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s device not attached, next configuration request in %d ms", __FUNCTION__, attach_retry_delay); - return State::SubscribeDeviceTopic; - } - - DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s device attached to a new valid thing_id %s %d", __FUNCTION__, getThingId().c_str(), _time_service.getTime()); - - /* Received valid thing_id, reconfigure timers for next state and go on */ - _connection_attempt.begin(AIOT_CONFIG_THING_TOPICS_SUBSCRIBE_RETRY_DELAY_ms); - - return State::SubscribeThingTopics; -} - -ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_SubscribeThingTopics() +ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Connected() { - if (!_mqttClient.connected() || _thing_id_property->isDifferentFromCloud()) + if (!_mqttClient.connected() || !_thing.connected() || !_device.connected()) { return State::Disconnect; } - if (_connection_attempt.isRetry() && !_connection_attempt.isExpired()) - return State::SubscribeThingTopics; - - if (_connection_attempt.getRetryCount() > AIOT_CONFIG_THING_TOPICS_SUBSCRIBE_MAX_RETRY_CNT) - { - return State::Disconnect; + /* Retransmit data in case there was a lost transaction due + * to phy layer or MQTT connectivity loss. + */ + if (_mqtt_data_request_retransmit && (_mqtt_data_len > 0)) { + write(_dataTopicOut, _mqtt_data_buf, _mqtt_data_len); + _mqtt_data_request_retransmit = false; } - _connection_attempt.retry(); + /* Call CloudDevice process to get configuration */ + _device.update(); - if (!_mqttClient.subscribe(_dataTopicIn)) - { - DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not subscribe to %s", __FUNCTION__, _dataTopicIn.c_str()); - DEBUG_ERROR("Check your thing configuration, and press the reset button on your board."); - return State::SubscribeThingTopics; + if (_device.isAttached()) { + /* Call CloudThing process to synchronize properties */ + _thing.update(); } - if (!_mqttClient.subscribe(_shadowTopicIn)) - { - DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not subscribe to %s", __FUNCTION__, _shadowTopicIn.c_str()); - DEBUG_ERROR("Check your thing configuration, and press the reset button on your board."); - return State::SubscribeThingTopics; +#if OTA_ENABLED + if (_device.connected()) { + handle_OTARequest(); } +#endif /* OTA_ENABLED */ - DEBUG_INFO("Connected to Arduino IoT Cloud"); - DEBUG_INFO("Thing ID: %s", getThingId().c_str()); - execCloudEventCallback(ArduinoIoTCloudEvent::CONNECT); - - /* Successfully subscribed to thing topics, reconfigure timers for next state and go on */ - _connection_attempt.begin(AIOT_CONFIG_TIMEOUT_FOR_LASTVALUES_SYNC_ms); return State::Connected; } -ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Connected() -{ - if (!_mqttClient.connected() || _thing_id_property->isDifferentFromCloud() || !_thing.connected()) - { - /* The last message was definitely lost, trigger a retransmit. */ - _mqtt_data_request_retransmit = true; - return State::Disconnect; - } - /* We are connected so let's to our stuff here. */ - else - { - /* Retransmit data in case there was a lost transaction due - * to phy layer or MQTT connectivity loss. - */ - if (_mqtt_data_request_retransmit && (_mqtt_data_len > 0)) { - write(_dataTopicOut, _mqtt_data_buf, _mqtt_data_len); - _mqtt_data_request_retransmit = false; - } - - /* Call CloudThing process to synchronize properties */ - _thing.update(); - - return State::Connected; - - } -} - #if OTA_ENABLED void ArduinoIoTCloudTCP::handle_OTARequest() { /* Request a OTA download if the hidden property @@ -538,20 +405,17 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Disconnect() if (!_mqttClient.connected()) { DEBUG_ERROR("ArduinoIoTCloudTCP::%s MQTT client connection lost", __FUNCTION__); } else { - _mqttClient.unsubscribe(_shadowTopicIn); - _mqttClient.unsubscribe(_dataTopicIn); - /* TODO add device topic */ + /* No need to manually unsubscribe because we are using clean sessions */ _mqttClient.stop(); } Message message = { ResetCmdId }; _thing.handleMessage(&message); + _device.handleMessage(&message); DEBUG_INFO("Disconnected from Arduino IoT Cloud"); execCloudEventCallback(ArduinoIoTCloudEvent::DISCONNECT); - updateThingTopics(); - /* Setup timer for broker connection and restart */ _connection_attempt.begin(AIOT_CONFIG_RECONNECTION_RETRY_DELAY_ms, AIOT_CONFIG_MAX_RECONNECTION_RETRY_DELAY_ms); return State::ConnectPhy; @@ -574,11 +438,28 @@ void ArduinoIoTCloudTCP::handleMessage(int length) /* Topic for OTA properties and device configuration */ if (_deviceTopicIn == topic) { - CBORDecoder::decode(_device_property_container, (uint8_t*)bytes, length); - if (_thing_id_property->isDifferentFromCloud() && (_thing_id.length() != 0)) { - _state = State::Disconnect; - } else { - _state = State::CheckDeviceConfig; + CBORDecoder::decode(_device.getPropertyContainer(), (uint8_t*)bytes, length); + + /* Temporary check to avoid flooding device state machine with usless messages */ + if (_thing_id_property->isDifferentFromCloud()) { + _thing_id_property->fromCloudToLocal(); + + Message message; + /* If we are attached we need first to detach */ + if (_device.isAttached()) { + detachThing(); + message = { DeviceDetachedCmdId }; + } + /* If received thing id is valid attach to the new thing */ + if (_thing_id.length()) { + attachThing(); + message = { DeviceAttachedCmdId }; + } else { + /* Send message to device state machine to inform we have received a null thing-id */ + _thing_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; + message = { DeviceRegisteredCmdId }; + } + _device.handleMessage(&message); } } @@ -588,14 +469,15 @@ void ArduinoIoTCloudTCP::handleMessage(int length) } /* Topic for sync Thing last values on connect */ - if (_shadowTopicIn == topic) - { + if (_shadowTopicIn == topic) { DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s [%d] last values received", __FUNCTION__, millis()); + /* Decode last values property array */ CBORDecoder::decode(_thing.getPropertyContainer(), (uint8_t*)bytes, length, true); + /* Unlock thing state machine waiting last values */ Message message = { LastValuesUpdateCmdId }; _thing.handleMessage(&message); + /* Call ArduinoIoTCloud sync user callback*/ execCloudEventCallback(ArduinoIoTCloudEvent::SYNC); - _state = State::Connected; } } @@ -603,14 +485,22 @@ void ArduinoIoTCloudTCP::sendMessage(Message * msg) { switch (msg->id) { - case PropertiesUpdateCmdId: - sendThingPropertiesToCloud(); + case DeviceBeginCmdId: + sendDevicePropertiesToCloud(); + break; + + case ThingBeginCmdId: + requestThingId(); break; case LastValuesBeginCmdId: requestLastValue(); break; + case PropertiesUpdateCmdId: + sendThingPropertiesToCloud(); + break; + default: break; } @@ -622,6 +512,7 @@ void ArduinoIoTCloudTCP::sendPropertyContainerToCloud(String const topic, Proper uint8_t data[MQTT_TRANSMIT_BUFFER_SIZE]; if (CBOREncoder::encode(property_container, data, sizeof(data), bytes_encoded, current_property_index, false) == CborNoError) + { if (bytes_encoded > 0) { /* If properties have been encoded store them in the back-up buffer @@ -632,6 +523,7 @@ void ArduinoIoTCloudTCP::sendPropertyContainerToCloud(String const topic, Proper /* Transmit the properties to the MQTT broker */ write(topic, _mqtt_data_buf, _mqtt_data_len); } + } } void ArduinoIoTCloudTCP::sendThingPropertiesToCloud() @@ -649,12 +541,11 @@ void ArduinoIoTCloudTCP::sendDevicePropertiesToCloud() ro_device_property_list.end(), [this, &ro_device_property_container ] (String const & name) { - Property* p = getProperty(this->_device_property_container, name); + Property* p = getProperty(this->_device.getPropertyContainer(), name); if(p != nullptr) addPropertyToContainer(ro_device_property_container, *p, p->name(), p->isWriteableByCloud() ? Permission::ReadWrite : Permission::Read); } ); - sendPropertyContainerToCloud(_deviceTopicOut, ro_device_property_container, last_device_property_index); } @@ -664,7 +555,7 @@ void ArduinoIoTCloudTCP::sendDevicePropertyToCloud(String const name) PropertyContainer temp_device_property_container; unsigned int last_device_property_index = 0; - Property* p = getProperty(this->_device_property_container, name); + Property* p = getProperty(this->_device.getPropertyContainer(), name); if(p != nullptr) { addPropertyToContainer(temp_device_property_container, *p, p->name(), p->isWriteableByCloud() ? Permission::ReadWrite : Permission::Read); @@ -682,6 +573,57 @@ void ArduinoIoTCloudTCP::requestLastValue() write(_shadowTopicOut, CBOR_REQUEST_LAST_VALUE_MSG, sizeof(CBOR_REQUEST_LAST_VALUE_MSG)); } +void ArduinoIoTCloudTCP::requestThingId() +{ + if (!_mqttClient.subscribe(_deviceTopicIn)) + { + /* If device_id is wrong the board can't connect to the broker so this condition + * should never happen. + */ + DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not subscribe to %s", __FUNCTION__, _deviceTopicIn.c_str()); + } +} + +void ArduinoIoTCloudTCP::attachThing() +{ + + _dataTopicIn = getTopic_datain(); + _dataTopicOut = getTopic_dataout(); + if (!_mqttClient.subscribe(_dataTopicIn)) { + DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not subscribe to %s", __FUNCTION__, _dataTopicIn.c_str()); + DEBUG_ERROR("Check your thing configuration, and press the reset button on your board."); + return; + } + + _shadowTopicIn = getTopic_shadowin(); + _shadowTopicOut = getTopic_shadowout(); + if (!_mqttClient.subscribe(_shadowTopicIn)) { + DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not subscribe to %s", __FUNCTION__, _shadowTopicIn.c_str()); + DEBUG_ERROR("Check your thing configuration, and press the reset button on your board."); + return; + } + + DEBUG_INFO("Connected to Arduino IoT Cloud"); + DEBUG_INFO("Thing ID: %s", getThingId().c_str()); + execCloudEventCallback(ArduinoIoTCloudEvent::CONNECT); +} + +void ArduinoIoTCloudTCP::detachThing() +{ + if (!_mqttClient.unsubscribe(_dataTopicIn)) { + DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not unsubscribe from %s", __FUNCTION__, _dataTopicIn.c_str()); + return; + } + + if (!_mqttClient.unsubscribe(_shadowTopicIn)) { + DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not unsubscribe from %s", __FUNCTION__, _shadowTopicIn.c_str()); + return; + } + + DEBUG_INFO("Disconnected from Arduino IoT Cloud"); + execCloudEventCallback(ArduinoIoTCloudEvent::DISCONNECT); +} + int ArduinoIoTCloudTCP::write(String const topic, byte const data[], int const length) { if (_mqttClient.beginMessage(topic, length, false, 0)) { @@ -694,16 +636,6 @@ int ArduinoIoTCloudTCP::write(String const topic, byte const data[], int const l return 0; } -void ArduinoIoTCloudTCP::updateThingTopics() -{ - _thing_id_property->fromCloudToLocal(); - - _shadowTopicOut = getTopic_shadowout(); - _shadowTopicIn = getTopic_shadowin(); - _dataTopicOut = getTopic_dataout(); - _dataTopicIn = getTopic_datain(); -} - /****************************************************************************** * EXTERN DEFINITION ******************************************************************************/ diff --git a/src/ArduinoIoTCloudTCP.h b/src/ArduinoIoTCloudTCP.h index 3d23e56a1..1c13592df 100644 --- a/src/ArduinoIoTCloudTCP.h +++ b/src/ArduinoIoTCloudTCP.h @@ -26,6 +26,7 @@ #include #include #include +#include #if defined(BOARD_HAS_SECURE_ELEMENT) #include @@ -51,6 +52,10 @@ #include #endif +#if OTA_ENABLED +#include +#endif + /****************************************************************************** CONSTANTS ******************************************************************************/ @@ -101,8 +106,7 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass _get_ota_confirmation = cb; _ask_user_before_executing_ota = true; } - - void handle_OTARequest(); + void handle_OTARequest(); #endif private: @@ -113,10 +117,6 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass ConnectPhy, SyncTime, ConnectMqttBroker, - SendDeviceProperties, - SubscribeDeviceTopic, - CheckDeviceConfig, - SubscribeThingTopics, Connected, Disconnect, }; @@ -126,7 +126,7 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass MessageStream _message_stream; ArduinoCloudThing _thing; Property * _thing_id_property; - PropertyContainer _device_property_container; + ArduinoCloudDevice _device; String _brokerAddress; uint16_t _brokerPort; @@ -190,10 +190,6 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass State handle_ConnectPhy(); State handle_SyncTime(); State handle_ConnectMqttBroker(); - State handle_SendDeviceProperties(); - State handle_CheckDeviceConfig(); - State handle_SubscribeDeviceTopic(); - State handle_SubscribeThingTopics(); State handle_Connected(); State handle_Disconnect(); @@ -204,13 +200,15 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass void sendThingPropertiesToCloud(); void sendDevicePropertiesToCloud(); void requestLastValue(); + void requestThingId(); + void attachThing(); + void detachThing(); int write(String const topic, byte const data[], int const length); #if OTA_ENABLED void sendDevicePropertyToCloud(String const name); #endif - void updateThingTopics(); }; /****************************************************************************** From 358194b9bd9fd01c5a357e4a58284d24d8bd7791 Mon Sep 17 00:00:00 2001 From: pennam Date: Mon, 15 Apr 2024 11:39:45 +0200 Subject: [PATCH 11/48] Cloud: initialize thing id to non null --- src/ArduinoIoTCloud.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ArduinoIoTCloud.cpp b/src/ArduinoIoTCloud.cpp index 0603b6759..a565cac44 100644 --- a/src/ArduinoIoTCloud.cpp +++ b/src/ArduinoIoTCloud.cpp @@ -28,9 +28,9 @@ ArduinoIoTCloudClass::ArduinoIoTCloudClass() : _connection{nullptr} , _time_service(TimeService) -, _thing_id{""} +, _thing_id{"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"} , _lib_version{AIOT_CONFIG_LIB_VERSION} -, _device_id{""} +, _device_id{"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"} , _cloud_event_callback{nullptr} { From 20d24e45e139b4e7d6c8cbcf0f992a0de8dfb341 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Mon, 5 Feb 2024 09:12:47 +0100 Subject: [PATCH 12/48] Defining encoder/decoder interfaces --- src/interfaces/Decoder.h | 41 ++++++++++++++++++++++++++++++++++++++++ src/interfaces/Encoder.h | 40 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 src/interfaces/Decoder.h create mode 100644 src/interfaces/Encoder.h diff --git a/src/interfaces/Decoder.h b/src/interfaces/Decoder.h new file mode 100644 index 000000000..fc725ec8b --- /dev/null +++ b/src/interfaces/Decoder.h @@ -0,0 +1,41 @@ +/* + 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 + +/****************************************************************************** + * INCLUDES + ******************************************************************************/ + +#include +#include + +/****************************************************************************** + * CLASS DECLARATION + ******************************************************************************/ + +class Decoder { +public: + enum Status: uint8_t { + Complete, + InProgress, + Error + }; + + /** + * Decode a buffer into a provided message structure + * @param msg: the message structure that is going to be filled with data provided in the buffer + * @param buf: the incoming buffer that needs to be decoded + * @param len: the length of the incoming buffer, value will be updated with the used len of the buffer + * @return SUCCESS: if the message is decoded correctly + * ERROR: if the message wasn't decoded correctly + */ + virtual Status decode(Message* msg, const uint8_t* const buf, size_t &len) = 0; +}; diff --git a/src/interfaces/Encoder.h b/src/interfaces/Encoder.h new file mode 100644 index 000000000..38fbe0edd --- /dev/null +++ b/src/interfaces/Encoder.h @@ -0,0 +1,40 @@ +/* + 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 + +/****************************************************************************** + * INCLUDES + ******************************************************************************/ +#include +#include + +/****************************************************************************** + * CLASS DECLARATION + ******************************************************************************/ + +class Encoder { +public: + enum Status: uint8_t { + Complete, + InProgress, + Error + }; + + /** + * Encode a message into a buffer in a single shot + * @param msg: the message that needs to be encoded + * @param buf: the buffer the message will be encoded into + * @param len: the length of the provided buffer, value will be updated with the consumed len of the buffer + * @return SUCCESS: if the message is encoded correctly + * ERROR: error during the encoding of the message + */ + virtual Status encode(Message* msg, uint8_t* buf, size_t& len) = 0; +}; From b98e3d2137bc071f33efe961f0c8fe5dfc2779ff Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 17 Apr 2024 17:31:35 +0200 Subject: [PATCH 13/48] CBOR encoder and decoder implementation for Command protocol model --- src/cbor/CBOR.cpp | 73 ++++++++++++ src/cbor/CBOR.h | 49 ++++++++ src/cbor/MessageDecoder.cpp | 230 ++++++++++++++++++++++++++++++++++++ src/cbor/MessageDecoder.h | 74 ++++++++++++ src/cbor/MessageEncoder.cpp | 175 +++++++++++++++++++++++++++ src/cbor/MessageEncoder.h | 65 ++++++++++ 6 files changed, 666 insertions(+) create mode 100644 src/cbor/CBOR.cpp create mode 100644 src/cbor/CBOR.h create mode 100644 src/cbor/MessageDecoder.cpp create mode 100644 src/cbor/MessageDecoder.h create mode 100644 src/cbor/MessageEncoder.cpp create mode 100644 src/cbor/MessageEncoder.h diff --git a/src/cbor/CBOR.cpp b/src/cbor/CBOR.cpp new file mode 100644 index 000000000..ced5e3e7f --- /dev/null +++ b/src/cbor/CBOR.cpp @@ -0,0 +1,73 @@ +/* + 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 "CBOR.h" + +/****************************************************************************** + * FUNCTION DEFINITION + ******************************************************************************/ + +CommandId toCommandId(CBORCommandTag tag) { + switch(tag) { + case CBORCommandTag::CBOROtaBeginUp: + return CommandId::OtaBeginUpId; + case CBORCommandTag::CBORThingBeginCmd: + return CommandId::ThingBeginCmdId; + case CBORCommandTag::CBORLastValuesBeginCmd: + return CommandId::LastValuesBeginCmdId; + case CBORCommandTag::CBORDeviceBeginCmd: + return CommandId::DeviceBeginCmdId; + case CBORCommandTag::CBOROtaProgressCmdUp: + return CommandId::OtaProgressCmdUpId; + case CBORCommandTag::CBORTimezoneCommandUp: + return CommandId::TimezoneCommandUpId; + case CBORCommandTag::CBOROtaUpdateCmdDown: + return CommandId::OtaUpdateCmdDownId; + case CBORCommandTag::CBORThingUpdateCmd: + return CommandId::ThingUpdateCmdId; + case CBORCommandTag::CBORLastValuesUpdate: + return CommandId::LastValuesUpdateCmdId; + case CBORCommandTag::CBORTimezoneCommandDown: + return CommandId::TimezoneCommandDownId; + default: + return CommandId::UnknownCmdId; + } +} + +CBORCommandTag toCBORCommandTag(CommandId id) { + switch(id) { + case CommandId::OtaBeginUpId: + return CBORCommandTag::CBOROtaBeginUp; + case CommandId::ThingBeginCmdId: + return CBORCommandTag::CBORThingBeginCmd; + case CommandId::LastValuesBeginCmdId: + return CBORCommandTag::CBORLastValuesBeginCmd; + case CommandId::DeviceBeginCmdId: + return CBORCommandTag::CBORDeviceBeginCmd; + case CommandId::OtaProgressCmdUpId: + return CBORCommandTag::CBOROtaProgressCmdUp; + case CommandId::TimezoneCommandUpId: + return CBORCommandTag::CBORTimezoneCommandUp; + case CommandId::OtaUpdateCmdDownId: + return CBORCommandTag::CBOROtaUpdateCmdDown; + case CommandId::ThingUpdateCmdId: + return CBORCommandTag::CBORThingUpdateCmd; + case CommandId::LastValuesUpdateCmdId: + return CBORCommandTag::CBORLastValuesUpdate; + case CommandId::TimezoneCommandDownId: + return CBORCommandTag::CBORTimezoneCommandDown; + default: + return CBORCommandTag::CBORUnknownCmdTag; + } +} diff --git a/src/cbor/CBOR.h b/src/cbor/CBOR.h new file mode 100644 index 000000000..999570455 --- /dev/null +++ b/src/cbor/CBOR.h @@ -0,0 +1,49 @@ +/* + 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 + +/****************************************************************************** + TYPEDEF + ******************************************************************************/ + +enum CBORCommandTag: uint64_t { + // Commands UP + CBOROtaBeginUp = 0x010000, + CBORThingBeginCmd = 0x010300, + CBORLastValuesBeginCmd = 0x010500, + CBORDeviceBeginCmd = 0x010700, + CBOROtaProgressCmdUp = 0x010200, + CBORTimezoneCommandUp = 0x010800, + + // Commands DOWN + CBOROtaUpdateCmdDown = 0x010100, + CBORThingUpdateCmd = 0x010400, + CBORLastValuesUpdate = 0x010600, + CBORTimezoneCommandDown = 0x010900, + + // Unknown Command Tag https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml + CBORUnknownCmdTag16b = 0xffff, // invalid tag + CBORUnknownCmdTag32b = 0xffffffff, // invalid tag + CBORUnknownCmdTag64b = 0xffffffffffffffff, // invalid tag + CBORUnknownCmdTag = CBORUnknownCmdTag32b +}; + +/****************************************************************************** + * FUNCTION DECLARATION + ******************************************************************************/ + +CommandId toCommandId(CBORCommandTag tag); +CBORCommandTag toCBORCommandTag(CommandId id); diff --git a/src/cbor/MessageDecoder.cpp b/src/cbor/MessageDecoder.cpp new file mode 100644 index 000000000..c500ceae9 --- /dev/null +++ b/src/cbor/MessageDecoder.cpp @@ -0,0 +1,230 @@ +/* + 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 + +#undef max +#undef min +#include + +#include "MessageDecoder.h" +#include + +/****************************************************************************** + PUBLIC MEMBER FUNCTIONS + ******************************************************************************/ + +Decoder::Status CBORMessageDecoder::decode(Message * message, uint8_t const * const payload, size_t& length) +{ + CborValue main_iter, array_iter; + CborTag tag; + CborParser parser; + + if (cbor_parser_init(payload, length, 0, &parser, &main_iter) != CborNoError) + return Decoder::Status::Error; + + if (main_iter.type != CborTagType) + return Decoder::Status::Error; + + if (cbor_value_get_tag(&main_iter, &tag) == CborNoError) { + message->id = toCommandId(CBORCommandTag(tag)); + } + + if (cbor_value_advance(&main_iter) != CborNoError) { + return Decoder::Status::Error; + } + + ArrayParserState current_state = ArrayParserState::EnterArray, + next_state = ArrayParserState::Error; + + while (current_state != ArrayParserState::Complete) { + switch (current_state) { + case ArrayParserState::EnterArray : next_state = handle_EnterArray(&main_iter, &array_iter); break; + case ArrayParserState::ParseParam : next_state = handle_Param(&array_iter, message); break; + case ArrayParserState::LeaveArray : next_state = handle_LeaveArray(&main_iter, &array_iter); break; + case ArrayParserState::Complete : return Decoder::Status::Complete; + case ArrayParserState::MessageNotSupported : return Decoder::Status::Error; + case ArrayParserState::Error : return Decoder::Status::Error; + } + + current_state = next_state; + } + + return Decoder::Status::Complete; +} + +/****************************************************************************** + PRIVATE MEMBER FUNCTIONS + ******************************************************************************/ + +bool copyCBORStringToArray(CborValue * param, char * dest, size_t dest_size) { + if (cbor_value_is_text_string(param)) { + // NOTE: keep in mind that _cbor_value_copy_string tries to put a \0 at the end of the string + if(_cbor_value_copy_string(param, dest, &dest_size, NULL) == CborNoError) { + return true; + } + } + + return false; +} + +// FIXME dest_size should be also returned, the copied byte array can have a different size from the starting one +// for the time being we need this on SHA256 only +bool copyCBORByteToArray(CborValue * param, uint8_t * dest, size_t dest_size) { + if (cbor_value_is_byte_string(param)) { + // NOTE: keep in mind that _cbor_value_copy_string tries to put a \0 at the end of the string + if(_cbor_value_copy_string(param, dest, &dest_size, NULL) == CborNoError) { + return true; + } + } + + return false; +} + +CBORMessageDecoder::ArrayParserState CBORMessageDecoder::handle_EnterArray(CborValue * main_iter, CborValue * array_iter) { + ArrayParserState next_state = ArrayParserState::Error; + if (cbor_value_get_type(main_iter) == CborArrayType) { + if (cbor_value_enter_container(main_iter, array_iter) == CborNoError) { + next_state = ArrayParserState::ParseParam; + } + } + + return next_state; +} + +CBORMessageDecoder::ArrayParserState CBORMessageDecoder::handle_LeaveArray(CborValue * main_iter, CborValue * array_iter) { + // Advance to the next parameter (the last one in the array) + if (cbor_value_advance(array_iter) == CborNoError) { + // Leave the array + if (cbor_value_leave_container(main_iter, array_iter) == CborNoError) { + return ArrayParserState::Complete; + } + } + + return ArrayParserState::Error; +} + +/****************************************************************************** + MESSAGE DECODE FUNCTIONS + ******************************************************************************/ + +CBORMessageDecoder::ArrayParserState CBORMessageDecoder::decodeThingUpdateCmd(CborValue * param, Message * message) { + ThingUpdateCmd * thingCommand = (ThingUpdateCmd *) message; + + // Message is composed of a single parameter, a string (thing_id) + if (!copyCBORStringToArray(param, thingCommand->params.thing_id, sizeof(thingCommand->params.thing_id))) { + return ArrayParserState::Error; + } + + return ArrayParserState::LeaveArray; +} + +CBORMessageDecoder::ArrayParserState CBORMessageDecoder::decodeTimezoneCommandDown(CborValue * param, Message * message) { + TimezoneCommandDown * setTz = (TimezoneCommandDown *) message; + + // Message is composed of 2 parameters, offset 32-bit signed integer and until 32-bit unsigned integer + // Get offset + if (cbor_value_is_integer(param)) { + int64_t val = 0; + if (cbor_value_get_int64(param, &val) == CborNoError) { + setTz->params.offset = static_cast(val); + } + } + + // Next + if (cbor_value_advance(param) != CborNoError) { + return ArrayParserState::Error; + } + + // Get until + if (cbor_value_is_integer(param)) { + uint64_t val = 0; + if (cbor_value_get_uint64(param, &val) == CborNoError) { + setTz->params.until = static_cast(val); + } + } + + return ArrayParserState::LeaveArray; +} + +CBORMessageDecoder::ArrayParserState CBORMessageDecoder::decodeLastValuesUpdateCmd(CborValue * param, Message * message) { + LastValuesUpdateCmd * setLv = (LastValuesUpdateCmd *) message; + + // Message is composed by a single parameter, a variable length byte array. + if (cbor_value_is_byte_string(param)) { + // Cortex M0 is not able to assign a value to pointed memory that is not 32bit aligned + // we use a support variable to cope with that + size_t s; + if (cbor_value_dup_byte_string(param, &setLv->params.last_values, &s, NULL) != CborNoError) { + return ArrayParserState::Error; + } + + setLv->params.length = s; + } + + return ArrayParserState::LeaveArray; +} + +CBORMessageDecoder::ArrayParserState CBORMessageDecoder::decodeOtaUpdateCmdDown(CborValue * param, Message * message) { + CborError error = CborNoError; + OtaUpdateCmdDown * ota = (OtaUpdateCmdDown *) message; + + // Message is composed 4 parameters: id, url, initialSha, finalSha + if (!copyCBORByteToArray(param, ota->params.id, sizeof(ota->params.id))) { + return ArrayParserState::Error; + } + + error = cbor_value_advance(param); + + if ((error != CborNoError) || !copyCBORStringToArray(param, ota->params.url, sizeof(ota->params.url))) { + return ArrayParserState::Error; + } + + error = cbor_value_advance(param); + + if ((error != CborNoError) || !copyCBORByteToArray(param, ota->params.initialSha256, sizeof(ota->params.initialSha256))) { + return ArrayParserState::Error; + } + + error = cbor_value_advance(param); + + if ((error != CborNoError) || !copyCBORByteToArray(param, ota->params.finalSha256, sizeof(ota->params.finalSha256))) { + return ArrayParserState::Error; + } + + return ArrayParserState::LeaveArray; +} + +CBORMessageDecoder::ArrayParserState CBORMessageDecoder::handle_Param(CborValue * param, Message * message) { + + switch (message->id) + { + case CommandId::ThingUpdateCmdId: + return CBORMessageDecoder::decodeThingUpdateCmd(param, message); + + case CommandId::TimezoneCommandDownId: + return CBORMessageDecoder::decodeTimezoneCommandDown(param, message); + + case CommandId::LastValuesUpdateCmdId: + return CBORMessageDecoder::decodeLastValuesUpdateCmd(param, message); + + case CommandId::OtaUpdateCmdDownId: + return CBORMessageDecoder::decodeOtaUpdateCmdDown(param, message); + + default: + return ArrayParserState::MessageNotSupported; + } + + return ArrayParserState::LeaveArray; +} diff --git a/src/cbor/MessageDecoder.h b/src/cbor/MessageDecoder.h new file mode 100644 index 000000000..712289c9d --- /dev/null +++ b/src/cbor/MessageDecoder.h @@ -0,0 +1,74 @@ +/* + 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/. +*/ + +#ifndef ARDUINO_CBOR_MESSAGE_DECODER_H_ +#define ARDUINO_CBOR_MESSAGE_DECODER_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include + +#undef max +#undef min +#include + +#include "CBOR.h" +#include "../interfaces/Decoder.h" +#include "lib/tinycbor/cbor-lib.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + +class CBORMessageDecoder: public Decoder +{ +public: + CBORMessageDecoder() { } + CBORMessageDecoder(CBORMessageDecoder const &) { } + + /* decode a CBOR payload received from the cloud */ + Decoder::Status decode(Message * msg, uint8_t const * const payload, size_t& length); + +private: + + enum class DecoderState { + Success, + MessageNotSupported, + MalformedMessage, + Error + }; + + enum class ArrayParserState { + EnterArray, + ParseParam, + LeaveArray, + Complete, + Error, + MessageNotSupported + }; + + ArrayParserState handle_EnterArray(CborValue * main_iter, CborValue * array_iter); + ArrayParserState handle_Param(CborValue * param, Message * message); + ArrayParserState handle_LeaveArray(CborValue * main_iter, CborValue * array_iter); + + bool ifNumericConvertToDouble(CborValue * value_iter, double * numeric_val); + double convertCborHalfFloatToDouble(uint16_t const half_val); + + // Message specific decoders + ArrayParserState decodeThingUpdateCmd(CborValue * param, Message * message); + ArrayParserState decodeTimezoneCommandDown(CborValue * param, Message * message); + ArrayParserState decodeLastValuesUpdateCmd(CborValue * param, Message * message); + ArrayParserState decodeOtaUpdateCmdDown(CborValue * param, Message * message); + +}; + +#endif /* ARDUINO_CBOR_MESSAGE_DECODER_H_ */ diff --git a/src/cbor/MessageEncoder.cpp b/src/cbor/MessageEncoder.cpp new file mode 100644 index 000000000..cdeb0f8ed --- /dev/null +++ b/src/cbor/MessageEncoder.cpp @@ -0,0 +1,175 @@ +/* + 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 "CBOREncoder.h" + +#undef max +#undef min +#include +#include + +#include "lib/tinycbor/cbor-lib.h" +#include "MessageEncoder.h" + +/****************************************************************************** + * PUBLIC MEMBER FUNCTIONS + ******************************************************************************/ + +Encoder::Status CBORMessageEncoder::encode(Message * message, uint8_t * data, size_t& len) +{ + EncoderState current_state = EncoderState::EncodeTag, + next_state = EncoderState::Error; + + CborEncoder encoder; + CborEncoder arrayEncoder; + + cbor_encoder_init(&encoder, data, len, 0); + + while (current_state != EncoderState::Complete) { + + switch (current_state) { + case EncoderState::EncodeTag : next_state = handle_EncodeTag(&encoder, message); break; + case EncoderState::EncodeArray : next_state = handle_EncodeArray(&encoder, &arrayEncoder, message); break; + case EncoderState::EncodeParam : next_state = handle_EncodeParam(&arrayEncoder, message); break; + case EncoderState::CloseArray : next_state = handle_CloseArray(&encoder, &arrayEncoder); break; + case EncoderState::Complete : /* Nothing to do */ break; + case EncoderState::MessageNotSupported : + case EncoderState::Error : return Encoder::Status::Error; + } + + current_state = next_state; + } + + len = cbor_encoder_get_buffer_size(&encoder, data); + + return Encoder::Status::Complete; +} + +/****************************************************************************** + PRIVATE MEMBER FUNCTIONS + ******************************************************************************/ + +CBORMessageEncoder::EncoderState CBORMessageEncoder::handle_EncodeTag(CborEncoder * encoder, Message * message) +{ + CborTag commandTag = toCBORCommandTag(message->id); + if (commandTag == CBORCommandTag::CBORUnknownCmdTag16b || + commandTag == CBORCommandTag::CBORUnknownCmdTag32b || + commandTag == CBORCommandTag::CBORUnknownCmdTag64b || + cbor_encode_tag(encoder, commandTag) != CborNoError) { + return EncoderState::Error; + } + + return EncoderState::EncodeArray; +} + +CBORMessageEncoder::EncoderState CBORMessageEncoder::handle_EncodeArray(CborEncoder * encoder, CborEncoder * array_encoder, Message * message) +{ + // Set array size based on the message id + size_t array_size = 0; + switch (message->id) + { + case CommandId::OtaBeginUpId: + array_size = 1; + break; + case CommandId::ThingBeginCmdId: + array_size = 1; + break; + case CommandId::DeviceBeginCmdId: + array_size = 1; + break; + case CommandId::LastValuesBeginCmdId: + break; + case CommandId::OtaProgressCmdUpId: + array_size = 4; + break; + case CommandId::TimezoneCommandUpId: + break; + default: + return EncoderState::MessageNotSupported; + } + + // Start an array with fixed width based on message type + if (cbor_encoder_create_array(encoder, array_encoder, array_size) != CborNoError){ + return EncoderState::Error; + } + + return EncoderState::EncodeParam; +} + +CBORMessageEncoder::EncoderState CBORMessageEncoder::handle_EncodeParam(CborEncoder * array_encoder, Message * message) +{ + CborError error = CborNoError; + switch (message->id) + { + case CommandId::OtaBeginUpId: + error = CBORMessageEncoder::encodeOtaBeginUp(array_encoder, message); + break; + case CommandId::ThingBeginCmdId: + error = CBORMessageEncoder::encodeThingBeginCmd(array_encoder, message); + break; + case CommandId::DeviceBeginCmdId: + error = CBORMessageEncoder::encodeDeviceBeginCmd(array_encoder, message); + break; + case CommandId::LastValuesBeginCmdId: + break; + case CommandId::OtaProgressCmdUpId: + error = CBORMessageEncoder::encodeOtaProgressCmdUp(array_encoder, message); + break; + case CommandId::TimezoneCommandUpId: + break; + default: + return EncoderState::MessageNotSupported; + } + + return (error != CborNoError) ? EncoderState::Error : EncoderState::CloseArray; +} + +CBORMessageEncoder::EncoderState CBORMessageEncoder::handle_CloseArray(CborEncoder * encoder, CborEncoder * array_encoder) +{ + CborError error = cbor_encoder_close_container(encoder, array_encoder); + + return (error != CborNoError) ? EncoderState::Error : EncoderState::Complete; +} + +// Message specific encoders +CborError CBORMessageEncoder::encodeOtaBeginUp(CborEncoder * array_encoder, Message * message) +{ + OtaBeginUp * otaBeginUp = (OtaBeginUp *) message; + CHECK_CBOR(cbor_encode_byte_string(array_encoder, otaBeginUp->params.sha, SHA256_SIZE)); + return CborNoError; +} + +CborError CBORMessageEncoder::encodeThingBeginCmd(CborEncoder * array_encoder, Message * message) +{ + ThingBeginCmd * thingBeginCmd = (ThingBeginCmd *) message; + CHECK_CBOR(cbor_encode_text_stringz(array_encoder, thingBeginCmd->params.thing_id)); + return CborNoError; +} + +CborError CBORMessageEncoder::encodeDeviceBeginCmd(CborEncoder * array_encoder, Message * message) +{ + DeviceBeginCmd * deviceBeginCmd = (DeviceBeginCmd *) message; + CHECK_CBOR(cbor_encode_text_stringz(array_encoder, deviceBeginCmd->params.lib_version)); + return CborNoError; +} + +CborError CBORMessageEncoder::encodeOtaProgressCmdUp(CborEncoder * array_encoder, Message * message) +{ + OtaProgressCmdUp * ota = (OtaProgressCmdUp *)message; + CHECK_CBOR(cbor_encode_byte_string(array_encoder, ota->params.id, ID_SIZE)); + CHECK_CBOR(cbor_encode_simple_value(array_encoder, ota->params.state)); + CHECK_CBOR(cbor_encode_int(array_encoder, ota->params.state_data)); + CHECK_CBOR(cbor_encode_uint(array_encoder, ota->params.time)); + return CborNoError; +} diff --git a/src/cbor/MessageEncoder.h b/src/cbor/MessageEncoder.h new file mode 100644 index 000000000..86792eb19 --- /dev/null +++ b/src/cbor/MessageEncoder.h @@ -0,0 +1,65 @@ +/* + 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/. +*/ + +#ifndef ARDUINO_CBOR_MESSAGE_ENCODER_H_ +#define ARDUINO_CBOR_MESSAGE_ENCODER_H_ + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include + +#undef max +#undef min +#include + +#include "CBOR.h" +#include "../interfaces/Encoder.h" +#include "lib/tinycbor/cbor-lib.h" + +/****************************************************************************** + * CLASS DECLARATION + ******************************************************************************/ + +class CBORMessageEncoder: public Encoder +{ + +public: + CBORMessageEncoder() { } + CBORMessageEncoder(CborEncoder const &) { } + Encoder::Status encode(Message * message, uint8_t * data, size_t& len); + +private: + + enum class EncoderState + { + EncodeTag, + EncodeArray, + EncodeParam, + CloseArray, + MessageNotSupported, + Complete, + Error + }; + + EncoderState handle_EncodeTag(CborEncoder * encoder, Message * message); + EncoderState handle_EncodeArray(CborEncoder * encoder, CborEncoder * array_encoder, Message * message); + EncoderState handle_EncodeParam(CborEncoder * array_encoder, Message * message); + EncoderState handle_CloseArray(CborEncoder * encoder, CborEncoder * array_encoder); + + // Message specific encoders + CborError encodeThingBeginCmd(CborEncoder * array_encoder, Message * message); + CborError encodeOtaBeginUp(CborEncoder * array_encoder, Message * message); + CborError encodeDeviceBeginCmd(CborEncoder * array_encoder, Message * message); + CborError encodeOtaProgressCmdUp(CborEncoder * array_encoder, Message * message); +}; + +#endif /* ARDUINO_CBOR_MESSAGE_ENCODER_H_ */ From f13300a9ee9acda7f07cb448a9f1bcb314b547eb Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 17 Apr 2024 17:31:59 +0200 Subject: [PATCH 14/48] Add tests for encoder and decoder --- extras/test/CMakeLists.txt | 5 + extras/test/src/test_command_decode.cpp | 736 ++++++++++++++++++++++++ extras/test/src/test_command_encode.cpp | 322 +++++++++++ 3 files changed, 1063 insertions(+) create mode 100644 extras/test/src/test_command_decode.cpp create mode 100644 extras/test/src/test_command_encode.cpp diff --git a/extras/test/CMakeLists.txt b/extras/test/CMakeLists.txt index 2588c27c0..2aab9ab9b 100644 --- a/extras/test/CMakeLists.txt +++ b/extras/test/CMakeLists.txt @@ -36,6 +36,8 @@ set(TEST_SRCS src/test_CloudSchedule.cpp src/test_decode.cpp src/test_encode.cpp + src/test_command_decode.cpp + src/test_command_encode.cpp src/test_publishEvery.cpp src/test_publishOnChange.cpp src/test_publishOnChangeRateLimit.cpp @@ -55,6 +57,9 @@ set(TEST_DUT_SRCS ../../src/property/PropertyContainer.cpp ../../src/cbor/CBORDecoder.cpp ../../src/cbor/CBOREncoder.cpp + ../../src/cbor/MessageDecoder.cpp + ../../src/cbor/MessageEncoder.cpp + ../../src/cbor/CBOR.cpp ../../src/cbor/lib/tinycbor/src/cborencoder.c ../../src/cbor/lib/tinycbor/src/cborencoder_close_container_checked.c ../../src/cbor/lib/tinycbor/src/cborerrorstrings.c diff --git a/extras/test/src/test_command_decode.cpp b/extras/test/src/test_command_decode.cpp new file mode 100644 index 000000000..818d02136 --- /dev/null +++ b/extras/test/src/test_command_decode.cpp @@ -0,0 +1,736 @@ +/* + Copyright (c) 2024 Arduino. All rights reserved. +*/ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include + +#include + +#include +#include + +/****************************************************************************** + TEST CODE + ******************************************************************************/ + +SCENARIO("Test the decoding of command messages") { + /****************************************************************************/ + + WHEN("Decode the ThingUpdateCmdId message") + { + CommandDown command; + /* + DA 00010400 # tag(66560) + 81 # array(1) + 78 24 # text(36) + 65343439346435352D383732612D346664322D393634362D393266383739343933393463 # "e4494d55-872a-4fd2-9646-92f87949394c" + */ + uint8_t const payload[] = {0xDA, 0x00, 0x01, 0x04, 0x00, 0x81, 0x78, 0x24, + 0x65, 0x34, 0x34, 0x39, 0x34, 0x64, 0x35, 0x35, + 0x2D, 0x38, 0x37, 0x32, 0x61, 0x2D, 0x34, 0x66, + 0x64, 0x32, 0x2D, 0x39, 0x36, 0x34, 0x36, 0x2D, + 0x39, 0x32, 0x66, 0x38, 0x37, 0x39, 0x34, 0x39, + 0x33, 0x39, 0x34, 0x63}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + const char *thingIdToMatch = "e4494d55-872a-4fd2-9646-92f87949394c"; + + THEN("The decode is successful") { + REQUIRE(err == Decoder::Status::Complete); + REQUIRE(strcmp(command.thingUpdateCmd.params.thing_id, thingIdToMatch) == 0); + REQUIRE(command.c.id == ThingUpdateCmdId); + } + } + + /****************************************************************************/ + + WHEN("Decode the ThingUpdateCmdId message containing a number instead of a string") + { + CommandDown command; + /* + DA 00010400 # tag(66560) + 81 # array(1) + 1A 65DCB821 # unsigned(1708963873) + */ + uint8_t const payload[] = {0xDA, 0x00, 0x01, 0x04, 0x00, 0x81, 0x1A, 0x65, + 0xDC, 0xB8, 0x21}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode the SetTimezoneCommand message") + { + CommandDown command; + + /* + DA 00010764 # tag(67840) + 82 # array(2) + 1A 65DCB821 # unsigned(1708963873) + 1A 78ACA191 # unsigned(2024579473) + */ + + uint8_t const payload[] = {0xDA, 0x00, 0x01, 0x09, 0x00, 0x82, 0x1A, 0x65, + 0xDC, 0xB8, 0x21, 0x1A, 0x78, 0xAC, 0xA1, 0x91}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is successful") { + REQUIRE(err == Decoder::Status::Complete); + REQUIRE(command.timezoneCommandDown.params.offset == (uint32_t)1708963873); + REQUIRE(command.timezoneCommandDown.params.until == (uint32_t)2024579473); + REQUIRE(command.c.id == TimezoneCommandDownId); + } + } + + /****************************************************************************/ + + WHEN("Decode the LastValuesUpdateCmd message") + { + CommandDown command; + + /* + DA 00010600 # tag(67072) + 81 # array(1) + 4D # bytes(13) + 00010203040506070809101112 # "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\u0010\u0011\u0012" + + */ + + uint8_t const payload[] = {0xDA, 0x00, 0x01, 0x06, 0x00, 0x81, 0x4D, 0x00, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x10, 0x11, 0x12}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is successful") { + REQUIRE(err == Decoder::Status::Complete); + REQUIRE(command.lastValuesUpdateCmd.params.length == 13); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[0] == (uint8_t)0x00); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[1] == (uint8_t)0x01); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[2] == (uint8_t)0x02); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[3] == (uint8_t)0x03); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[4] == (uint8_t)0x04); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[5] == (uint8_t)0x05); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[6] == (uint8_t)0x06); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[7] == (uint8_t)0x07); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[8] == (uint8_t)0x08); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[9] == (uint8_t)0x09); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[10] == (uint8_t)0x10); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[11] == (uint8_t)0x11); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[12] == (uint8_t)0x12); + REQUIRE(command.c.id == LastValuesUpdateCmdId); + } + free(command.lastValuesUpdateCmd.params.last_values); + } + + /****************************************************************************/ + + WHEN("Decode the OtaUpdateCmdDown message") + { + CommandDown command; + + /* + DA 00010100 # tag(65792) + 84 # array(4) + 50 # bytes(16) + C73CB045F9C2434585AFFA36A307BFE7"\xC7<\xB0E\xF9\xC2CE\x85\xAF\xFA6\xA3\a\xBF\xE7" + 78 72 # text(141) + 68747470733A2F2F626F617264732D69 + 6E742E6F6E69756472612E63632F7374 + 6F726167652F6669726D776172652F76 + 312F6466316561633963376264363334 + 37336666666231313766393837333730 + 33653465633935353933316532363766 + 32363236326230393439626331366463 + 3439 # "https://boards-int.oniudra.cc/storage/firmware/v1/df1eac9c7bd63473fffb117f9873703e4ec955931e267f26262b0949bc16dc49" + 58 20 # bytes(32) + 00000000000000000000000000000000 + 00000000000000000000000000000000# "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000" + 58 20 # bytes(32) + DF1EAC9C7BD63473FFFB117F9873703E + 4EC955931E267F26262B0949BC16DC49# "\xDF\u001E\xAC\x9C{\xD64s\xFF\xFB\u0011\u007F\x98sp>N\xC9U\x93\u001E&\u007F&&+\tI\xBC\u0016\xDCI" + + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x01, 0x00, 0x84, 0x50, 0xc7, + 0x3c, 0xb0, 0x45, 0xf9, 0xc2, 0x43, 0x45, 0x85, + 0xaf, 0xfa, 0x36, 0xa3, 0x07, 0xbf, 0xe7, 0x78, + 0x72, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, + 0x2f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x2d, + 0x69, 0x6e, 0x74, 0x2e, 0x6f, 0x6e, 0x69, 0x75, + 0x64, 0x72, 0x61, 0x2e, 0x63, 0x63, 0x2f, 0x73, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x66, + 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2f, + 0x76, 0x31, 0x2f, 0x64, 0x66, 0x31, 0x65, 0x61, + 0x63, 0x39, 0x63, 0x37, 0x62, 0x64, 0x36, 0x33, + 0x34, 0x37, 0x33, 0x66, 0x66, 0x66, 0x62, 0x31, + 0x31, 0x37, 0x66, 0x39, 0x38, 0x37, 0x33, 0x37, + 0x30, 0x33, 0x65, 0x34, 0x65, 0x63, 0x39, 0x35, + 0x35, 0x39, 0x33, 0x31, 0x65, 0x32, 0x36, 0x37, + 0x66, 0x32, 0x36, 0x32, 0x36, 0x32, 0x62, 0x30, + 0x39, 0x34, 0x39, 0x62, 0x63, 0x31, 0x36, 0x64, + 0x63, 0x34, 0x39, 0x58, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x20, 0xdf, + 0x1e, 0xac, 0x9c, 0x7b, 0xd6, 0x34, 0x73, 0xff, + 0xfb, 0x11, 0x7f, 0x98, 0x73, 0x70, 0x3e, 0x4e, + 0xc9, 0x55, 0x93, 0x1e, 0x26, 0x7f, 0x26, 0x26, + 0x2b, 0x09, 0x49, 0xbc, 0x16, 0xdc, 0x49}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + uint8_t otaIdToMatch[ID_SIZE] = { 0xC7, 0x3C, 0xB0, 0x45, 0xF9, 0xC2, 0x43, 0x45, + 0x85, 0xAF, 0xFA, 0x36, 0xA3, 0x07, 0xBF, 0xE7}; + const char *urlToMatch = "https://boards-int.oniudra.cc/storage/firmware/v1/df1eac9c7bd63473fffb117f9873703e4ec955931e267f26262b0949bc16dc49"; + + THEN("The decode is successful") { + REQUIRE(err == Decoder::Status::Complete); + REQUIRE(memcmp(command.otaUpdateCmdDown.params.id, otaIdToMatch, ID_SIZE) == 0); + REQUIRE(strcmp(command.otaUpdateCmdDown.params.url, urlToMatch) == 0); + // Initial SHA256 check + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[0] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[1] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[2] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[3] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[4] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[5] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[6] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[7] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[8] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[9] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[10] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[11] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[12] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[13] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[14] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[15] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[16] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[17] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[18] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[19] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[20] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[21] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[22] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[23] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[24] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[25] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[26] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[27] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[28] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[29] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[30] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[31] == (uint8_t)0x00); + + // Final SHA256 check + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[0] == (uint8_t)0xdf); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[1] == (uint8_t)0x1e); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[2] == (uint8_t)0xac); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[3] == (uint8_t)0x9c); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[4] == (uint8_t)0x7b); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[5] == (uint8_t)0xd6); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[6] == (uint8_t)0x34); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[7] == (uint8_t)0x73); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[8] == (uint8_t)0xff); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[9] == (uint8_t)0xfb); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[10] == (uint8_t)0x11); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[11] == (uint8_t)0x7f); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[12] == (uint8_t)0x98); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[13] == (uint8_t)0x73); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[14] == (uint8_t)0x70); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[15] == (uint8_t)0x3e); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[16] == (uint8_t)0x4e); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[17] == (uint8_t)0xc9); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[18] == (uint8_t)0x55); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[19] == (uint8_t)0x93); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[20] == (uint8_t)0x1e); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[21] == (uint8_t)0x26); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[22] == (uint8_t)0x7f); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[23] == (uint8_t)0x26); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[24] == (uint8_t)0x26); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[25] == (uint8_t)0x2b); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[26] == (uint8_t)0x09); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[27] == (uint8_t)0x49); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[28] == (uint8_t)0xbc); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[29] == (uint8_t)0x16); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[30] == (uint8_t)0xdc); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[31] == (uint8_t)0x49); + + REQUIRE(command.c.id == OtaUpdateCmdDownId); + } + } + +/****************************************************************************/ + + WHEN("Decode the OtaUpdateCmdDown message with out of order fields 1") + { + CommandDown command; + + /* + DA 00010100 # tag(65792) + 84 # array(4) + 78 72 # text(141) + 68747470733A2F2F626F617264732D69 + 6E742E6F6E69756472612E63632F7374 + 6F726167652F6669726D776172652F76 + 312F6466316561633963376264363334 + 37336666666231313766393837333730 + 33653465633935353933316532363766 + 32363236326230393439626331366463 + 3439 # "https://boards-int.oniudra.cc/storage/firmware/v1/df1eac9c7bd63473fffb117f9873703e4ec955931e267f26262b0949bc16dc49" + 50 # bytes(16) + C73CB045F9C2434585AFFA36A307BFE7"\xC7<\xB0E\xF9\xC2CE\x85\xAF\xFA6\xA3\a\xBF\xE7" + 58 20 # bytes(32) + 00000000000000000000000000000000 + 00000000000000000000000000000000# "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000" + 58 20 # bytes(32) + DF1EAC9C7BD63473FFFB117F9873703E + 4EC955931E267F26262B0949BC16DC49# "\xDF\u001E\xAC\x9C{\xD64s\xFF\xFB\u0011\u007F\x98sp>N\xC9U\x93\u001E&\u007F&&+\tI\xBC\u0016\xDCI" + + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x01, 0x00, 0x84, 0x78, 0x72, + 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, + 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x2d, 0x69, + 0x6e, 0x74, 0x2e, 0x6f, 0x6e, 0x69, 0x75, 0x64, + 0x72, 0x61, 0x2e, 0x63, 0x63, 0x2f, 0x73, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x66, 0x69, + 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2f, 0x76, + 0x31, 0x2f, 0x64, 0x66, 0x31, 0x65, 0x61, 0x63, + 0x39, 0x63, 0x37, 0x62, 0x64, 0x36, 0x33, 0x34, + 0x37, 0x33, 0x66, 0x66, 0x66, 0x62, 0x31, 0x31, + 0x37, 0x66, 0x39, 0x38, 0x37, 0x33, 0x37, 0x30, + 0x33, 0x65, 0x34, 0x65, 0x63, 0x39, 0x35, 0x35, + 0x39, 0x33, 0x31, 0x65, 0x32, 0x36, 0x37, 0x66, + 0x32, 0x36, 0x32, 0x36, 0x32, 0x62, 0x30, 0x39, + 0x34, 0x39, 0x62, 0x63, 0x31, 0x36, 0x64, 0x63, + 0x34, 0x39, 0x50, 0xc7, 0x3c, 0xb0, 0x45, 0xf9, + 0xc2, 0x43, 0x45, 0x85, 0xaf, 0xfa, 0x36, 0xa3, + 0x07, 0xbf, 0xe7, 0x58, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x20, 0xdf, + 0x1e, 0xac, 0x9c, 0x7b, 0xd6, 0x34, 0x73, 0xff, + 0xfb, 0x11, 0x7f, 0x98, 0x73, 0x70, 0x3e, 0x4e, + 0xc9, 0x55, 0x93, 0x1e, 0x26, 0x7f, 0x26, 0x26, + 0x2b, 0x09, 0x49, 0xbc, 0x16, 0xdc, 0x49}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is successful") { + REQUIRE(err == Decoder::Status::Error); + } + } + +/****************************************************************************/ + + WHEN("Decode the OtaUpdateCmdDown message with out of order fields 2") + { + CommandDown command; + + /* + DA 00010100 # tag(65792) + 84 # array(4) + 50 # bytes(16) + C73CB045F9C2434585AFFA36A307BFE7"\xC7<\xB0E\xF9\xC2CE\x85\xAF\xFA6\xA3\a\xBF\xE7" + 58 20 # bytes(32) + 00000000000000000000000000000000 + 00000000000000000000000000000000# "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000" + 78 72 # text(141) + 68747470733A2F2F626F617264732D69 + 6E742E6F6E69756472612E63632F7374 + 6F726167652F6669726D776172652F76 + 312F6466316561633963376264363334 + 37336666666231313766393837333730 + 33653465633935353933316532363766 + 32363236326230393439626331366463 + 3439 # "https://boards-int.oniudra.cc/storage/firmware/v1/df1eac9c7bd63473fffb117f9873703e4ec955931e267f26262b0949bc16dc49" + 58 20 # bytes(32) + DF1EAC9C7BD63473FFFB117F9873703E + 4EC955931E267F26262B0949BC16DC49# "\xDF\u001E\xAC\x9C{\xD64s\xFF\xFB\u0011\u007F\x98sp>N\xC9U\x93\u001E&\u007F&&+\tI\xBC\u0016\xDCI" + + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x01, 0x00, 0x84, 0x50, 0xc7, + 0x3c, 0xb0, 0x45, 0xf9, 0xc2, 0x43, 0x45, 0x85, + 0xaf, 0xfa, 0x36, 0xa3, 0x07, 0xbf, 0xe7, 0x58, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x78, 0x72, 0x68, 0x74, 0x74, 0x70, 0x73, + 0x3a, 0x2f, 0x2f, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x73, 0x2d, 0x69, 0x6e, 0x74, 0x2e, 0x6f, 0x6e, + 0x69, 0x75, 0x64, 0x72, 0x61, 0x2e, 0x63, 0x63, + 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x2f, 0x66, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, + 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x66, 0x31, + 0x65, 0x61, 0x63, 0x39, 0x63, 0x37, 0x62, 0x64, + 0x36, 0x33, 0x34, 0x37, 0x33, 0x66, 0x66, 0x66, + 0x62, 0x31, 0x31, 0x37, 0x66, 0x39, 0x38, 0x37, + 0x33, 0x37, 0x30, 0x33, 0x65, 0x34, 0x65, 0x63, + 0x39, 0x35, 0x35, 0x39, 0x33, 0x31, 0x65, 0x32, + 0x36, 0x37, 0x66, 0x32, 0x36, 0x32, 0x36, 0x32, + 0x62, 0x30, 0x39, 0x34, 0x39, 0x62, 0x63, 0x31, + 0x36, 0x64, 0x63, 0x34, 0x39, 0x58, 0x20, 0xdf, + 0x1e, 0xac, 0x9c, 0x7b, 0xd6, 0x34, 0x73, 0xff, + 0xfb, 0x11, 0x7f, 0x98, 0x73, 0x70, 0x3e, 0x4e, + 0xc9, 0x55, 0x93, 0x1e, 0x26, 0x7f, 0x26, 0x26, + 0x2b, 0x09, 0x49, 0xbc, 0x16, 0xdc, 0x49}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is successful") { + REQUIRE(err == Decoder::Status::Error); + } + } + +/****************************************************************************/ + + WHEN("Decode the OtaUpdateCmdDown message with corrupted fields 1") + { + CommandDown command; + + /* + DA 00010100 # tag(65792) + 84 # array(4) + 50 # bytes(16) + C73CB045F9C2434585AFFA36A307BFE7"\xC7<\xB0E\xF9\xC2CE\x85\xAF\xFA6\xA3\a\xBF\xE7" + 78 72 # text(141) + 68747470733A2F2F626F617264732D69 + 6E742E6F6E69756472612E63632F7374 + 6F726167652F6669726D776172652F76 + 312F6466316561633963376264363334 + 37336666666231313766393837333730 + 33653465633935353933316532363766 + 32363236326230393439626331366463 + 3439 # "https://boards-int.oniudra.cc/storage/firmware/v1/df1eac9c7bd63473fffb117f9873703e4ec955931e267f26262b0949bc16dc49" + 1A 65DCB821 # unsigned(1708963873) + 58 20 # bytes(32) + DF1EAC9C7BD63473FFFB117F9873703E + 4EC955931E267F26262B0949BC16DC49# "\xDF\u001E\xAC\x9C{\xD64s\xFF\xFB\u0011\u007F\x98sp>N\xC9U\x93\u001E&\u007F&&+\tI\xBC\u0016\xDCI" + + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x01, 0x00, 0x84, 0x50, 0xc7, + 0x3c, 0xb0, 0x45, 0xf9, 0xc2, 0x43, 0x45, 0x85, + 0xaf, 0xfa, 0x36, 0xa3, 0x07, 0xbf, 0xe7, 0x78, + 0x72, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, + 0x2f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x2d, + 0x69, 0x6e, 0x74, 0x2e, 0x6f, 0x6e, 0x69, 0x75, + 0x64, 0x72, 0x61, 0x2e, 0x63, 0x63, 0x2f, 0x73, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x66, + 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2f, + 0x76, 0x31, 0x2f, 0x64, 0x66, 0x31, 0x65, 0x61, + 0x63, 0x39, 0x63, 0x37, 0x62, 0x64, 0x36, 0x33, + 0x34, 0x37, 0x33, 0x66, 0x66, 0x66, 0x62, 0x31, + 0x31, 0x37, 0x66, 0x39, 0x38, 0x37, 0x33, 0x37, + 0x30, 0x33, 0x65, 0x34, 0x65, 0x63, 0x39, 0x35, + 0x35, 0x39, 0x33, 0x31, 0x65, 0x32, 0x36, 0x37, + 0x66, 0x32, 0x36, 0x32, 0x36, 0x32, 0x62, 0x30, + 0x39, 0x34, 0x39, 0x62, 0x63, 0x31, 0x36, 0x64, + 0x63, 0x34, 0x39, 0x1A, 0x65, 0xDC, 0xB8, 0x21, + 0x58, 0x20, 0xdf, 0x1e, 0xac, 0x9c, 0x7b, 0xd6, + 0x34, 0x73, 0xff, 0xfb, 0x11, 0x7f, 0x98, 0x73, + 0x70, 0x3e, 0x4e, 0xc9, 0x55, 0x93, 0x1e, 0x26, + 0x7f, 0x26, 0x26, 0x2b, 0x09, 0x49, 0xbc, 0x16, + 0xdc, 0x49}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is successful") { + REQUIRE(err == Decoder::Status::Error); + } + } + +/****************************************************************************/ + + WHEN("Decode the OtaUpdateCmdDown message with corrupted fields 2") + { + CommandDown command; + + /* + DA 00010100 # tag(65792) + 84 # array(4) + 50 # bytes(16) + C73CB045F9C2434585AFFA36A307BFE7"\xC7<\xB0E\xF9\xC2CE\x85\xAF\xFA6\xA3\a\xBF\xE7" + 78 72 # text(141) + 68747470733A2F2F626F617264732D69 + 6E742E6F6E69756472612E63632F7374 + 6F726167652F6669726D776172652F76 + 312F6466316561633963376264363334 + 37336666666231313766393837333730 + 33653465633935353933316532363766 + 32363236326230393439626331366463 + 3439 # "https://boards-int.oniudra.cc/storage/firmware/v1/df1eac9c7bd63473fffb117f9873703e4ec955931e267f26262b0949bc16dc49" + 58 20 # bytes(32) + DF1EAC9C7BD63473FFFB117F9873703E + 4EC955931E267F26262B0949BC16DC49# "\xDF\u001E\xAC\x9C{\xD64s\xFF\xFB\u0011\u007F\x98sp>N\xC9U\x93\u001E&\u007F&&+\tI\xBC\u0016\xDCI" + 1A 65DCB821 # unsigned(1708963873) + + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x01, 0x00, 0x84, 0x50, 0xc7, + 0x3c, 0xb0, 0x45, 0xf9, 0xc2, 0x43, 0x45, 0x85, + 0xaf, 0xfa, 0x36, 0xa3, 0x07, 0xbf, 0xe7, 0x78, + 0x72, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, + 0x2f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x2d, + 0x69, 0x6e, 0x74, 0x2e, 0x6f, 0x6e, 0x69, 0x75, + 0x64, 0x72, 0x61, 0x2e, 0x63, 0x63, 0x2f, 0x73, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x66, + 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2f, + 0x76, 0x31, 0x2f, 0x64, 0x66, 0x31, 0x65, 0x61, + 0x63, 0x39, 0x63, 0x37, 0x62, 0x64, 0x36, 0x33, + 0x34, 0x37, 0x33, 0x66, 0x66, 0x66, 0x62, 0x31, + 0x31, 0x37, 0x66, 0x39, 0x38, 0x37, 0x33, 0x37, + 0x30, 0x33, 0x65, 0x34, 0x65, 0x63, 0x39, 0x35, + 0x35, 0x39, 0x33, 0x31, 0x65, 0x32, 0x36, 0x37, + 0x66, 0x32, 0x36, 0x32, 0x36, 0x32, 0x62, 0x30, + 0x39, 0x34, 0x39, 0x62, 0x63, 0x31, 0x36, 0x64, + 0x63, 0x34, 0x39, 0x58, 0x20, 0xdf, 0x1e, 0xac, + 0x9c, 0x7b, 0xd6, 0x34, 0x73, 0xff, 0xfb, 0x11, + 0x7f, 0x98, 0x73, 0x70, 0x3e, 0x4e, 0xc9, 0x55, + 0x93, 0x1e, 0x26, 0x7f, 0x26, 0x26, 0x2b, 0x09, + 0x49, 0xbc, 0x16, 0xdc, 0x49, 0x1A, 0x65, 0xDC, + 0xB8, 0x21}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is successful") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode the OtaBeginUp message") + { + CommandDown command; + /* + DA 00010000 # tag(65536) + 81 # array(1) + 58 20 # bytes(32) + 01020304 + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x00, 0x00, 0x81, 0x58, 0x20, + 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful - OtaBeginUp is not supported") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode the ThingBeginCmd message") + { + CommandDown command; + /* + DA 00010300 # tag(66304) + 81 # array(1) + 68 # text(8) + 7468696E675F6964 # "thing_id" + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x03, 0x00, 0x81, 0x68, 0x74, + 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful - ThingBeginCmd is not supported") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode the LastValuesBeginCmd message") + { + CommandDown command; + /* + DA 00010500 # tag(66816) + 80 # array(0) + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x05, 0x00, 0x80}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful - LastValuesBeginCmd is not supported") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode the DeviceBeginCmd message") + { + CommandDown command; + /* + DA 00010700 # tag(67328) + 81 # array(1) + 65 # text(5) + 322E302E30 # "2.0.0" + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x07, 0x00, 0x81, 0x65, 0x32, + 0x2e, 0x30, 0x2e, 0x30}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful - DeviceBeginCmd is not supported") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode the OtaProgressCmdUp message") + { + CommandDown command; + /* + DA 00010200 # tag(66048) + 84 # array(4) + 50 # bytes(16) + 000102030405060708090A0B0C0D0E0F # "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f" + E1 # primitive(1) + 20 # negative(0) + 18 64 # unsigned(100) + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x02, 0x00, 0x84, 0x50, 0x00, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xe1, + 0x20, 0x18, 0x64}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful - OtaProgressCmdUp is not supported") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode the TimezoneCommandUp message") + { + CommandDown command; + /* + DA 00010800 # tag(67584) + 80 # array(0) + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x08, 0x00, 0x80}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful - TimezoneCommandUp is not supported") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode a message with invalid CBOR tag") + { + CommandDown command; + + /* + DA ffffffff # invalid tag + 82 # array(2) + 1A 65DCB821 # unsigned(1708963873) + 1A 78ACA191 # unsigned(2024579473) + */ + + uint8_t const payload[] = {0xDA, 0xff, 0xff, 0xff, 0xff, 0x82, 0x1A, 0x65, + 0xDC, 0xB8, 0x21, 0x1A, 0x78, 0xAC, 0xA1, 0x91}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode a message not starting with a CBOR tag") + { + CommandDown command; + + /* + 82 # array(2) + 1A 65DCB821 # unsigned(1708963873) + 1A 78ACA191 # unsigned(2024579473) + */ + + uint8_t const payload[] = {0x82, 0x1A, 0x65, 0xDC, 0xB8, 0x21, 0x1A, 0x78, + 0xAC, 0xA1, 0x91}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode an invalid CBOR message") + { + CommandDown command; + + uint8_t const payload[] = {0xFF}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful") { + REQUIRE(err == Decoder::Status::Error); + } + } + +} diff --git a/extras/test/src/test_command_encode.cpp b/extras/test/src/test_command_encode.cpp new file mode 100644 index 000000000..7e31c1487 --- /dev/null +++ b/extras/test/src/test_command_encode.cpp @@ -0,0 +1,322 @@ +/* + Copyright (c) 2024 Arduino. All rights reserved. +*/ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include + +#include + +#include +#include + +/****************************************************************************** + TEST CODE + ******************************************************************************/ + +SCENARIO("Test the encoding of command messages") { + /****************************************************************************/ + + WHEN("Encode the OtaBeginUp message") + { + OtaBeginUp command; + uint8_t sha[SHA256_SIZE] = {0x01, 0x02, 0x03, 0x04}; + memcpy(command.params.sha, sha, SHA256_SIZE); + + command.c.id = CommandId::OtaBeginUpId; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + uint8_t expected_result[] = { + 0xda, 0x00, 0x01, 0x00, 0x00, 0x81, 0x58, 0x20, + 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + // Test the encoding is + // DA 00010000 # tag(65536) + // 81 # array(1) + // 58 20 # bytes(32) + // 01020304 + THEN("The encoding is successful") { + REQUIRE(err == Encoder::Status::Complete); + REQUIRE(bytes_encoded == sizeof(expected_result)); + REQUIRE(memcmp(buffer, expected_result, sizeof(expected_result)) == 0); + } + } + + + /****************************************************************************/ + + WHEN("Encode the ThingBeginCmd message") + { + ThingBeginCmd command; + String thing_id = "thing_id"; + strcpy(command.params.thing_id, thing_id.c_str()); + + command.c.id = CommandId::ThingBeginCmdId; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + uint8_t expected_result[] = { + 0xda, 0x00, 0x01, 0x03, 0x00, 0x81, 0x68, 0x74, + 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64 + }; + + // Test the encoding is + // DA 00010300 # tag(66304) + // 81 # array(1) + // 68 # text(8) + // 7468696E675F6964 # "thing_id" + + THEN("The encoding is successful") { + REQUIRE(err == Encoder::Status::Complete); + REQUIRE(bytes_encoded == sizeof(expected_result)); + REQUIRE(memcmp(buffer, expected_result, sizeof(expected_result)) == 0); + } + } + + /****************************************************************************/ + + WHEN("Encode the LastValuesBeginCmd message") + { + LastValuesBeginCmd command; + command.c.id = CommandId::LastValuesBeginCmdId; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + uint8_t expected_result[] = { + 0xda, 0x00, 0x01, 0x05, 0x00, 0x80 + }; + + // Test the encoding is + // DA 00010500 # tag(66816) + // 80 # array(0) + THEN("The encoding is successful") { + REQUIRE(err == Encoder::Status::Complete); + REQUIRE(bytes_encoded == sizeof(expected_result)); + REQUIRE(memcmp(buffer, expected_result, sizeof(expected_result)) == 0); + } + } + + /**************************************************************************/ + + WHEN("Encode the DeviceBeginCmd message") + { + DeviceBeginCmd command; + String lib_version = "2.0.0"; + strcpy(command.params.lib_version, lib_version.c_str()); + + command.c.id = CommandId::DeviceBeginCmdId; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + uint8_t expected_result[] = { + 0xda, 0x00, 0x01, 0x07, 0x00, 0x81, 0x65, 0x32, + 0x2e, 0x30, 0x2e, 0x30 + }; + + // Test the encoding is + // DA 00010700 # tag(67328) + // 81 # array(1) + // 65 # text(5) + // 322E302E30 # "2.0.0" + THEN("The encoding is successful") { + REQUIRE(err == Encoder::Status::Complete); + REQUIRE(bytes_encoded == sizeof(expected_result)); + REQUIRE(memcmp(buffer, expected_result, sizeof(expected_result)) == 0); + } + } + + /****************************************************************************/ + + WHEN("Encode the OtaProgressCmdUp message") + { + OtaProgressCmdUp command; + command.params.time = 2; + + uint8_t id[ID_SIZE] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}; + memcpy(command.params.id, id, ID_SIZE); + command.params.state = 1; + command.params.state_data = -1; + command.params.time = 100; + + command.c.id = CommandId::OtaProgressCmdUpId; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + uint8_t expected_result[] = { + 0xda, 0x00, 0x01, 0x02, 0x00, 0x84, 0x50, 0x00, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xe1, + 0x20, 0x18, 0x64 + }; + + // Test the encoding is + // DA 00010200 # tag(66048) + // 84 # array(4) + // 50 # bytes(16) + // 000102030405060708090A0B0C0D0E0F # "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f" + // E1 # primitive(1) + // 20 # negative(0) + // 18 64 # unsigned(100) + THEN("The encoding is successful") { + REQUIRE(err == Encoder::Status::Complete); + REQUIRE(bytes_encoded == sizeof(expected_result)); + REQUIRE(memcmp(buffer, expected_result, sizeof(expected_result)) == 0); + } + } + + /****************************************************************************/ + + WHEN("Encode the TimezoneCommandUp message") + { + TimezoneCommandUp command; + command.c.id = CommandId::TimezoneCommandUpId; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + uint8_t expected_result[] = { + 0xda, 0x00, 0x01, 0x08, 0x00, 0x80 + }; + + // Test the encoding is + // DA 00010800 # tag(67584) + // 80 # array(0) + THEN("The encoding is successful") { + REQUIRE(err == Encoder::Status::Complete); + REQUIRE(bytes_encoded == sizeof(expected_result)); + REQUIRE(memcmp(buffer, expected_result, sizeof(expected_result)) == 0); + } + } + + /****************************************************************************/ + + WHEN("Encode the ThingUpdateCmdId message") + { + ThingUpdateCmd command; + command.c.id = CommandId::ThingUpdateCmdId; + + String thing_id = "e4494d55-872a-4fd2-9646-92f87949394c"; + strcpy(command.params.thing_id, thing_id.c_str()); + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + THEN("The encoding is unsuccessful - ThingUpdateCmdId is not supported") { + REQUIRE(err == Encoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Encode the SetTimezoneCommand message") + { + TimezoneCommandDown command; + command.c.id = CommandId::TimezoneCommandDownId; + + command.params.offset = 1708963873; + command.params.until = 2024579473; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + THEN("The encoding is unsuccessful - SetTimezoneCommand is not supported") { + REQUIRE(err == Encoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Encode the LastValuesUpdateCmd message") + { + LastValuesUpdateCmd command; + command.c.id = CommandId::LastValuesUpdateCmdId; + + command.params.length = 13; + uint8_t last_values[13] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x10, 0x11, 0x12}; + command.params.last_values = last_values; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + THEN("The encoding is unsuccessful - LastValuesUpdateCmd is not supported") { + REQUIRE(err == Encoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Encode the OtaUpdateCmdDown message") + { + OtaUpdateCmdDown command; + command.c.id = CommandId::OtaUpdateCmdDownId; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + THEN("The encoding is unsuccessful - OtaUpdateCmdDown is not supported") { + REQUIRE(err == Encoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Encode a message with unknown command Id") + { + OtaUpdateCmdDown command; + command.c.id = CommandId::UnknownCmdId; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + THEN("The encoding is unsuccessful - UnknownCmdId is not supported") { + REQUIRE(err == Encoder::Status::Error); + } + } +} From 24de8e60ff1da07759dfcf1cc7493b3d9d49543f Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 19 Mar 2024 17:09:11 +0100 Subject: [PATCH 15/48] Extend Commands.h to include new Command protocol model --- src/message/Commands.h | 103 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 2 deletions(-) diff --git a/src/message/Commands.h b/src/message/Commands.h index 0f9cdac20..ce71f9c81 100644 --- a/src/message/Commands.h +++ b/src/message/Commands.h @@ -18,10 +18,20 @@ #include /****************************************************************************** - * TYPEDEF + * DEFINE ******************************************************************************/ -enum CommandId : uint16_t { +#define THING_ID_SIZE 37 +#define SHA256_SIZE 32 +#define URL_SIZE 256 +#define ID_SIZE 16 +#define MAX_LIB_VERSION_SIZE 10 + +/****************************************************************************** + TYPEDEF + ******************************************************************************/ + +enum CommandId: uint32_t { /* Device commands */ DeviceBeginCmdId, @@ -39,6 +49,15 @@ enum CommandId : uint16_t { /* Generic commands */ ResetCmdId, + /* OTA commands */ + OtaBeginUpId, + OtaProgressCmdUpId, + OtaUpdateCmdDownId, + + /* Timezone commands */ + TimezoneCommandUpId, + TimezoneCommandDownId, + /* Unknown command id */ UnknownCmdId }; @@ -48,3 +67,83 @@ struct Command { }; typedef Command Message; + +struct DeviceBeginCmd { + Command c; + struct { + char lib_version[MAX_LIB_VERSION_SIZE]; + } params; +}; + +struct ThingBeginCmd { + Command c; + struct { + char thing_id[THING_ID_SIZE]; + } params; +}; + +struct ThingUpdateCmd { + Command c; + struct { + char thing_id[THING_ID_SIZE]; + } params; +}; + +struct LastValuesBeginCmd { + Command c; +}; + +struct LastValuesUpdateCmd { + Command c; + struct { + uint8_t * last_values; + size_t length; + } params; +}; + +struct OtaBeginUp { + Command c; + struct { + uint8_t sha [SHA256_SIZE]; + } params; +}; + +struct OtaProgressCmdUp { + Command c; + struct { + uint8_t id[ID_SIZE]; + uint8_t state; + int32_t state_data; + uint64_t time; + } params; +}; + +struct OtaUpdateCmdDown { + Command c; + struct { + uint8_t id[ID_SIZE]; + char url[URL_SIZE]; + uint8_t initialSha256[SHA256_SIZE]; + uint8_t finalSha256[SHA256_SIZE]; + } params; +}; + +struct TimezoneCommandUp { + Command c; +}; + +struct TimezoneCommandDown { + Command c; + struct { + int32_t offset; + uint32_t until; + } params; +}; + +union CommandDown { + struct Command c; + struct OtaUpdateCmdDown otaUpdateCmdDown; + struct ThingUpdateCmd thingUpdateCmd; + struct LastValuesUpdateCmd lastValuesUpdateCmd; + struct TimezoneCommandDown timezoneCommandDown; +}; From 8f0c24367d759f0a4b1e559ece340879824d2aa1 Mon Sep 17 00:00:00 2001 From: pennam Date: Thu, 29 Feb 2024 17:25:28 +0100 Subject: [PATCH 16/48] ArduinoIoTCloudTCP: switch to messages --- src/ArduinoIoTCloudTCP.cpp | 211 +++++++++++++++++-------------------- src/ArduinoIoTCloudTCP.h | 20 ++-- 2 files changed, 107 insertions(+), 124 deletions(-) diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index e9ccd4a94..49a181521 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -43,6 +43,7 @@ #include #include "cbor/CBOREncoder.h" #include "utility/watchdog/Watchdog.h" +#include /****************************************************************************** LOCAL MODULE FUNCTIONS @@ -62,7 +63,6 @@ ArduinoIoTCloudTCP::ArduinoIoTCloudTCP() , _connection_attempt(0,0) , _message_stream(std::bind(&ArduinoIoTCloudTCP::sendMessage, this, std::placeholders::_1)) , _thing(&_message_stream) -, _thing_id_property{nullptr} , _device(&_message_stream) , _mqtt_data_buf{0} , _mqtt_data_len{0} @@ -76,8 +76,8 @@ ArduinoIoTCloudTCP::ArduinoIoTCloudTCP() , _mqttClient{nullptr} , _deviceTopicOut("") , _deviceTopicIn("") -, _shadowTopicOut("") -, _shadowTopicIn("") +, _messageTopicOut("") +, _messageTopicIn("") , _dataTopicOut("") , _dataTopicIn("") #if OTA_ENABLED @@ -181,6 +181,7 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, _mqttClient.setUsernamePassword(getDeviceId(), _password); } #endif + _mqttClient.onMessage(ArduinoIoTCloudTCP::onMessage); _mqttClient.setKeepAliveInterval(30 * 1000); _mqttClient.setConnectionTimeout(1500); @@ -188,17 +189,14 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, _deviceTopicOut = getTopic_deviceout(); _deviceTopicIn = getTopic_devicein(); - - Property* p; - p = new CloudWrapperString(_lib_version); - addPropertyToContainer(_device.getPropertyContainer(), *p, "LIB_VERSION", Permission::Read, -1); - p = new CloudWrapperString(_thing_id); - _thing_id_property = &addPropertyToContainer(_device.getPropertyContainer(), *p, "thing_id", Permission::ReadWrite, -1).writeOnDemand(); + _messageTopicIn = getTopic_messagein(); + _messageTopicOut = getTopic_messageout(); _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); @@ -322,9 +320,16 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_ConnectMqttBroker() { if (_mqttClient.connect(_brokerAddress.c_str(), _brokerPort)) { - DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s connected to %s:%d", __FUNCTION__, _brokerAddress.c_str(), _brokerPort); + /* 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); + + DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s connected to %s:%d", __FUNCTION__, _brokerAddress.c_str(), _brokerPort); return State::Connected; } @@ -439,28 +444,6 @@ void ArduinoIoTCloudTCP::handleMessage(int length) /* Topic for OTA properties and device configuration */ if (_deviceTopicIn == topic) { CBORDecoder::decode(_device.getPropertyContainer(), (uint8_t*)bytes, length); - - /* Temporary check to avoid flooding device state machine with usless messages */ - if (_thing_id_property->isDifferentFromCloud()) { - _thing_id_property->fromCloudToLocal(); - - Message message; - /* If we are attached we need first to detach */ - if (_device.isAttached()) { - detachThing(); - message = { DeviceDetachedCmdId }; - } - /* If received thing id is valid attach to the new thing */ - if (_thing_id.length()) { - attachThing(); - message = { DeviceAttachedCmdId }; - } else { - /* Send message to device state machine to inform we have received a null thing-id */ - _thing_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; - message = { DeviceRegisteredCmdId }; - } - _device.handleMessage(&message); - } } /* Topic for user input data */ @@ -468,41 +451,97 @@ void ArduinoIoTCloudTCP::handleMessage(int length) CBORDecoder::decode(_thing.getPropertyContainer(), (uint8_t*)bytes, length); } - /* Topic for sync Thing last values on connect */ - if (_shadowTopicIn == topic) { - DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s [%d] last values received", __FUNCTION__, millis()); - /* Decode last values property array */ - CBORDecoder::decode(_thing.getPropertyContainer(), (uint8_t*)bytes, length, true); - /* Unlock thing state machine waiting last values */ - Message message = { LastValuesUpdateCmdId }; - _thing.handleMessage(&message); - /* Call ArduinoIoTCloud sync user callback*/ - execCloudEventCallback(ArduinoIoTCloudEvent::SYNC); + /* Topic for device commands */ + if (_messageTopicIn == topic) { + CommandDown command; + DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s [%d] received %d bytes", __FUNCTION__, millis(), length); + CBORMessageDecoder decoder; + + size_t buffer_length = length; + if (decoder.decode((Message*)&command, bytes, buffer_length) != Decoder::Status::Error) { + DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s [%d] received command id %d", __FUNCTION__, millis(), command.c.id); + switch (command.c.id) + { + case CommandId::ThingUpdateCmdId: + { + DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s [%d] device configuration received", __FUNCTION__, millis()); + if ( _thing_id != String(command.thingUpdateCmd.params.thing_id)) { + _thing_id = String(command.thingUpdateCmd.params.thing_id); + Message message; + /* If we are attached we need first to detach */ + if (_device.isAttached()) { + detachThing(); + message = { DeviceDetachedCmdId }; + } + /* If received thing id is valid attach to the new thing */ + if (_thing_id.length()) { + attachThing(); + message = { DeviceAttachedCmdId }; + } else { + /* Send message to device state machine to inform we have received a null thing-id */ + _thing_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; + message = { DeviceRegisteredCmdId }; + } + _device.handleMessage(&message); + } + } + break; + + case CommandId::LastValuesUpdateCmdId: + { + DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s [%d] last values received", __FUNCTION__, millis()); + CBORDecoder::decode(_thing.getPropertyContainer(), + (uint8_t*)command.lastValuesUpdateCmd.params.last_values, + command.lastValuesUpdateCmd.params.length, true); + _thing.handleMessage((Message*)&command); + execCloudEventCallback(ArduinoIoTCloudEvent::SYNC); + + /* + * NOTE: in this current version properties are not properly integrated with the new paradigm of + * modeling the messages with C structs. The current CBOR library allocates an array in the heap + * thus we need to delete it after decoding it with the old CBORDecoder + */ + free(command.lastValuesUpdateCmd.params.last_values); + } + break; + + default: + break; + } + } } } void ArduinoIoTCloudTCP::sendMessage(Message * msg) { - switch (msg->id) - { - case DeviceBeginCmdId: - sendDevicePropertiesToCloud(); - break; + uint8_t data[MQTT_TRANSMIT_BUFFER_SIZE]; + size_t bytes_encoded = sizeof(data); + CBORMessageEncoder encoder; - case ThingBeginCmdId: - requestThingId(); - break; + switch (msg->id) { + case PropertiesUpdateCmdId: + return sendPropertyContainerToCloud(_dataTopicOut, + _thing.getPropertyContainer(), + _thing.getPropertyContainerIndex()); + break; - case LastValuesBeginCmdId: - requestLastValue(); - break; +#if OTA_ENABLED + case DeviceBeginCmdId: + sendDevicePropertyToCloud("OTA_CAP"); + sendDevicePropertyToCloud("OTA_ERROR"); + sendDevicePropertyToCloud("OTA_SHA256"); + break; +#endif - case PropertiesUpdateCmdId: - sendThingPropertiesToCloud(); - break; + default: + break; + } - default: - break; + if (encoder.encode(msg, data, bytes_encoded) == Encoder::Status::Complete && + bytes_encoded > 0) { + write(_messageTopicOut, data, bytes_encoded); + } else { + DEBUG_ERROR("error encoding %d", msg->id); } } @@ -526,29 +565,6 @@ void ArduinoIoTCloudTCP::sendPropertyContainerToCloud(String const topic, Proper } } -void ArduinoIoTCloudTCP::sendThingPropertiesToCloud() -{ - sendPropertyContainerToCloud(_dataTopicOut, _thing.getPropertyContainer(), _thing.getPropertyContainerIndex()); -} - -void ArduinoIoTCloudTCP::sendDevicePropertiesToCloud() -{ - PropertyContainer ro_device_property_container; - unsigned int last_device_property_index = 0; - - std::list ro_device_property_list {"LIB_VERSION", "OTA_CAP", "OTA_ERROR", "OTA_SHA256"}; - std::for_each(ro_device_property_list.begin(), - ro_device_property_list.end(), - [this, &ro_device_property_container ] (String const & name) - { - Property* p = getProperty(this->_device.getPropertyContainer(), name); - if(p != nullptr) - addPropertyToContainer(ro_device_property_container, *p, p->name(), p->isWriteableByCloud() ? Permission::ReadWrite : Permission::Read); - } - ); - sendPropertyContainerToCloud(_deviceTopicOut, ro_device_property_container, last_device_property_index); -} - #if OTA_ENABLED void ArduinoIoTCloudTCP::sendDevicePropertyToCloud(String const name) { @@ -564,26 +580,6 @@ void ArduinoIoTCloudTCP::sendDevicePropertyToCloud(String const name) } #endif -void ArduinoIoTCloudTCP::requestLastValue() -{ - // Send the getLastValues CBOR message to the cloud - // [{0: "r:m", 3: "getLastValues"}] = 81 A2 00 63 72 3A 6D 03 6D 67 65 74 4C 61 73 74 56 61 6C 75 65 73 - // Use http://cbor.me to easily generate CBOR encoding - const uint8_t CBOR_REQUEST_LAST_VALUE_MSG[] = { 0x81, 0xA2, 0x00, 0x63, 0x72, 0x3A, 0x6D, 0x03, 0x6D, 0x67, 0x65, 0x74, 0x4C, 0x61, 0x73, 0x74, 0x56, 0x61, 0x6C, 0x75, 0x65, 0x73 }; - write(_shadowTopicOut, CBOR_REQUEST_LAST_VALUE_MSG, sizeof(CBOR_REQUEST_LAST_VALUE_MSG)); -} - -void ArduinoIoTCloudTCP::requestThingId() -{ - if (!_mqttClient.subscribe(_deviceTopicIn)) - { - /* If device_id is wrong the board can't connect to the broker so this condition - * should never happen. - */ - DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not subscribe to %s", __FUNCTION__, _deviceTopicIn.c_str()); - } -} - void ArduinoIoTCloudTCP::attachThing() { @@ -595,14 +591,6 @@ void ArduinoIoTCloudTCP::attachThing() return; } - _shadowTopicIn = getTopic_shadowin(); - _shadowTopicOut = getTopic_shadowout(); - if (!_mqttClient.subscribe(_shadowTopicIn)) { - DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not subscribe to %s", __FUNCTION__, _shadowTopicIn.c_str()); - DEBUG_ERROR("Check your thing configuration, and press the reset button on your board."); - return; - } - DEBUG_INFO("Connected to Arduino IoT Cloud"); DEBUG_INFO("Thing ID: %s", getThingId().c_str()); execCloudEventCallback(ArduinoIoTCloudEvent::CONNECT); @@ -615,11 +603,6 @@ void ArduinoIoTCloudTCP::detachThing() return; } - if (!_mqttClient.unsubscribe(_shadowTopicIn)) { - DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not unsubscribe from %s", __FUNCTION__, _shadowTopicIn.c_str()); - return; - } - DEBUG_INFO("Disconnected from Arduino IoT Cloud"); execCloudEventCallback(ArduinoIoTCloudEvent::DISCONNECT); } diff --git a/src/ArduinoIoTCloudTCP.h b/src/ArduinoIoTCloudTCP.h index 1c13592df..ef1a93105 100644 --- a/src/ArduinoIoTCloudTCP.h +++ b/src/ArduinoIoTCloudTCP.h @@ -56,6 +56,9 @@ #include #endif +#include "cbor/MessageDecoder.h" +#include "cbor/MessageEncoder.h" + /****************************************************************************** CONSTANTS ******************************************************************************/ @@ -125,7 +128,6 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass TimedAttempt _connection_attempt; MessageStream _message_stream; ArduinoCloudThing _thing; - Property * _thing_id_property; ArduinoCloudDevice _device; String _brokerAddress; @@ -165,8 +167,8 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass String _deviceTopicOut; String _deviceTopicIn; - String _shadowTopicOut; - String _shadowTopicIn; + String _messageTopicOut; + String _messageTopicIn; String _dataTopicOut; String _dataTopicIn; @@ -182,8 +184,10 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass 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_shadowout() { return ( getThingId().length() == 0) ? String("") : String("/a/t/" + getThingId() + "/shadow/o"); } - inline String getTopic_shadowin () { return ( getThingId().length() == 0) ? String("") : String("/a/t/" + getThingId() + "/shadow/i"); } + + inline String getTopic_messageout() { return String("/a/d/" + getDeviceId() + "/c/up");} + inline String getTopic_messagein () { return String("/a/d/" + getDeviceId() + "/c/dw");} + inline String getTopic_dataout () { return ( getThingId().length() == 0) ? String("") : String("/a/t/" + getThingId() + "/e/o"); } inline String getTopic_datain () { return ( getThingId().length() == 0) ? String("") : String("/a/t/" + getThingId() + "/e/i"); } @@ -197,10 +201,7 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass void handleMessage(int length); void sendMessage(Message * msg); void sendPropertyContainerToCloud(String const topic, PropertyContainer & property_container, unsigned int & current_property_index); - void sendThingPropertiesToCloud(); - void sendDevicePropertiesToCloud(); - void requestLastValue(); - void requestThingId(); + void attachThing(); void detachThing(); int write(String const topic, byte const data[], int const length); @@ -208,7 +209,6 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass #if OTA_ENABLED void sendDevicePropertyToCloud(String const name); #endif - }; /****************************************************************************** From 0008b175357e1a5a0c3b5b93963c835d6e7b2b5f Mon Sep 17 00:00:00 2001 From: pennam Date: Wed, 20 Mar 2024 10:43:43 +0100 Subject: [PATCH 17/48] ArduinoIoTCloudDevice: switch to messages --- src/ArduinoIoTCloudDevice.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ArduinoIoTCloudDevice.cpp b/src/ArduinoIoTCloudDevice.cpp index f72b65588..b580d16ac 100644 --- a/src/ArduinoIoTCloudDevice.cpp +++ b/src/ArduinoIoTCloudDevice.cpp @@ -102,12 +102,12 @@ ArduinoCloudDevice::State ArduinoCloudDevice::handleInit() { ArduinoCloudDevice::State ArduinoCloudDevice::handleSendCapabilities() { /* Sends device capabilities message */ - Message message = { DeviceBeginCmdId }; - deliver(&message); + DeviceBeginCmd deviceBegin = { DeviceBeginCmdId, AIOT_CONFIG_LIB_VERSION }; + deliver(reinterpret_cast(&deviceBegin)); /* Subscribe to device topic to request */ - message = { ThingBeginCmdId }; - deliver(&message); + ThingBeginCmd thingBegin = { ThingBeginCmdId }; + deliver(reinterpret_cast(&thingBegin)); /* No device configuration received. Wait: 4s -> 8s -> 16s -> 32s -> 32s ...*/ _attachAttempt.retry(); From 52bf98d2c925340c71280388990e982bd8bfe466 Mon Sep 17 00:00:00 2001 From: pennam Date: Tue, 19 Mar 2024 10:52:32 +0100 Subject: [PATCH 18/48] BearSSLClient: allow configuration after object creation --- src/tls/BearSSLClient.cpp | 24 ++++++++++++++++++------ src/tls/BearSSLClient.h | 7 ++++++- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/tls/BearSSLClient.cpp b/src/tls/BearSSLClient.cpp index cdc58794f..a92b42078 100644 --- a/src/tls/BearSSLClient.cpp +++ b/src/tls/BearSSLClient.cpp @@ -34,18 +34,30 @@ #include "BearSSLClient.h" -extern "C" void aiotc_client_profile_init(br_ssl_client_context *cc, br_x509_minimal_context *xc, const br_x509_trust_anchor *trust_anchors, size_t trust_anchors_num); +bool BearSSLClient::_sslio_closing = false; +extern "C" void aiotc_client_profile_init(br_ssl_client_context *cc, br_x509_minimal_context *xc, const br_x509_trust_anchor *trust_anchors, size_t trust_anchors_num); -bool BearSSLClient::_sslio_closing = false; +BearSSLClient::BearSSLClient() : + _noSNI(false), + _get_time_func(nullptr) +{ + _ecKey.curve = 0; + _ecKey.x = NULL; + _ecKey.xlen = 0; + _ecCert.data = NULL; + _ecCert.data_len = 0; + _ecCertDynamic = false; +} BearSSLClient::BearSSLClient(Client* client, const br_x509_trust_anchor* myTAs, int myNumTAs, GetTimeCallbackFunc func) : _client(client), _TAs(myTAs), _numTAs(myNumTAs), _noSNI(false), - _get_time_func(func) + _get_time_func(func), + _br_ssl_client_init_function(aiotc_client_profile_init) { assert(_get_time_func != nullptr); @@ -266,8 +278,8 @@ int BearSSLClient::connectSSL(const char* host) /* Ensure this flag is cleared so we don't terminate a just starting connection. */ _sslio_closing = false; - // initialize client context with all necessary algorithms and hardcoded trust anchors. - aiotc_client_profile_init(&_sc, &_xc, _TAs, _numTAs); + // initialize client context with enabled algorithms and trust anchors + _br_ssl_client_init_function(&_sc, &_xc, _TAs, _numTAs); br_ssl_engine_set_buffers_bidi(&_sc.eng, _ibuf, sizeof(_ibuf), _obuf, sizeof(_obuf)); @@ -278,7 +290,7 @@ int BearSSLClient::connectSSL(const char* host) // ECC508 random success, add custom ECDSA vfry and EC sign br_ssl_engine_set_ecdsa(&_sc.eng, eccX08_vrfy_asn1); br_x509_minimal_set_ecdsa(&_xc, br_ssl_engine_get_ec(&_sc.eng), br_ssl_engine_get_ecdsa(&_sc.eng)); - + // enable client auth using the ECCX08 if (_ecCert.data_len && _ecKey.xlen) { br_ssl_client_set_single_ec(&_sc, &_ecCert, 1, &_ecKey, BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN, BR_KEYTYPE_EC, br_ec_get_default(), eccX08_sign_asn1); diff --git a/src/tls/BearSSLClient.h b/src/tls/BearSSLClient.h index 457ef92f0..6ea64c714 100644 --- a/src/tls/BearSSLClient.h +++ b/src/tls/BearSSLClient.h @@ -48,11 +48,14 @@ class BearSSLClient : public Client { public: BearSSLClient(Client* client, const br_x509_trust_anchor* myTAs, int myNumTAs, GetTimeCallbackFunc func); + BearSSLClient(); virtual ~BearSSLClient(); inline void setClient(Client& client) { _client = &client; } - + inline void setProfile(void(*client_init_function)(br_ssl_client_context *cc, br_x509_minimal_context *xc, const br_x509_trust_anchor *trust_anchors, size_t trustrust_anchorst_anchors_num)) { _br_ssl_client_init_function = client_init_function; } + inline void setTrustAnchors(const br_x509_trust_anchor* myTAs, int myNumTAs) { _TAs = myTAs; _numTAs = myNumTAs; } + inline void onGetTime(GetTimeCallbackFunc callback) { _get_time_func = callback;} virtual int connect(IPAddress ip, uint16_t port); virtual int connect(const char* host, uint16_t port); @@ -103,6 +106,8 @@ class BearSSLClient : public Client { unsigned char _ibuf[BEAR_SSL_CLIENT_IBUF_SIZE]; unsigned char _obuf[BEAR_SSL_CLIENT_OBUF_SIZE]; br_sslio_context _ioc; + + void (*_br_ssl_client_init_function)(br_ssl_client_context *cc, br_x509_minimal_context *xc, const br_x509_trust_anchor *trust_anchors, size_t trust_anchors_num); }; #endif /* #ifdef BOARD_HAS_ECCX08 */ From fbf03e1e49434c2ffefec579204b4ab290744e67 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 16 Apr 2024 16:29:45 +0200 Subject: [PATCH 19/48] BearSSLClient: allow clients to stop independently making _sslio_closing not static anymore, so that we are able to stop bearsslclients independently from one another --- src/tls/BearSSLClient.cpp | 23 ++++++++++++----------- src/tls/BearSSLClient.h | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/tls/BearSSLClient.cpp b/src/tls/BearSSLClient.cpp index a92b42078..1ebd78653 100644 --- a/src/tls/BearSSLClient.cpp +++ b/src/tls/BearSSLClient.cpp @@ -34,13 +34,12 @@ #include "BearSSLClient.h" -bool BearSSLClient::_sslio_closing = false; - extern "C" void aiotc_client_profile_init(br_ssl_client_context *cc, br_x509_minimal_context *xc, const br_x509_trust_anchor *trust_anchors, size_t trust_anchors_num); BearSSLClient::BearSSLClient() : _noSNI(false), - _get_time_func(nullptr) + _get_time_func(nullptr), + _sslio_closing(false) { _ecKey.curve = 0; _ecKey.x = NULL; @@ -172,7 +171,7 @@ void BearSSLClient::stop() { if (_client->connected()) { if ((br_ssl_engine_current_state(&_sc.eng) & BR_SSL_CLOSED) == 0) { - BearSSLClient::_sslio_closing = true; + _sslio_closing = true; br_sslio_close(&_ioc); } @@ -314,7 +313,7 @@ int BearSSLClient::connectSSL(const char* host) br_x509_minimal_set_time(&_xc, days, sec); // use our own socket I/O operations - br_sslio_init(&_ioc, &_sc.eng, BearSSLClient::clientRead, _client, BearSSLClient::clientWrite, _client); + br_sslio_init(&_ioc, &_sc.eng, BearSSLClient::clientRead, this, BearSSLClient::clientWrite, this); br_sslio_flush(&_ioc); @@ -335,12 +334,13 @@ int BearSSLClient::connectSSL(const char* host) int BearSSLClient::clientRead(void *ctx, unsigned char *buf, size_t len) { - if (BearSSLClient::_sslio_closing) { + BearSSLClient* bc = (BearSSLClient*)ctx; + Client* c = bc->_client; + + if(bc->_sslio_closing) { return -1; } - Client* c = (Client*)ctx; - if (!c->connected()) { return -1; } @@ -370,12 +370,13 @@ int BearSSLClient::clientRead(void *ctx, unsigned char *buf, size_t len) int BearSSLClient::clientWrite(void *ctx, const unsigned char *buf, size_t len) { - if (BearSSLClient::_sslio_closing) { + BearSSLClient* bc = (BearSSLClient*)ctx; + Client* c = bc->_client; + + if(bc->_sslio_closing) { return -1; } - Client* c = (Client*)ctx; - #ifdef DEBUGSERIAL DEBUGSERIAL.print("BearSSLClient::clientWrite - "); DEBUGSERIAL.print(len); diff --git a/src/tls/BearSSLClient.h b/src/tls/BearSSLClient.h index 6ea64c714..2979eebf4 100644 --- a/src/tls/BearSSLClient.h +++ b/src/tls/BearSSLClient.h @@ -100,7 +100,7 @@ class BearSSLClient : public Client { br_x509_certificate _ecCert; bool _ecCertDynamic; - static bool _sslio_closing; + bool _sslio_closing; br_ssl_client_context _sc; br_x509_minimal_context _xc; unsigned char _ibuf[BEAR_SSL_CLIENT_IBUF_SIZE]; From 8e6184acedc6e14d95a67f7fdf5d20d96841957a Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 16 Apr 2024 16:31:53 +0200 Subject: [PATCH 20/48] BearSSLClient: removing trailing white spaces --- src/tls/BearSSLClient.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tls/BearSSLClient.cpp b/src/tls/BearSSLClient.cpp index 1ebd78653..995718e93 100644 --- a/src/tls/BearSSLClient.cpp +++ b/src/tls/BearSSLClient.cpp @@ -196,7 +196,7 @@ uint8_t BearSSLClient::connected() BearSSLClient::operator bool() { - return (*_client); + return (*_client); } void BearSSLClient::setInsecure(SNI insecure) @@ -353,7 +353,7 @@ int BearSSLClient::clientRead(void *ctx, unsigned char *buf, size_t len) #ifdef DEBUGSERIAL DEBUGSERIAL.print("BearSSLClient::clientRead - "); DEBUGSERIAL.print(result); - DEBUGSERIAL.print(" - "); + DEBUGSERIAL.print(" - "); for (size_t i = 0; i < result; i++) { byte b = buf[i]; From 1ba3e3a88a1041b26cd776044afef8a532d88fac Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 16 Apr 2024 16:39:08 +0200 Subject: [PATCH 21/48] BearSSLClient: adding FIXME comment --- src/tls/BearSSLClient.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tls/BearSSLClient.h b/src/tls/BearSSLClient.h index 2979eebf4..32fb18727 100644 --- a/src/tls/BearSSLClient.h +++ b/src/tls/BearSSLClient.h @@ -100,6 +100,11 @@ class BearSSLClient : public Client { br_x509_certificate _ecCert; bool _ecCertDynamic; + /* FIXME By introducing _sslio_closing we are overriding the correct behaviour of SSL protocol + * where the client is require to correctly close the ssl session. In the way we use it + * we are blocking bearssl from sending any data on the underlying level, this fix requires + * further investigation in the bearssl code + */ bool _sslio_closing; br_ssl_client_context _sc; br_x509_minimal_context _xc; From fa514ea74cf8809ed2d6266093c1a32d99bd1d82 Mon Sep 17 00:00:00 2001 From: pennam Date: Tue, 19 Mar 2024 09:28:15 +0100 Subject: [PATCH 22/48] Add TLSClientMqtt --- src/tls/utility/TLSClientMqtt.cpp | 68 ++++++++++++++++++++++++++++++ src/tls/utility/TLSClientMqtt.h | 69 +++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 src/tls/utility/TLSClientMqtt.cpp create mode 100644 src/tls/utility/TLSClientMqtt.h diff --git a/src/tls/utility/TLSClientMqtt.cpp b/src/tls/utility/TLSClientMqtt.cpp new file mode 100644 index 000000000..8c6c3f529 --- /dev/null +++ b/src/tls/utility/TLSClientMqtt.cpp @@ -0,0 +1,68 @@ +/* + 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 + +#ifdef HAS_TCP + +#include "TLSClientMqtt.h" + +#if defined(BOARD_HAS_SECRET_KEY) + #include "tls/AIoTCUPCert.h" +#endif + +#if defined(BOARD_HAS_SE050) || defined(BOARD_HAS_SOFTSE) + #include "tls/AIoTCSSCert.h" +#endif + +#ifdef BOARD_HAS_ECCX08 + #include "tls/BearSSLTrustAnchors.h" + extern "C" { + void aiotc_client_profile_init(br_ssl_client_context *cc, + br_x509_minimal_context *xc, + const br_x509_trust_anchor *trust_anchors, + size_t trust_anchors_num); + unsigned long getTime(); + } +#endif + +void TLSClientMqtt::begin(ConnectionHandler & connection) { + +#if defined(BOARD_HAS_OFFLOADED_ECCX08) + /* Arduino Root CA is configured in nina-fw + * https://github.com/arduino/nina-fw/blob/master/arduino/libraries/ArduinoBearSSL/src/BearSSLTrustAnchors.h + */ +#elif defined(BOARD_HAS_ECCX08) + setClient(connection.getClient()); + setProfile(aiotc_client_profile_init); + setTrustAnchors(ArduinoIoTCloudTrustAnchor, ArduinoIoTCloudTrustAnchor_NUM); + onGetTime(getTime); +#elif defined(ARDUINO_PORTENTA_C33) + setClient(connection.getClient()); + setCACert(AIoTSSCert); +#elif defined(ARDUINO_NICLA_VISION) + appendCustomCACert(AIoTSSCert); +#elif defined(ARDUINO_EDGE_CONTROL) + appendCustomCACert(AIoTUPCert); +#elif defined(ARDUINO_UNOR4_WIFI) + /* Arduino Root CA is configured in uno-r4-wifi-usb-bridge fw >= 0.4.1 + * https://github.com/arduino/uno-r4-wifi-usb-bridge/blob/main/certificates/cacrt_all.pem + * Boards using username/password authentication relies on Starfield Class 2 CA + * also present in older firmware revisions + * https://github.com/arduino/uno-r4-wifi-usb-bridge/blob/f09ca94fdcab845b8368d4435fdac9f6999d21d2/certificates/certificates.pem#L852 + */ +#elif defined(ARDUINO_ARCH_ESP32) + setCACertBundle(x509_crt_bundle); +#elif defined(ARDUINO_ARCH_ESP8266) + setInsecure(); +#endif +} + +#endif diff --git a/src/tls/utility/TLSClientMqtt.h b/src/tls/utility/TLSClientMqtt.h new file mode 100644 index 000000000..837e76dec --- /dev/null +++ b/src/tls/utility/TLSClientMqtt.h @@ -0,0 +1,69 @@ +/* + 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 defined(BOARD_HAS_OFFLOADED_ECCX08) + /* + * Arduino MKR WiFi1010 - WiFi + * Arduino NANO 33 IoT - WiFi + */ + #include "WiFiSSLClient.h" + class TLSClientMqtt : public WiFiBearSSLClient { +#elif defined(BOARD_HAS_ECCX08) + /* + * Arduino MKR GSM 1400 + * Arduino MKR NB 1500 + * Arduino Portenta H7 + * Arduino Giga R1 + * OPTA + */ + #include + class TLSClientMqtt : public BearSSLClient { +#elif defined(ARDUINO_PORTENTA_C33) + /* + * Arduino Portenta C33 + */ + #include + class TLSClientMqtt : public SSLClient { +#elif defined(ARDUINO_NICLA_VISION) + /* + * Arduino Nicla Vision + */ + #include + class TLSClientMqtt : public WiFiSSLSE050Client { +#elif defined(ARDUINO_EDGE_CONTROL) + /* + * Arduino Edge Control + */ + #include + class TLSClientMqtt : public GSMSSLClient { +#elif defined(ARDUINO_UNOR4_WIFI) + /* + * Arduino UNO R4 WiFi + */ + #include + class TLSClientMqtt : public WiFiSSLClient { +#elif defined(BOARD_ESP) + /* + * ESP32* + * ESP82* + */ + #include + class TLSClientMqtt : public WiFiClientSecure { +#endif + +public: + void begin(ConnectionHandler & connection); + +}; From a92a57c676875cdce120ffb0f1b91b6e9a42af04 Mon Sep 17 00:00:00 2001 From: pennam Date: Mon, 25 Mar 2024 15:57:35 +0100 Subject: [PATCH 23/48] Add TLSClientOta --- src/tls/utility/TLSClientOta.cpp | 64 +++++++++++++++++++++ src/tls/utility/TLSClientOta.h | 96 ++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 src/tls/utility/TLSClientOta.cpp create mode 100644 src/tls/utility/TLSClientOta.h diff --git a/src/tls/utility/TLSClientOta.cpp b/src/tls/utility/TLSClientOta.cpp new file mode 100644 index 000000000..8aabf652e --- /dev/null +++ b/src/tls/utility/TLSClientOta.cpp @@ -0,0 +1,64 @@ +/* + 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(HAS_TCP) && OTA_ENABLED + +#include "TLSClientOta.h" + +#if defined(BOARD_HAS_SECRET_KEY) + #include "tls/AIoTCUPCert.h" +#endif + +#if defined(BOARD_HAS_SE050) || defined(BOARD_HAS_SOFTSE) + #include "tls/AIoTCSSCert.h" +#endif + +#ifdef BOARD_HAS_ECCX08 + #include "tls/BearSSLTrustAnchors.h" + extern "C" { + void aiotc_client_profile_init(br_ssl_client_context *cc, + br_x509_minimal_context *xc, + const br_x509_trust_anchor *trust_anchors, + size_t trust_anchors_num); + unsigned long getTime(); + } +#endif + +void TLSClientOta::begin(ConnectionHandler &connection) { +#if defined(BOARD_HAS_OFFLOADED_ECCX08) + /* AWS Root CAs are configured in nina-fw + * https://github.com/arduino/nina-fw/blob/master/data/roots.pem + */ +#elif defined(BOARD_HAS_ECCX08) + setClient(*getNewClient(connection.getInterface())); + setProfile(aiotc_client_profile_init); + setTrustAnchors(ArduinoIoTCloudTrustAnchor, ArduinoIoTCloudTrustAnchor_NUM); + onGetTime(getTime); +#elif defined(ARDUINO_PORTENTA_C33) + setClient(*getNewClient(connection.getInterface())); + setCACert(AIoTSSCert); +#elif defined(ARDUINO_NICLA_VISION) + appendCustomCACert(AIoTSSCert); +#elif defined(ARDUINO_EDGE_CONTROL) + appendCustomCACert(AIoTUPCert); +#elif defined(ARDUINO_UNOR4_WIFI) + /* AWS Root CAs are configured in uno-r4-wifi-usb-bridge/libraries/Arduino_ESP32_OTA + * https://github.com/arduino-libraries/Arduino_ESP32_OTA/blob/fc755e7d1d3946232107e2590662ee08d6ccdec4/src/tls/amazon_root_ca.h + */ +#elif defined(ARDUINO_ARCH_ESP32) + setCACertBundle(x509_crt_bundle); +#elif defined(ARDUINO_ARCH_ESP8266) + setInsecure(); +#endif +} + +#endif diff --git a/src/tls/utility/TLSClientOta.h b/src/tls/utility/TLSClientOta.h new file mode 100644 index 000000000..3e76433ab --- /dev/null +++ b/src/tls/utility/TLSClientOta.h @@ -0,0 +1,96 @@ +/* + 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 defined(BOARD_HAS_OFFLOADED_ECCX08) + /* + * Arduino MKR WiFi1010 - WiFi + * Arduino NANO 33 IoT - WiFi + */ + #include "WiFiSSLClient.h" + class TLSClientOta : public WiFiBearSSLClient { +#elif defined(BOARD_HAS_ECCX08) + /* + * Arduino MKR GSM 1400 + * Arduino MKR NB 1500 + * Arduino Portenta H7 + * Arduino Giga R1 + * OPTA + */ + #include + class TLSClientOta : public BearSSLClient { +#elif defined(ARDUINO_PORTENTA_C33) + /* + * Arduino Portenta C33 + */ + #include + class TLSClientOta : public SSLClient { +#elif defined(ARDUINO_NICLA_VISION) + /* + * Arduino Nicla Vision + */ + #include + class TLSClientOta : public WiFiSSLSE050Client { +#elif defined(ARDUINO_EDGE_CONTROL) + /* + * Arduino Edge Control + */ + #include + class TLSClientOta : public GSMSSLClient { +#elif defined(ARDUINO_UNOR4_WIFI) + /* + * Arduino UNO R4 WiFi + */ + #include + class TLSClientOta : public WiFiSSLClient { +#elif defined(BOARD_ESP) + /* + * ESP32* + * ESP82* + */ + #include + class TLSClientOta : public WiFiClientSecure { +#endif + +public: + void begin(ConnectionHandler & connection); + +private: + inline Client* getNewClient(NetworkAdapter net) { + switch(net) { +#ifdef BOARD_HAS_WIFI + case NetworkAdapter::WIFI: + return new WiFiClient(); +#endif // BOARD_HAS_WIFI +#ifdef BOARD_HAS_ETHERNET + case NetworkAdapter::ETHERNET: + return new EthernetClient(); +#endif // BOARD_HAS_ETHERNET +#ifdef BOARD_HAS_NB + case NetworkAdapter::NB: + return new NBClient(); +#endif // BOARD_HAS_NB +#ifdef BOARD_HAS_GSM + case NetworkAdapter::GSM: + return new GSMClient(); +#endif // BOARD_HAS_GSM +#ifdef BOARD_HAS_CATM1_NBIOT + case NetworkAdapter::CATM1: + return new GSMClient(); +#endif // BOARD_HAS_CATM1_NBIOT + default: + return nullptr; + } + } +}; From 34e8ad509fe7a2967a71ed5ffa11d2b1be61e28e Mon Sep 17 00:00:00 2001 From: pennam Date: Mon, 25 Mar 2024 15:54:19 +0100 Subject: [PATCH 24/48] BearSLL: increase input buffer size to allow file downloading --- src/AIoTC_Config.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/AIoTC_Config.h b/src/AIoTC_Config.h index f4fbd2a6a..652c37176 100644 --- a/src/AIoTC_Config.h +++ b/src/AIoTC_Config.h @@ -115,9 +115,14 @@ #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 #endif +#if defined(ARDUINO_NANO_RP2040_CONNECT) + #define BEAR_SSL_CLIENT_IBUF_SIZE (16384 + 325) // Allows download from storage API +#endif + #if defined(ARDUINO_EDGE_CONTROL) #define BOARD_HAS_SECRET_KEY #define HAS_TCP From 63b084012ac76c781630eb9233feeea2478f6255 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 17 Apr 2024 17:13:45 +0200 Subject: [PATCH 25/48] Removing x509_crt_bundle_len since not used anywhere --- src/tls/AIoTCUPCert.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tls/AIoTCUPCert.h b/src/tls/AIoTCUPCert.h index 330b8cb78..46a933d73 100644 --- a/src/tls/AIoTCUPCert.h +++ b/src/tls/AIoTCUPCert.h @@ -224,7 +224,6 @@ static const unsigned char x509_crt_bundle[] = { 0x75, 0x40, 0x60, 0x17, 0x85, 0x02, 0x55, 0x39, 0x8b, 0x7f, 0x05, 0x02, 0x03, 0x01, 0x00, 0x01 }; -unsigned int x509_crt_bundle_len = 2164; #elif defined (ARDUINO_EDGE_CONTROL) /* From 561c76703c9547312c4706d96844824daf1654f0 Mon Sep 17 00:00:00 2001 From: pennam Date: Tue, 9 Apr 2024 09:46:42 +0200 Subject: [PATCH 26/48] Add ArduinoLCC certificate inside certificate bundle --- src/tls/AIoTCUPCert.h | 157 +++++++++++++++++++++++------------------- 1 file changed, 86 insertions(+), 71 deletions(-) diff --git a/src/tls/AIoTCUPCert.h b/src/tls/AIoTCUPCert.h index 46a933d73..1e942bcb8 100644 --- a/src/tls/AIoTCUPCert.h +++ b/src/tls/AIoTCUPCert.h @@ -38,11 +38,12 @@ * https://www.amazontrust.com/repository/AmazonRootCA4.pem * https://www.amazontrust.com/repository/SFSRootCAG2.pem * https://certs.secureserver.net/repository/sf-class2-root.crt + * https://iot.arduino.cc */ static const unsigned char x509_crt_bundle[] = { - 0x00, 0x06, 0x00, 0x3b, 0x01, 0x26, 0x30, 0x39, 0x31, 0x0b, 0x30, 0x09, + 0x00, 0x07, 0x00, 0x3b, 0x01, 0x26, 0x30, 0x39, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x06, 0x41, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, @@ -151,78 +152,92 @@ static const unsigned char x509_crt_bundle[] = { 0xe0, 0xe3, 0xbd, 0x5f, 0x84, 0x62, 0xf3, 0x70, 0x64, 0x33, 0xa0, 0xcb, 0x24, 0x2f, 0x70, 0xba, 0x88, 0xa1, 0x2a, 0xa0, 0x75, 0xf8, 0x81, 0xae, 0x62, 0x06, 0xc4, 0x81, 0xdb, 0x39, 0x6e, 0x29, 0xb0, 0x1e, 0xfa, 0x2e, - 0x5c, 0x00, 0x6a, 0x01, 0x24, 0x30, 0x68, 0x31, 0x0b, 0x30, 0x09, 0x06, - 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x25, 0x30, 0x23, - 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x74, 0x61, 0x72, 0x66, - 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, - 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, - 0x32, 0x30, 0x30, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x29, 0x53, 0x74, - 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x43, 0x6c, 0x61, 0x73, - 0x73, 0x20, 0x32, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x30, 0x82, 0x01, 0x20, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, - 0x01, 0x0d, 0x00, 0x30, 0x82, 0x01, 0x08, 0x02, 0x82, 0x01, 0x01, 0x00, - 0xb7, 0x32, 0xc8, 0xfe, 0xe9, 0x71, 0xa6, 0x04, 0x85, 0xad, 0x0c, 0x11, - 0x64, 0xdf, 0xce, 0x4d, 0xef, 0xc8, 0x03, 0x18, 0x87, 0x3f, 0xa1, 0xab, - 0xfb, 0x3c, 0xa6, 0x9f, 0xf0, 0xc3, 0xa1, 0xda, 0xd4, 0xd8, 0x6e, 0x2b, - 0x53, 0x90, 0xfb, 0x24, 0xa4, 0x3e, 0x84, 0xf0, 0x9e, 0xe8, 0x5f, 0xec, - 0xe5, 0x27, 0x44, 0xf5, 0x28, 0xa6, 0x3f, 0x7b, 0xde, 0xe0, 0x2a, 0xf0, - 0xc8, 0xaf, 0x53, 0x2f, 0x9e, 0xca, 0x05, 0x01, 0x93, 0x1e, 0x8f, 0x66, - 0x1c, 0x39, 0xa7, 0x4d, 0xfa, 0x5a, 0xb6, 0x73, 0x04, 0x25, 0x66, 0xeb, - 0x77, 0x7f, 0xe7, 0x59, 0xc6, 0x4a, 0x99, 0x25, 0x14, 0x54, 0xeb, 0x26, - 0xc7, 0xf3, 0x7f, 0x19, 0xd5, 0x30, 0x70, 0x8f, 0xaf, 0xb0, 0x46, 0x2a, - 0xff, 0xad, 0xeb, 0x29, 0xed, 0xd7, 0x9f, 0xaa, 0x04, 0x87, 0xa3, 0xd4, - 0xf9, 0x89, 0xa5, 0x34, 0x5f, 0xdb, 0x43, 0x91, 0x82, 0x36, 0xd9, 0x66, - 0x3c, 0xb1, 0xb8, 0xb9, 0x82, 0xfd, 0x9c, 0x3a, 0x3e, 0x10, 0xc8, 0x3b, - 0xef, 0x06, 0x65, 0x66, 0x7a, 0x9b, 0x19, 0x18, 0x3d, 0xff, 0x71, 0x51, - 0x3c, 0x30, 0x2e, 0x5f, 0xbe, 0x3d, 0x77, 0x73, 0xb2, 0x5d, 0x06, 0x6c, - 0xc3, 0x23, 0x56, 0x9a, 0x2b, 0x85, 0x26, 0x92, 0x1c, 0xa7, 0x02, 0xb3, - 0xe4, 0x3f, 0x0d, 0xaf, 0x08, 0x79, 0x82, 0xb8, 0x36, 0x3d, 0xea, 0x9c, - 0xd3, 0x35, 0xb3, 0xbc, 0x69, 0xca, 0xf5, 0xcc, 0x9d, 0xe8, 0xfd, 0x64, - 0x8d, 0x17, 0x80, 0x33, 0x6e, 0x5e, 0x4a, 0x5d, 0x99, 0xc9, 0x1e, 0x87, - 0xb4, 0x9d, 0x1a, 0xc0, 0xd5, 0x6e, 0x13, 0x35, 0x23, 0x5e, 0xdf, 0x9b, - 0x5f, 0x3d, 0xef, 0xd6, 0xf7, 0x76, 0xc2, 0xea, 0x3e, 0xbb, 0x78, 0x0d, - 0x1c, 0x42, 0x67, 0x6b, 0x04, 0xd8, 0xf8, 0xd6, 0xda, 0x6f, 0x8b, 0xf2, - 0x44, 0xa0, 0x01, 0xab, 0x02, 0x01, 0x03, 0x00, 0x9b, 0x01, 0x26, 0x30, - 0x81, 0x98, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, - 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, - 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, - 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, - 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, + 0x5c, 0x00, 0x47, 0x00, 0x5b, 0x30, 0x45, 0x31, 0x0b, 0x30, 0x09, 0x06, + 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, + 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x41, 0x72, 0x64, 0x75, 0x69, + 0x6e, 0x6f, 0x20, 0x4c, 0x4c, 0x43, 0x20, 0x55, 0x53, 0x31, 0x0b, 0x30, + 0x09, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x02, 0x49, 0x54, 0x31, 0x10, + 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x07, 0x41, 0x72, 0x64, + 0x75, 0x69, 0x6e, 0x6f, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, + 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, + 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0x6d, 0x77, 0x6c, 0x5a, 0xcf, + 0x61, 0x1c, 0x7d, 0x44, 0x98, 0x51, 0xf2, 0x5e, 0xe1, 0x02, 0x40, 0x77, + 0xb7, 0x9c, 0xbd, 0x49, 0xa2, 0xa3, 0x8c, 0x4e, 0xab, 0x5e, 0x98, 0xac, + 0x82, 0xfc, 0x69, 0x5b, 0x44, 0x22, 0x77, 0xb4, 0x4d, 0x2e, 0x8e, 0xdf, + 0x2a, 0x71, 0xc1, 0x39, 0x6c, 0xd6, 0x39, 0x14, 0xbd, 0xd9, 0x6b, 0x18, + 0x4b, 0x4b, 0xec, 0xb3, 0xd5, 0xee, 0x42, 0x89, 0x89, 0x55, 0x22, 0x00, + 0x6a, 0x01, 0x24, 0x30, 0x68, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, + 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, - 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x3b, 0x30, - 0x39, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x32, 0x53, 0x74, 0x61, 0x72, - 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, - 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, - 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd5, 0x0c, 0x3a, 0xc4, 0x2a, - 0xf9, 0x4e, 0xe2, 0xf5, 0xbe, 0x19, 0x97, 0x5f, 0x8e, 0x88, 0x53, 0xb1, - 0x1f, 0x3f, 0xcb, 0xcf, 0x9f, 0x20, 0x13, 0x6d, 0x29, 0x3a, 0xc8, 0x0f, - 0x7d, 0x3c, 0xf7, 0x6b, 0x76, 0x38, 0x63, 0xd9, 0x36, 0x60, 0xa8, 0x9b, - 0x5e, 0x5c, 0x00, 0x80, 0xb2, 0x2f, 0x59, 0x7f, 0xf6, 0x87, 0xf9, 0x25, - 0x43, 0x86, 0xe7, 0x69, 0x1b, 0x52, 0x9a, 0x90, 0xe1, 0x71, 0xe3, 0xd8, - 0x2d, 0x0d, 0x4e, 0x6f, 0xf6, 0xc8, 0x49, 0xd9, 0xb6, 0xf3, 0x1a, 0x56, - 0xae, 0x2b, 0xb6, 0x74, 0x14, 0xeb, 0xcf, 0xfb, 0x26, 0xe3, 0x1a, 0xba, - 0x1d, 0x96, 0x2e, 0x6a, 0x3b, 0x58, 0x94, 0x89, 0x47, 0x56, 0xff, 0x25, - 0xa0, 0x93, 0x70, 0x53, 0x83, 0xda, 0x84, 0x74, 0x14, 0xc3, 0x67, 0x9e, - 0x04, 0x68, 0x3a, 0xdf, 0x8e, 0x40, 0x5a, 0x1d, 0x4a, 0x4e, 0xcf, 0x43, - 0x91, 0x3b, 0xe7, 0x56, 0xd6, 0x00, 0x70, 0xcb, 0x52, 0xee, 0x7b, 0x7d, - 0xae, 0x3a, 0xe7, 0xbc, 0x31, 0xf9, 0x45, 0xf6, 0xc2, 0x60, 0xcf, 0x13, - 0x59, 0x02, 0x2b, 0x80, 0xcc, 0x34, 0x47, 0xdf, 0xb9, 0xde, 0x90, 0x65, - 0x6d, 0x02, 0xcf, 0x2c, 0x91, 0xa6, 0xa6, 0xe7, 0xde, 0x85, 0x18, 0x49, - 0x7c, 0x66, 0x4e, 0xa3, 0x3a, 0x6d, 0xa9, 0xb5, 0xee, 0x34, 0x2e, 0xba, - 0x0d, 0x03, 0xb8, 0x33, 0xdf, 0x47, 0xeb, 0xb1, 0x6b, 0x8d, 0x25, 0xd9, - 0x9b, 0xce, 0x81, 0xd1, 0x45, 0x46, 0x32, 0x96, 0x70, 0x87, 0xde, 0x02, - 0x0e, 0x49, 0x43, 0x85, 0xb6, 0x6c, 0x73, 0xbb, 0x64, 0xea, 0x61, 0x41, - 0xac, 0xc9, 0xd4, 0x54, 0xdf, 0x87, 0x2f, 0xc7, 0x22, 0xb2, 0x26, 0xcc, - 0x9f, 0x59, 0x54, 0x68, 0x9f, 0xfc, 0xbe, 0x2a, 0x2f, 0xc4, 0x55, 0x1c, - 0x75, 0x40, 0x60, 0x17, 0x85, 0x02, 0x55, 0x39, 0x8b, 0x7f, 0x05, 0x02, - 0x03, 0x01, 0x00, 0x01 + 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x32, 0x30, + 0x30, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x29, 0x53, 0x74, 0x61, 0x72, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, + 0x32, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, + 0x79, 0x30, 0x82, 0x01, 0x20, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0d, + 0x00, 0x30, 0x82, 0x01, 0x08, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb7, 0x32, + 0xc8, 0xfe, 0xe9, 0x71, 0xa6, 0x04, 0x85, 0xad, 0x0c, 0x11, 0x64, 0xdf, + 0xce, 0x4d, 0xef, 0xc8, 0x03, 0x18, 0x87, 0x3f, 0xa1, 0xab, 0xfb, 0x3c, + 0xa6, 0x9f, 0xf0, 0xc3, 0xa1, 0xda, 0xd4, 0xd8, 0x6e, 0x2b, 0x53, 0x90, + 0xfb, 0x24, 0xa4, 0x3e, 0x84, 0xf0, 0x9e, 0xe8, 0x5f, 0xec, 0xe5, 0x27, + 0x44, 0xf5, 0x28, 0xa6, 0x3f, 0x7b, 0xde, 0xe0, 0x2a, 0xf0, 0xc8, 0xaf, + 0x53, 0x2f, 0x9e, 0xca, 0x05, 0x01, 0x93, 0x1e, 0x8f, 0x66, 0x1c, 0x39, + 0xa7, 0x4d, 0xfa, 0x5a, 0xb6, 0x73, 0x04, 0x25, 0x66, 0xeb, 0x77, 0x7f, + 0xe7, 0x59, 0xc6, 0x4a, 0x99, 0x25, 0x14, 0x54, 0xeb, 0x26, 0xc7, 0xf3, + 0x7f, 0x19, 0xd5, 0x30, 0x70, 0x8f, 0xaf, 0xb0, 0x46, 0x2a, 0xff, 0xad, + 0xeb, 0x29, 0xed, 0xd7, 0x9f, 0xaa, 0x04, 0x87, 0xa3, 0xd4, 0xf9, 0x89, + 0xa5, 0x34, 0x5f, 0xdb, 0x43, 0x91, 0x82, 0x36, 0xd9, 0x66, 0x3c, 0xb1, + 0xb8, 0xb9, 0x82, 0xfd, 0x9c, 0x3a, 0x3e, 0x10, 0xc8, 0x3b, 0xef, 0x06, + 0x65, 0x66, 0x7a, 0x9b, 0x19, 0x18, 0x3d, 0xff, 0x71, 0x51, 0x3c, 0x30, + 0x2e, 0x5f, 0xbe, 0x3d, 0x77, 0x73, 0xb2, 0x5d, 0x06, 0x6c, 0xc3, 0x23, + 0x56, 0x9a, 0x2b, 0x85, 0x26, 0x92, 0x1c, 0xa7, 0x02, 0xb3, 0xe4, 0x3f, + 0x0d, 0xaf, 0x08, 0x79, 0x82, 0xb8, 0x36, 0x3d, 0xea, 0x9c, 0xd3, 0x35, + 0xb3, 0xbc, 0x69, 0xca, 0xf5, 0xcc, 0x9d, 0xe8, 0xfd, 0x64, 0x8d, 0x17, + 0x80, 0x33, 0x6e, 0x5e, 0x4a, 0x5d, 0x99, 0xc9, 0x1e, 0x87, 0xb4, 0x9d, + 0x1a, 0xc0, 0xd5, 0x6e, 0x13, 0x35, 0x23, 0x5e, 0xdf, 0x9b, 0x5f, 0x3d, + 0xef, 0xd6, 0xf7, 0x76, 0xc2, 0xea, 0x3e, 0xbb, 0x78, 0x0d, 0x1c, 0x42, + 0x67, 0x6b, 0x04, 0xd8, 0xf8, 0xd6, 0xda, 0x6f, 0x8b, 0xf2, 0x44, 0xa0, + 0x01, 0xab, 0x02, 0x01, 0x03, 0x00, 0x9b, 0x01, 0x26, 0x30, 0x81, 0x98, + 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, + 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x07, + 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, 0x11, 0x06, + 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, 0x74, 0x73, + 0x64, 0x61, 0x6c, 0x65, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, + 0x0a, 0x13, 0x1c, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, + 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x3b, 0x30, 0x39, 0x06, + 0x03, 0x55, 0x04, 0x03, 0x13, 0x32, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, + 0x02, 0x82, 0x01, 0x01, 0x00, 0xd5, 0x0c, 0x3a, 0xc4, 0x2a, 0xf9, 0x4e, + 0xe2, 0xf5, 0xbe, 0x19, 0x97, 0x5f, 0x8e, 0x88, 0x53, 0xb1, 0x1f, 0x3f, + 0xcb, 0xcf, 0x9f, 0x20, 0x13, 0x6d, 0x29, 0x3a, 0xc8, 0x0f, 0x7d, 0x3c, + 0xf7, 0x6b, 0x76, 0x38, 0x63, 0xd9, 0x36, 0x60, 0xa8, 0x9b, 0x5e, 0x5c, + 0x00, 0x80, 0xb2, 0x2f, 0x59, 0x7f, 0xf6, 0x87, 0xf9, 0x25, 0x43, 0x86, + 0xe7, 0x69, 0x1b, 0x52, 0x9a, 0x90, 0xe1, 0x71, 0xe3, 0xd8, 0x2d, 0x0d, + 0x4e, 0x6f, 0xf6, 0xc8, 0x49, 0xd9, 0xb6, 0xf3, 0x1a, 0x56, 0xae, 0x2b, + 0xb6, 0x74, 0x14, 0xeb, 0xcf, 0xfb, 0x26, 0xe3, 0x1a, 0xba, 0x1d, 0x96, + 0x2e, 0x6a, 0x3b, 0x58, 0x94, 0x89, 0x47, 0x56, 0xff, 0x25, 0xa0, 0x93, + 0x70, 0x53, 0x83, 0xda, 0x84, 0x74, 0x14, 0xc3, 0x67, 0x9e, 0x04, 0x68, + 0x3a, 0xdf, 0x8e, 0x40, 0x5a, 0x1d, 0x4a, 0x4e, 0xcf, 0x43, 0x91, 0x3b, + 0xe7, 0x56, 0xd6, 0x00, 0x70, 0xcb, 0x52, 0xee, 0x7b, 0x7d, 0xae, 0x3a, + 0xe7, 0xbc, 0x31, 0xf9, 0x45, 0xf6, 0xc2, 0x60, 0xcf, 0x13, 0x59, 0x02, + 0x2b, 0x80, 0xcc, 0x34, 0x47, 0xdf, 0xb9, 0xde, 0x90, 0x65, 0x6d, 0x02, + 0xcf, 0x2c, 0x91, 0xa6, 0xa6, 0xe7, 0xde, 0x85, 0x18, 0x49, 0x7c, 0x66, + 0x4e, 0xa3, 0x3a, 0x6d, 0xa9, 0xb5, 0xee, 0x34, 0x2e, 0xba, 0x0d, 0x03, + 0xb8, 0x33, 0xdf, 0x47, 0xeb, 0xb1, 0x6b, 0x8d, 0x25, 0xd9, 0x9b, 0xce, + 0x81, 0xd1, 0x45, 0x46, 0x32, 0x96, 0x70, 0x87, 0xde, 0x02, 0x0e, 0x49, + 0x43, 0x85, 0xb6, 0x6c, 0x73, 0xbb, 0x64, 0xea, 0x61, 0x41, 0xac, 0xc9, + 0xd4, 0x54, 0xdf, 0x87, 0x2f, 0xc7, 0x22, 0xb2, 0x26, 0xcc, 0x9f, 0x59, + 0x54, 0x68, 0x9f, 0xfc, 0xbe, 0x2a, 0x2f, 0xc4, 0x55, 0x1c, 0x75, 0x40, + 0x60, 0x17, 0x85, 0x02, 0x55, 0x39, 0x8b, 0x7f, 0x05, 0x02, 0x03, 0x01, + 0x00, 0x01 }; #elif defined (ARDUINO_EDGE_CONTROL) From af78334d711d40c21c0c22c8d3ebc9ec9c97b5ce Mon Sep 17 00:00:00 2001 From: pennam Date: Wed, 24 Apr 2024 09:07:22 +0200 Subject: [PATCH 27/48] BearSSL Trust Anchors: remove ces.iot.arduino.cc --- src/tls/BearSSLTrustAnchors.h | 36 ++--------------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/src/tls/BearSSLTrustAnchors.h b/src/tls/BearSSLTrustAnchors.h index 802dff3a5..3646b6e8f 100644 --- a/src/tls/BearSSLTrustAnchors.h +++ b/src/tls/BearSSLTrustAnchors.h @@ -55,26 +55,7 @@ static const unsigned char TA0_EC_Q[] = { 0x42, 0x89, 0x89, 0x55, 0x22 }; -static const unsigned char TA1_DN[] = { - 0x30, 0x50, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, - 0x02, 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0A, - 0x13, 0x0E, 0x41, 0x72, 0x64, 0x75, 0x69, 0x6E, 0x6F, 0x20, 0x4C, 0x4C, - 0x43, 0x20, 0x55, 0x53, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, - 0x0B, 0x13, 0x02, 0x49, 0x54, 0x31, 0x1B, 0x30, 0x19, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x12, 0x63, 0x65, 0x73, 0x2E, 0x69, 0x6F, 0x74, 0x2E, - 0x61, 0x72, 0x64, 0x75, 0x69, 0x6E, 0x6F, 0x2E, 0x63, 0x63 -}; - -static const unsigned char TA1_EC_Q[] = { - 0x04, 0x57, 0x4A, 0xF7, 0xFB, 0x20, 0x2A, 0x1E, 0xBD, 0x98, 0xD5, 0xA8, - 0xFF, 0xD4, 0xEF, 0x7B, 0x90, 0xB9, 0x37, 0xA0, 0xB7, 0x00, 0x16, 0x09, - 0x57, 0x3C, 0xD5, 0x92, 0x42, 0xAA, 0x9F, 0x78, 0xCF, 0xD5, 0x54, 0x21, - 0xE6, 0x28, 0x89, 0x73, 0x2A, 0x4E, 0xC3, 0x9D, 0xBC, 0x10, 0x57, 0x79, - 0x91, 0x87, 0x93, 0xD8, 0xAE, 0x15, 0xA7, 0xDB, 0x79, 0x56, 0x4D, 0x5F, - 0x96, 0x8D, 0xE3, 0xDC, 0x51 -}; - -static const br_x509_trust_anchor ArduinoIoTCloudTrustAnchor[2] = { +static const br_x509_trust_anchor ArduinoIoTCloudTrustAnchor[1] = { { { (unsigned char *)TA0_DN, sizeof TA0_DN }, BR_X509_TA_CA, @@ -87,23 +68,10 @@ static const br_x509_trust_anchor ArduinoIoTCloudTrustAnchor[2] = { } } } - }, - { - { (unsigned char *)TA1_DN, sizeof TA1_DN }, - 0, - { - BR_KEYTYPE_EC, - { - .ec = { - BR_EC_secp256r1, - (unsigned char *)TA1_EC_Q, sizeof TA1_EC_Q, - } - } - } } }; -#define ArduinoIoTCloudTrustAnchor_NUM (2) +#define ArduinoIoTCloudTrustAnchor_NUM (1) #endif /* #ifdef BOARD_HAS_ECCX08 */ From a42f164d07652ddced7011a9dd74a5285d25b951 Mon Sep 17 00:00:00 2001 From: pennam Date: Wed, 24 Apr 2024 09:13:33 +0200 Subject: [PATCH 28/48] UNO R4 WiFi: remove unused certificate bundle --- src/tls/AIoTCUPCert.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tls/AIoTCUPCert.h b/src/tls/AIoTCUPCert.h index 1e942bcb8..5c933da03 100644 --- a/src/tls/AIoTCUPCert.h +++ b/src/tls/AIoTCUPCert.h @@ -24,7 +24,7 @@ ******************************************************************************/ #include -#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_UNOR4_WIFI) +#if defined(ARDUINO_ARCH_ESP32) /****************************************************************************** * CONSTANTS From 6b0883b37583f712208fe6f9612072c9d782bebe Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Thu, 18 Apr 2024 15:42:49 +0200 Subject: [PATCH 29/48] 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 baf9a4b1e..0e6c98ef9 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 8569ac107004b80099a167ac3017629105c0f406 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 12 Mar 2024 15:43:32 +0100 Subject: [PATCH 30/48] 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 c98825ea2..3664ce8c5 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 b6bd5af19f6866d0aecdda8dcc01d1844389d7cf Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Thu, 18 Apr 2024 15:39:52 +0200 Subject: [PATCH 31/48] CI: removed unused libraries --- .github/workflows/compile-examples.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 0e6c98ef9..a4e68ab0d 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -170,7 +170,6 @@ jobs: - name: arduino:mbed_portenta libraries: | - name: ArduinoECCX08 - - name: Arduino_Portenta_OTA sketch-paths: | - examples/ArduinoIoTCloud-DeferredOTA - examples/utility/Provisioning @@ -181,7 +180,6 @@ jobs: # Install mbed_nicla platform via Boards Manager - name: arduino:mbed_nicla libraries: | - - name: Arduino_Portenta_OTA sketch-paths: | - examples/ArduinoIoTCloud-DeferredOTA - examples/utility/Provisioning @@ -193,7 +191,6 @@ jobs: - name: arduino:mbed_opta libraries: | - name: ArduinoECCX08 - - name: Arduino_Portenta_OTA sketch-paths: | - examples/ArduinoIoTCloud-DeferredOTA - examples/utility/Provisioning @@ -205,7 +202,6 @@ jobs: - name: arduino:mbed_giga libraries: | - name: ArduinoECCX08 - - name: Arduino_Portenta_OTA sketch-paths: | - examples/ArduinoIoTCloud-DeferredOTA - examples/utility/Provisioning @@ -229,8 +225,6 @@ jobs: platforms: | # Install arduino_esp32 platform via Boards Manager - name: arduino:esp32 - libraries: | - - name: Arduino_ESP32_OTA sketch-paths: | - examples/ArduinoIoTCloud-DeferredOTA # Edge Control @@ -257,8 +251,6 @@ jobs: # Install ESP32 platform via Boards Manager - name: esp32:esp32 source-url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json - libraries: | - - name: Arduino_ESP32_OTA sketch-paths: | - examples/ArduinoIoTCloud-DeferredOTA From 232e25acdf0548efc46c7f8dfd613dc42be40dfa Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 21 Feb 2024 17:22:27 +0100 Subject: [PATCH 32/48] 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 e71aac5a2..000000000 --- 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 88c7229b9..f8d5fcfdd 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 e36cf27ab3bed6a50153cca5c72e8468f949f7ff Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 21 Feb 2024 17:10:46 +0100 Subject: [PATCH 33/48] 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 000000000..1a11399f6 --- /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 000000000..bf652026e --- /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 e6163740b4e78fb7752d5a4946371ec7a4014b4f Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Mon, 15 Apr 2024 12:20:22 +0200 Subject: [PATCH 34/48] 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 d0fc3d532..000000000 --- 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 a3125f717..000000000 --- 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 6258eb0635eb8967e8adb6825044fda52db22a6d Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Mon, 15 Apr 2024 12:20:57 +0200 Subject: [PATCH 35/48] 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 000000000..c1a56194a --- /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 000000000..71f78d00e --- /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 000000000..409b9154d --- /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 f8d5fcfdd..000000000 --- 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 8ac52a9895a464929ca5e473d150468a31c9f8d2 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Mon, 15 Apr 2024 12:21:31 +0200 Subject: [PATCH 36/48] 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 000000000..b659917b0 --- /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 000000000..a62b7cb21 --- /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 000000000..de698e660 --- /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 000000000..ae68d53bc --- /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 ddef0560590d30d7573e0cd0e79f5cddd794a74c Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 21 Feb 2024 17:28:04 +0100 Subject: [PATCH 37/48] 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 6ae5393313c40cb46baf34d6d0e504acdbccc4d4 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Mon, 4 Mar 2024 15:47:30 +0100 Subject: [PATCH 38/48] 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 000000000..7ac036134 --- /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 000000000..8654d2c7e --- /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 000000000..e0f6feb72 --- /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 000000000..165bb1cea --- /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 000000000..455066e37 --- /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 000000000..09093a30d --- /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 000000000..1dc307f46 --- /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 000000000..3c248dd17 --- /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 000000000..e44e7f65e --- /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 000000000..871ea58a8 --- /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 51065d1ba..000000000 --- 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 4e3c1d047..000000000 --- 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 1d7ecf3a7..000000000 --- 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 4ef214f32..000000000 --- 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 d446476c3..000000000 --- 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 b010d91d0535b8ba6de8208cd25dcd82ecba80a5 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Mon, 25 Mar 2024 13:53:21 +0100 Subject: [PATCH 39/48] 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 000000000..b1cbe7017 --- /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 455066e37..ac1021715 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 09093a30d..000000000 --- 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 86cbcab7376a38b45d06601ea048734f9ea91a29 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Thu, 29 Feb 2024 13:35:02 +0100 Subject: [PATCH 40/48] 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 e0f6feb72..5b1266f60 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 165bb1cea..dd165d27e 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 c7ed12b2a0dcf2f5c6c7bdebcb1bd46adbe98a4f Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Thu, 29 Feb 2024 13:48:08 +0100 Subject: [PATCH 41/48] 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 e44e7f65e..8119c2432 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 871ea58a8..28c70e2f1 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 7f9476cabeee969aa9c2d675a6c5307612b056c5 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Thu, 29 Feb 2024 14:02:00 +0100 Subject: [PATCH 42/48] 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 7ac036134..f52e64721 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 8654d2c7e..b904308e8 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 60d2886cfd968821af8bab135c4b519f38107193 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Thu, 29 Feb 2024 15:07:20 +0100 Subject: [PATCH 43/48] 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 1dc307f46..bc9194491 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 3c248dd17..3f448c779 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 eb6311cf0bdba394cadf2a9104f3a675ce54b13c Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Mon, 18 Mar 2024 14:49:24 +0100 Subject: [PATCH 44/48] 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 652c37176..e9c408903 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 c4b0110397ab70311ee155138467131c480a3edd Mon Sep 17 00:00:00 2001 From: pennam Date: Fri, 15 Mar 2024 11:47:55 +0100 Subject: [PATCH 45/48] 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 b580d16ac..9aedc0fb6 100644 --- a/src/ArduinoIoTCloudDevice.cpp +++ b/src/ArduinoIoTCloudDevice.cpp @@ -133,7 +133,6 @@ ArduinoCloudDevice::State ArduinoCloudDevice::handleConnected() { } return State::SendCapabilities; } - return State::Connected; } diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index 49a181521..5facf1ecb 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); @@ -505,6 +431,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; } @@ -525,14 +459,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; } @@ -565,21 +491,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() { diff --git a/src/ArduinoIoTCloudTCP.h b/src/ArduinoIoTCloudTCP.h index ef1a93105..dfaf265d6 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 76485441f229d0d8c14196aec0bc0f6b9214ec5c Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 24 Apr 2024 14:34:26 +0200 Subject: [PATCH 46/48] 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 de698e660..d96442bc3 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 ae68d53bc..8f7aaf18a 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 d79280af35ff54cfc03bf98cb3b29a2b68409a25 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 24 Apr 2024 15:02:39 +0200 Subject: [PATCH 47/48] 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 d96442bc3..d39ec0f2d 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(); } From 25e3252c4351a2f2e95efe238c0a18aa966736db Mon Sep 17 00:00:00 2001 From: pennam Date: Tue, 7 May 2024 15:41:04 +0200 Subject: [PATCH 48/48] Fix thing reconfiguration after wifi connection loss --- src/ArduinoIoTCloudTCP.cpp | 39 +++++++++++++++++++++++--------------- src/ArduinoIoTCloudTCP.h | 2 +- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index 5facf1ecb..0e964a3e1 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -391,24 +391,21 @@ void ArduinoIoTCloudTCP::handleMessage(int length) case CommandId::ThingUpdateCmdId: { DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s [%d] device configuration received", __FUNCTION__, millis()); - if ( _thing_id != String(command.thingUpdateCmd.params.thing_id)) { - _thing_id = String(command.thingUpdateCmd.params.thing_id); + String new_thing_id = String(command.thingUpdateCmd.params.thing_id); + + if (!new_thing_id.length()) { + /* Send message to device state machine to inform we have received a null thing-id */ + _thing_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; Message message; - /* If we are attached we need first to detach */ - if (_device.isAttached()) { + message = { DeviceRegisteredCmdId }; + _device.handleMessage(&message); + } else { + if (_device.isAttached() && _thing_id != new_thing_id) { detachThing(); - message = { DeviceDetachedCmdId }; } - /* If received thing id is valid attach to the new thing */ - if (_thing_id.length()) { - attachThing(); - message = { DeviceAttachedCmdId }; - } else { - /* Send message to device state machine to inform we have received a null thing-id */ - _thing_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; - message = { DeviceRegisteredCmdId }; + if (!_device.isAttached()) { + attachThing(new_thing_id); } - _device.handleMessage(&message); } } break; @@ -491,17 +488,23 @@ void ArduinoIoTCloudTCP::sendPropertyContainerToCloud(String const topic, Proper } } -void ArduinoIoTCloudTCP::attachThing() +void ArduinoIoTCloudTCP::attachThing(String thingId) { + _thing_id = thingId; _dataTopicIn = getTopic_datain(); _dataTopicOut = getTopic_dataout(); if (!_mqttClient.subscribe(_dataTopicIn)) { DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not subscribe to %s", __FUNCTION__, _dataTopicIn.c_str()); DEBUG_ERROR("Check your thing configuration, and press the reset button on your board."); + _thing_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; return; } + Message message; + message = { DeviceAttachedCmdId }; + _device.handleMessage(&message); + DEBUG_INFO("Connected to Arduino IoT Cloud"); DEBUG_INFO("Thing ID: %s", getThingId().c_str()); execCloudEventCallback(ArduinoIoTCloudEvent::CONNECT); @@ -514,6 +517,12 @@ void ArduinoIoTCloudTCP::detachThing() return; } + Message message; + message = { DeviceDetachedCmdId }; + _device.handleMessage(&message); + + _thing_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; + DEBUG_INFO("Disconnected from Arduino IoT Cloud"); execCloudEventCallback(ArduinoIoTCloudEvent::DISCONNECT); } diff --git a/src/ArduinoIoTCloudTCP.h b/src/ArduinoIoTCloudTCP.h index dfaf265d6..29dfc7543 100644 --- a/src/ArduinoIoTCloudTCP.h +++ b/src/ArduinoIoTCloudTCP.h @@ -170,7 +170,7 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass void sendMessage(Message * msg); void sendPropertyContainerToCloud(String const topic, PropertyContainer & property_container, unsigned int & current_property_index); - void attachThing(); + void attachThing(String thingId); void detachThing(); int write(String const topic, byte const data[], int const length);