diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 5eb4f66d9..d30277a07 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -194,7 +194,11 @@ jobs: - name: ArduinoECCX08 - name: Arduino_Cellular - name: Blues Wireless Notecard + - name: ArduinoBLE + - name: Arduino_KVStore + - name: Arduino_NetworkConfigurator sketch-paths: | + - examples/ArduinoIoTCloud-NetConfig - examples/ArduinoIoTCloud-DeferredOTA - examples/ArduinoIoTCloud-Notecard - examples/ArduinoIoTCloud-Schedule @@ -207,7 +211,11 @@ jobs: - name: arduino:mbed_nicla libraries: | - name: Blues Wireless Notecard + - name: ArduinoBLE + - name: Arduino_KVStore + - name: Arduino_NetworkConfigurator sketch-paths: | + - examples/ArduinoIoTCloud-NetConfig - examples/ArduinoIoTCloud-DeferredOTA - examples/ArduinoIoTCloud-Notecard - examples/ArduinoIoTCloud-Schedule @@ -222,7 +230,11 @@ jobs: - name: ArduinoBearSSL - name: ArduinoECCX08 - name: Blues Wireless Notecard + - name: ArduinoBLE + - name: Arduino_KVStore + - name: Arduino_NetworkConfigurator sketch-paths: | + - examples/ArduinoIoTCloud-NetConfig - examples/ArduinoIoTCloud-DeferredOTA - examples/ArduinoIoTCloud-Notecard - examples/ArduinoIoTCloud-Schedule @@ -237,7 +249,11 @@ jobs: - name: ArduinoBearSSL - name: ArduinoECCX08 - name: Blues Wireless Notecard + - name: ArduinoBLE + - name: Arduino_KVStore + - name: Arduino_NetworkConfigurator sketch-paths: | + - examples/ArduinoIoTCloud-NetConfig - examples/ArduinoIoTCloud-DeferredOTA - examples/ArduinoIoTCloud-Notecard - examples/ArduinoIoTCloud-Schedule @@ -251,7 +267,11 @@ jobs: libraries: | - name: Arduino_Cellular - name: Blues Wireless Notecard + - name: ArduinoBLE + - name: Arduino_KVStore + - name: Arduino_NetworkConfigurator sketch-paths: | + - examples/ArduinoIoTCloud-NetConfig - examples/ArduinoIoTCloud-Notecard - examples/ArduinoIoTCloud-Schedule - examples/utility/Provisioning @@ -263,7 +283,11 @@ jobs: - name: arduino:renesas_uno libraries: | - name: Blues Wireless Notecard + - name: ArduinoBLE + - name: Arduino_KVStore + - name: Arduino_NetworkConfigurator sketch-paths: | + - examples/ArduinoIoTCloud-NetConfig - examples/ArduinoIoTCloud-Notecard - examples/ArduinoIoTCloud-Schedule # Nano ESP32 diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29bb..000000000 diff --git a/LICENSE b/LICENSE index 4a1b324fd..f288702d2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,17 +1,3 @@ -This file includes licensing information for ArduinoIoTCloud - -Copyright (c) 2019 ARDUINO SA (www.arduino.cc) - -The software is released under the GNU General Public License, which covers the main body -of the ArduinoIoTCloud code. 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 - GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 diff --git a/README.md b/README.md index 4b248e784..93a0829a8 100644 --- a/README.md +++ b/README.md @@ -96,3 +96,9 @@ Boards can authenticate to the ArduinoIoTCloud servers using 3 methods: * `DEVICE_CERTIFICATE` and `PRIVATE_KEY`. This values are stored inside the board secure element during the device provisioning phase. Boards that are using this method are: [`MKR 1000`](https://store.arduino.cc/arduino-mkr1000-wifi), [`MKR WiFi 1010`](https://store.arduino.cc/arduino-mkr-wifi-1010), [`MKR GSM 1400`](https://store.arduino.cc/arduino-mkr-gsm-1400-1415), [`MKR NB 1500`](https://store.arduino.cc/arduino-mkr-nb-1500-1413), [`Nano 33 IoT`](https://store.arduino.cc/arduino-nano-33-iot), [`Portenta H7`](https://store.arduino.cc/portenta-h7), [`Nano RP2040 Connect`](https://store.arduino.cc/products/arduino-nano-rp2040-connect), [`Nicla Vision`](https://store.arduino.cc/products/nicla-vision), [`OPTA WiFi`](https://store.arduino.cc/products/opta-wifi), [`OPTA RS485`](https://store.arduino.cc/products/opta-rs485), [`OPTA Lite`](https://store.arduino.cc/products/opta-lite), [`GIGA R1 WiFi`](https://store.arduino.cc/products/giga-r1-wifi), [`Portenta C33`](https://store.arduino.cc/products/portenta-c33) * `APP_EUI` and `APP_KEY`. This values are defined in the `thingProperties.h` file and included in the Sketch. Boards that are using this method are: [`MKR WAN 1300/1310`](https://store.arduino.cc/mkr-wan-1310) + +### License + +The ArduinoIoTCloud library is licensed under the GNU General Public License v3.0. + +You can be released from the requirements of the above license 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 diff --git a/examples/ArduinoIoTCloud-NetConfig/ArduinoIoTCloud-NetConfig.ino b/examples/ArduinoIoTCloud-NetConfig/ArduinoIoTCloud-NetConfig.ino new file mode 100644 index 000000000..238b59f55 --- /dev/null +++ b/examples/ArduinoIoTCloud-NetConfig/ArduinoIoTCloud-NetConfig.ino @@ -0,0 +1,62 @@ +/* + This sketch demonstrates how to exchange data between your board and the Arduino IoT Cloud. + + * Connect a potentiometer (or other analog sensor) to A0. + * When the potentiometer (or sensor) value changes the data is sent to the Cloud. + * When you flip the switch in the Cloud dashboard the onboard LED lights gets turned ON or OFF. + + IMPORTANT: + This sketch works with WiFi, GSM, NB, Ethernet and Lora enabled boards supported by Arduino IoT Cloud. + On a LoRa board, if it is configured as a class A device (default and preferred option), + values from Cloud dashboard are received only after a value is sent to Cloud. + + The full list of compatible boards can be found here: + - https://github.com/arduino-libraries/ArduinoIoTCloud#what +*/ + +#include "thingProperties.h" + +#if !defined(LED_BUILTIN) && !defined(ARDUINO_NANO_ESP32) +static int const LED_BUILTIN = 2; +#endif + +void setup() { + /* Initialize serial and wait up to 5 seconds for port to open */ + Serial.begin(9600); + for(unsigned long const serialBeginTime = millis(); !Serial && (millis() - serialBeginTime <= 5000); ) { } + + /* Set the debug message level: + * - DBG_ERROR: Only show error messages + * - DBG_WARNING: Show warning and error messages + * - DBG_INFO: Show info, warning, and error messages + * - DBG_DEBUG: Show debug, info, warning, and error messages + * - DBG_VERBOSE: Show all messages + */ + setDebugMessageLevel(DBG_INFO); + + /* Configure LED pin as an output */ + pinMode(LED_BUILTIN, OUTPUT); + + /* This function takes care of connecting your sketch variables to the ArduinoIoTCloud object */ + initProperties(); + + /* Initialize Arduino IoT Cloud library */ + ArduinoCloud.begin(ArduinoIoTPreferredConnection); + + ArduinoCloud.printDebugInfo(); +} + +void loop() { + ArduinoCloud.update(); + potentiometer = analogRead(A0); + seconds = millis() / 1000; +} + +/* + * 'onLedChange' is called when the "led" property of your Thing changes + */ +void onLedChange() { + Serial.print("LED set to "); + Serial.println(led); + digitalWrite(LED_BUILTIN, led); +} diff --git a/examples/ArduinoIoTCloud-NetConfig/thingProperties.h b/examples/ArduinoIoTCloud-NetConfig/thingProperties.h new file mode 100644 index 000000000..4eba7e49c --- /dev/null +++ b/examples/ArduinoIoTCloud-NetConfig/thingProperties.h @@ -0,0 +1,49 @@ +#if !defined(ARDUINO_SAMD_MKRWIFI1010) && !defined(ARDUINO_SAMD_NANO_33_IOT) && !defined(ARDUINO_NANO_RP2040_CONNECT) \ + && !defined(ARDUINO_PORTENTA_H7_M7) && !defined(ARDUINO_NICLA_VISION) && !defined(ARDUINO_OPTA) && !defined(ARDUINO_GIGA) \ + && !defined(ARDUINO_UNOR4_WIFI) && !defined(ARDUINO_PORTENTA_C33) +#error "This example is not compatible with this board." +#endif +#include +#include +#include +#include +#include +#include + +void onLedChange(); + +bool led; +int potentiometer; +int seconds; + +GenericConnectionHandler ArduinoIoTPreferredConnection; +KVStore kvStore; +NetworkConfiguratorClass NetworkConfigurator(ArduinoIoTPreferredConnection); +BLEAgentClass BLEAgent; +SerialAgentClass SerialAgent; + +void initProperties() { + NetworkConfigurator.addAgent(BLEAgent); + NetworkConfigurator.addAgent(SerialAgent); + NetworkConfigurator.setStorage(kvStore); + + /* For changing the default reset pin uncomment and set your preferred pin. + * Use DISABLE_PIN for disabling the reset procedure. + * The pin must be in the list of digital pins usable for interrupts. + * Please refer to the Arduino documentation for more details: + * https://docs.arduino.cc/language-reference/en/functions/external-interrupts/attachInterrupt/ + */ + //NetworkConfigurator.setReconfigurePin(your_pin); + + /* Otherwise if you need to monitor the pin status changes + * you can set a custom callback function that is fired on every change + */ + //NetworkConfigurator.setPinChangedCallback(your_callback); + + ArduinoCloud.setConfigurator(NetworkConfigurator); + + ArduinoCloud.addProperty(led, Permission::Write).onUpdate(onLedChange); + ArduinoCloud.addProperty(potentiometer, Permission::Read).publishOnChange(10); + ArduinoCloud.addProperty(seconds, Permission::Read).publishOnChange(1); + +} diff --git a/examples/utility/Provisioning_2.0/CSRHandler.cpp b/examples/utility/Provisioning_2.0/CSRHandler.cpp new file mode 100644 index 000000000..1a6101e4b --- /dev/null +++ b/examples/utility/Provisioning_2.0/CSRHandler.cpp @@ -0,0 +1,428 @@ +/* + 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 "CSRHandler.h" +#include +#include +#include +#include +#include +#include +#include +#include "Arduino_DebugUtils.h" +#include + +#define RESPONSE_TIMEOUT 5000 +#define CONNECTION_RETRY_TIMEOUT_MS 2000 +#define BACKEND_INTERVAL_s 12 +#define MAX_CSR_REQUEST_INTERVAL 180000 +#define MAX_CSR_REQUEST_INTERVAL_ATTEMPTS 15 + +#ifdef COMPILE_TEST +constexpr char *server = "boards-v2.oniudra.cc"; +#else +constexpr char *server = "boards-v2.arduino.cc"; +#endif + +CSRHandlerClass::CSRHandlerClass() : + _ledFeedback{LEDFeedbackClass::getInstance()}, + _state{CSRHandlerStates::END}, + _nextRequestAt{0}, + _requestAttempt{0}, + _startWaitingResponse{0}, + _uhwid{nullptr}, + _certForCSR{nullptr}, + _connectionHandler{nullptr}, + _secureElement{nullptr}, + _tlsClient{nullptr}, + _client{nullptr}, + _fw_version{""}, + _deviceId{""}, + _issueYear{0}, + _issueMonth{0}, + _issueDay{0}, + _issueHour{0} { + memset(_serialNumber, 0, sizeof(_serialNumber)); + memset(_authorityKeyIdentifier, 0, sizeof(_authorityKeyIdentifier)); + memset(_signature, 0, sizeof(_signature)); +} + +CSRHandlerClass::~CSRHandlerClass() { + if (_certForCSR) { + delete _certForCSR; + _certForCSR = nullptr; + } + if (_client) { + delete _client; + _client = nullptr; + } + + if(_tlsClient){ + delete _tlsClient; + _tlsClient = nullptr; + } +} + +bool CSRHandlerClass::begin(ConnectionHandler &connectionHandler, SecureElement &secureElement, String &uhwid) { + if(_state != CSRHandlerStates::END) { + return true; + } + + if(uhwid == "") { + return false; + } + + _connectionHandler = &connectionHandler; + _secureElement = &secureElement; + _uhwid = &uhwid; + +#ifdef BOARD_HAS_WIFI + _fw_version = WiFi.firmwareVersion(); +#endif + if(!_tlsClient){ + _tlsClient = new TLSClientMqtt(); + } + _tlsClient->begin(*_connectionHandler); + _tlsClient->setTimeout(RESPONSE_TIMEOUT); + _client = new HttpClient(*_tlsClient, server, 443); + TimeService.begin(_connectionHandler); + _requestAttempt = 0; + _nextRequestAt = 0; + _startWaitingResponse = 0; + _state = CSRHandlerStates::BUILD_CSR; +} + +void CSRHandlerClass::end() { + if (_client) { + _client->stop(); + delete _client; + _client = nullptr; + } + + if (_certForCSR) { + delete _certForCSR; + _certForCSR = nullptr; + } + + if(_tlsClient){ + delete _tlsClient; + _tlsClient = nullptr; + } + _fw_version = ""; + _deviceId = ""; + _state = CSRHandlerStates::END; +} + +CSRHandlerClass::CSRHandlerStates CSRHandlerClass::poll() { + switch (_state) { + case CSRHandlerStates::BUILD_CSR: _state = handleBuildCSR (); break; + case CSRHandlerStates::REQUEST_SIGNATURE: _state = handleRequestSignature (); break; + case CSRHandlerStates::WAITING_RESPONSE: _state = handleWaitingResponse (); break; + case CSRHandlerStates::PARSE_RESPONSE: _state = handleParseResponse (); break; + case CSRHandlerStates::BUILD_CERTIFICATE: _state = handleBuildCertificate (); break; + case CSRHandlerStates::CERT_CREATED: _state = handleCertCreated (); break; + case CSRHandlerStates::WAITING_COMPLETE_RES: _state = handleWaitingCompleteRes(); break; + case CSRHandlerStates::COMPLETED: break; + case CSRHandlerStates::ERROR: handleError (); break; + case CSRHandlerStates::END: break; + } + + return _state; +} + +void CSRHandlerClass::updateNextRequestAt() { + uint32_t delay; + if(_requestAttempt <= MAX_CSR_REQUEST_INTERVAL_ATTEMPTS) { + delay = BACKEND_INTERVAL_s * _requestAttempt * 1000; // use linear backoff since backend has a rate limit + }else { + delay = MAX_CSR_REQUEST_INTERVAL; + } + + _nextRequestAt = millis() + delay + jitter(); +} + +uint32_t CSRHandlerClass::jitter(uint32_t base, uint32_t max) { + srand(millis()); + return base + rand() % (max - base); +} + +bool CSRHandlerClass::postRequest(const char *url, String &postData) { + if(!_client){ + _client = new HttpClient(*_tlsClient, server, 443); + } + + uint32_t ts = getTimestamp(); + if(ts == 0){ + DEBUG_WARNING("CSRH::%s Failed getting timestamp", __FUNCTION__); + return false; + } + + String token = getAIoTCloudJWT(*_secureElement, *_uhwid, ts, 1); + + _requestAttempt++; + _client->beginRequest(); + + if(_client->post(url) == 0){ + _client->sendHeader("Host", server); + _client->sendHeader("Connection", "close"); + _client->sendHeader("Content-Type", "application/json;charset=UTF-8"); + _client->sendHeader("Authorization", "Bearer " + token); + _client->sendHeader("Content-Length", postData.length()); + _client->beginBody(); + _client->print(postData); + _startWaitingResponse = millis(); + return true; + } + return false; +} + +uint32_t CSRHandlerClass::getTimestamp() { + uint8_t getTsAttempt = 0; + uint32_t ts = 0; + do{ + TimeService.sync(); + ts = TimeService.getTime(); + getTsAttempt++; + }while(ts == 0 && getTsAttempt < 3); + + return ts; +} + +CSRHandlerClass::CSRHandlerStates CSRHandlerClass::handleBuildCSR() { + if (!_certForCSR) { + _certForCSR = new ECP256Certificate(); + } + _certForCSR->begin(); + + _certForCSR->setSubjectCommonName(*_uhwid); + + if (!SElementCSR::build(*_secureElement, *_certForCSR, static_cast(SElementArduinoCloudSlot::Key), true)) { + DEBUG_ERROR("CSRH::%s Error generating CSR!", __FUNCTION__); + _ledFeedback.setMode(LEDFeedbackClass::LEDFeedbackMode::ERROR); + return CSRHandlerStates::ERROR; + } + return CSRHandlerStates::REQUEST_SIGNATURE; +} + +CSRHandlerClass::CSRHandlerStates CSRHandlerClass::handleRequestSignature() { + CSRHandlerStates nextState = _state; + + if(millis() < _nextRequestAt) { + return nextState; + } + + NetworkConnectionState connectionRes = _connectionHandler->check(); + if (connectionRes != NetworkConnectionState::CONNECTED) { + nextNetworkRetry(); + return nextState; + } + + if(!_certForCSR){ + return CSRHandlerStates::BUILD_CSR; + } + + String csr = _certForCSR->getCSRPEM(); + csr.replace("\n", "\\n"); + + String PostData = "{\"csr\":\""; + PostData += csr; + PostData += "\"}"; + DEBUG_INFO("CSRH Downloading certificate..."); + + if(postRequest("/provisioning/v1/onboarding/provision/csr", PostData)){ + nextState = CSRHandlerStates::WAITING_RESPONSE; + } else { + updateNextRequestAt(); + DEBUG_WARNING("CSRH::%s Failed sending request, retrying in %d ms", __FUNCTION__, _nextRequestAt - millis()); + } + + return nextState; +} + +CSRHandlerClass::CSRHandlerStates CSRHandlerClass::handleWaitingResponse() { + CSRHandlerStates nextState = _state; + NetworkConnectionState connectionRes = _connectionHandler->check(); + if (connectionRes != NetworkConnectionState::CONNECTED) { + nextNetworkRetry(); + _client->stop(); + return CSRHandlerStates::REQUEST_SIGNATURE; + } + + if (millis() - _startWaitingResponse > RESPONSE_TIMEOUT) { + _client->stop(); + updateNextRequestAt(); + DEBUG_WARNING("CSRH::%s CSR request timeout, retrying in %d ms", __FUNCTION__, _nextRequestAt - millis()); + nextState = CSRHandlerStates::REQUEST_SIGNATURE; + } + + + int statusCode = _client->responseStatusCode(); + if(statusCode == 200){ + nextState = CSRHandlerStates::PARSE_RESPONSE; + } else { + _client->stop(); + updateNextRequestAt(); + DEBUG_WARNING("CSRH::%s CSR request error code %d, retrying in %d ms", __FUNCTION__, statusCode ,_nextRequestAt - millis()); + nextState = CSRHandlerStates::REQUEST_SIGNATURE; + } + + return nextState; +} + +CSRHandlerClass::CSRHandlerStates CSRHandlerClass::handleParseResponse() { + String certResponse = _client->responseBody(); + _client->stop(); + + /* Parse the response in format: + * device_id|authority_key_identifier|not_before|serial|signature_asn1_x|signature_asn1_y + */ + char *response = (char *)certResponse.c_str(); + char *token[6]; + int i = 1; + token[0] = strtok(response, "|"); + for (; i < 6; i++) { + char *tok = strtok(NULL, "|"); + if(tok == NULL){ + break; + } + token[i] = tok; + } + + if(i < 6 || strlen(token[0]) != 36 || strlen(token[1]) != 40 + || strlen(token[2]) < 10 || strlen(token[3]) != 32 + || strlen(token[4]) != 64 || strlen(token[5]) != 64 + || sscanf(token[2], "%4d-%2d-%2dT%2d", &_issueYear, &_issueMonth, &_issueDay, &_issueHour) != 4){ + updateNextRequestAt(); + DEBUG_ERROR("CSRH::%s Error parsing response, retrying in %d ms", __FUNCTION__, _nextRequestAt - millis()); + return CSRHandlerStates::REQUEST_SIGNATURE; + } + + _deviceId = token[0]; + hex::decode(token[1], _authorityKeyIdentifier, sizeof(_authorityKeyIdentifier)); + hex::decode(token[3], _serialNumber, sizeof(_serialNumber)); + hex::decode(token[4], _signature, sizeof(_signature)); + hex::decode(token[5], &_signature[32], sizeof(_signature) - 32); + + return CSRHandlerStates::BUILD_CERTIFICATE; +} + +CSRHandlerClass::CSRHandlerStates CSRHandlerClass::handleBuildCertificate() { + int expireYears = 31; + + if (!SElementArduinoCloudDeviceId::write(*_secureElement, _deviceId, SElementArduinoCloudSlot::DeviceId)) { + DEBUG_ERROR("CSRH::%s Error storing device id!", __FUNCTION__); + _ledFeedback.setMode(LEDFeedbackClass::LEDFeedbackMode::ERROR); + return CSRHandlerStates::ERROR; + } + + ECP256Certificate cert; + cert.begin(); + + cert.setSubjectCommonName(_deviceId); + cert.setIssuerCountryName("US"); + cert.setIssuerOrganizationName("Arduino LLC US"); + cert.setIssuerOrganizationalUnitName("IT"); + cert.setIssuerCommonName("Arduino"); + cert.setSignature(_signature, sizeof(_signature)); + cert.setAuthorityKeyId(_authorityKeyIdentifier, sizeof(_authorityKeyIdentifier)); + cert.setSerialNumber(_serialNumber, sizeof(_serialNumber)); + cert.setIssueYear(_issueYear); + cert.setIssueMonth(_issueMonth); + cert.setIssueDay(_issueDay); + cert.setIssueHour(_issueHour); + cert.setExpireYears(expireYears); + + if (!SElementArduinoCloudCertificate::build(*_secureElement, cert, static_cast(SElementArduinoCloudSlot::Key))) { + DEBUG_ERROR("CSRH::%s Error building secureElement compressed cert!", __FUNCTION__); + _ledFeedback.setMode(LEDFeedbackClass::LEDFeedbackMode::ERROR); + return CSRHandlerStates::ERROR; + } + + if (!SElementArduinoCloudCertificate::write(*_secureElement, cert, SElementArduinoCloudSlot::CompressedCertificate)) { + DEBUG_ERROR("CSRH::%s Error storing cert!" , __FUNCTION__); + _ledFeedback.setMode(LEDFeedbackClass::LEDFeedbackMode::ERROR); + return CSRHandlerStates::ERROR; + } + + DEBUG_INFO("CSRH Certificate created!"); + _nextRequestAt = 0; + _requestAttempt = 0; + return CSRHandlerStates::CERT_CREATED; +} + +CSRHandlerClass::CSRHandlerStates CSRHandlerClass::handleCertCreated() { + CSRHandlerStates nextState = _state; + + if(millis() < _nextRequestAt) { + return nextState; + } + + NetworkConnectionState connectionRes = _connectionHandler->check(); + if (connectionRes != NetworkConnectionState::CONNECTED) { + nextNetworkRetry(); + return nextState; + } + + String PostData = "{\"wifi_fw_version\":\""; + PostData += _fw_version; + PostData += "\"}"; + if(postRequest("/provisioning/v1/onboarding/provision/complete", PostData)){ + nextState = CSRHandlerStates::WAITING_COMPLETE_RES; + } else { + updateNextRequestAt(); + DEBUG_WARNING("CSRH::%s Error sending complete request, retrying in %d ms", __FUNCTION__, _nextRequestAt - millis()); + } + + return nextState; +} + +CSRHandlerClass::CSRHandlerStates CSRHandlerClass::handleWaitingCompleteRes() { + CSRHandlerStates nextState = _state; + NetworkConnectionState connectionRes = _connectionHandler->check(); + if (connectionRes != NetworkConnectionState::CONNECTED) { + nextNetworkRetry(); + _client->stop(); + return CSRHandlerStates::CERT_CREATED; + } + + if (millis() - _startWaitingResponse > RESPONSE_TIMEOUT) { + _client->stop(); + updateNextRequestAt(); + DEBUG_WARNING("CSRH::%s Complete request timeout, retrying in %d ms", __FUNCTION__, _nextRequestAt - millis()); + nextState = CSRHandlerStates::CERT_CREATED; + } + + int statusCode = _client->responseStatusCode(); + if(statusCode == 200){ + if (_certForCSR) { + delete _certForCSR; + _certForCSR = nullptr; + } + DEBUG_INFO("CSRH Provisioning completed!"); + nextState = CSRHandlerStates::COMPLETED; + } else if (statusCode == 429 || statusCode == 503) { + updateNextRequestAt(); + nextState = CSRHandlerStates::CERT_CREATED; + } else { + DEBUG_WARNING("CSRH::%s Complete request error code %d, retrying in %d ms", __FUNCTION__, statusCode ,_nextRequestAt - millis()); + _requestAttempt = 0; + _nextRequestAt = 0; + nextState = CSRHandlerStates::REQUEST_SIGNATURE; + } + _client->stop(); + + return nextState; +} + +void CSRHandlerClass::nextNetworkRetry() { + _nextRequestAt = millis() + CONNECTION_RETRY_TIMEOUT_MS; +} + +void CSRHandlerClass::handleError() { + _ledFeedback.update(); +} diff --git a/examples/utility/Provisioning_2.0/CSRHandler.h b/examples/utility/Provisioning_2.0/CSRHandler.h new file mode 100644 index 000000000..ae5956b7e --- /dev/null +++ b/examples/utility/Provisioning_2.0/CSRHandler.h @@ -0,0 +1,74 @@ +/* + 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 +#include +#include "utility/LEDFeedback.h" +#define JITTER_BASE 0 +#define JITTER_MAX 1000 + +class CSRHandlerClass { +public: + CSRHandlerClass(); + ~CSRHandlerClass(); + enum class CSRHandlerStates { + BUILD_CSR, + REQUEST_SIGNATURE, + WAITING_RESPONSE, + PARSE_RESPONSE, + BUILD_CERTIFICATE, + CERT_CREATED, + WAITING_COMPLETE_RES, + COMPLETED, + ERROR, + END + }; + bool begin(ConnectionHandler &connectionHandler, SecureElement &secureElement, String &uhwid); + void end(); + CSRHandlerStates poll(); +private: + CSRHandlerStates _state; + unsigned long _nextRequestAt; + uint32_t _requestAttempt; + uint32_t _startWaitingResponse; + String *_uhwid; + String _fw_version; + + int _issueYear; + uint8_t _issueMonth; + uint8_t _issueDay; + uint8_t _issueHour; + byte _serialNumber[16]; + byte _authorityKeyIdentifier[20]; + byte _signature[64]; + String _deviceId; + + ECP256Certificate *_certForCSR; + ConnectionHandler *_connectionHandler; + SecureElement *_secureElement; + TLSClientMqtt *_tlsClient; + HttpClient *_client; + LEDFeedbackClass &_ledFeedback; + void updateNextRequestAt(); + void nextNetworkRetry(); + uint32_t jitter(uint32_t base = JITTER_BASE, uint32_t max = JITTER_MAX); + bool postRequest(const char *url, String &postData); + uint32_t getTimestamp(); + CSRHandlerStates handleBuildCSR(); + CSRHandlerStates handleRequestSignature(); + CSRHandlerStates handleWaitingResponse(); + CSRHandlerStates handleParseResponse(); + CSRHandlerStates handleBuildCertificate(); + CSRHandlerStates handleCertCreated(); + CSRHandlerStates handleWaitingCompleteRes(); + void handleError(); +}; diff --git a/examples/utility/Provisioning_2.0/ClaimingHandler.cpp b/examples/utility/Provisioning_2.0/ClaimingHandler.cpp new file mode 100644 index 000000000..7456f410b --- /dev/null +++ b/examples/utility/Provisioning_2.0/ClaimingHandler.cpp @@ -0,0 +1,192 @@ +/* + 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 "ClaimingHandler.h" +#include +#include "Arduino_DebugUtils.h" +#include +#include "utility/HCI.h" +#include + +extern const char *SKETCH_VERSION; + +ClaimingHandlerClass::ClaimingHandlerClass(): + _uhwid {nullptr}, + _state {ClaimingHandlerStates::END}, + _secureElement {nullptr}, + _clearStoredCredentials {nullptr}, + _agentManager { AgentsManagerClass::getInstance()}, + _ledFeedback {LEDFeedbackClass::getInstance()} { + _receivedEvent = ClaimingReqEvents::NONE; + _ts = 0; +} + +bool ClaimingHandlerClass::begin(SecureElement &secureElement, String &uhwid, ClearStoredCredentialsHandler clearStoredCredentials) { + if(_state != ClaimingHandlerStates::END) { + return true; + } + + if(uhwid == "" || clearStoredCredentials == nullptr) { + return false; + } + + if (!_agentManager.addRequestHandler(RequestType::GET_ID, getIdRequestCb)) { + return false; + } + + if (!_agentManager.addRequestHandler(RequestType::RESET, resetStoredCredRequestCb)) { + return false; + } + + if(!_agentManager.addRequestHandler(RequestType::GET_BLE_MAC_ADDRESS, getBLEMacAddressRequestCb)) { + return false; + } + + if(!_agentManager.addRequestHandler(RequestType::GET_PROVISIONING_SKETCH_VERSION, getProvSketchVersionRequestCb)) { + return false; + } + + if (!_agentManager.addReturnTimestampCallback(setTimestamp)) { + return false; + } + + _agentManager.begin(); + _uhwid = &uhwid; + _secureElement = &secureElement; + _clearStoredCredentials = clearStoredCredentials; + _state = ClaimingHandlerStates::INIT; +} + +void ClaimingHandlerClass::end() { + if(_state == ClaimingHandlerStates::END) { + return; + } + + _agentManager.removeReturnTimestampCallback(); + _agentManager.removeRequestHandler(RequestType::GET_ID); + _agentManager.removeRequestHandler(RequestType::RESET); + _agentManager.end(); + _state = ClaimingHandlerStates::END; +} + +void ClaimingHandlerClass::poll() { + if(_state == ClaimingHandlerStates::END) { + return; + } + _ledFeedback.update(); + _agentManager.update(); + + switch (_receivedEvent) { + case ClaimingReqEvents::GET_ID: getIdReqHandler (); break; + case ClaimingReqEvents::RESET: resetStoredCredReqHandler (); break; + case ClaimingReqEvents::GET_BLE_MAC_ADDRESS: getBLEMacAddressReqHandler (); break; + case ClaimingReqEvents::GET_PROV_SKETCH_VERSION: getProvSketchVersionReqHandler(); break; + } + _receivedEvent = ClaimingReqEvents::NONE; + return; +} + +void ClaimingHandlerClass::getIdReqHandler() { + if (_ts != 0) { + byte _uhwidBytes[32]; + hex::decode(_uhwid->c_str(), _uhwidBytes, _uhwid->length()); + //Send UHWID + ProvisioningOutputMessage idMsg = {MessageOutputType::UHWID}; + idMsg.m.uhwid = _uhwidBytes; + _agentManager.sendMsg(idMsg); + + String token = getAIoTCloudJWT(*_secureElement, *_uhwid, _ts, 1); + if (token == "") { + DEBUG_ERROR("CH::%s Error: token not created", __FUNCTION__); + sendStatus(StatusMessage::ERROR); + return; + } + + //Send JWT + ProvisioningOutputMessage jwtMsg = {MessageOutputType::JWT}; + jwtMsg.m.jwt = token.c_str(); + _agentManager.sendMsg(jwtMsg); + _ts = 0; + } else { + DEBUG_ERROR("CH::%s Error: timestamp not provided" , __FUNCTION__); + sendStatus(StatusMessage::PARAMS_NOT_FOUND); + } +} + +void ClaimingHandlerClass::resetStoredCredReqHandler() { + if( !_clearStoredCredentials()){ + DEBUG_ERROR("CH::%s Error: reset stored credentials failed", __FUNCTION__); + sendStatus(StatusMessage::ERROR); + } else { + sendStatus(StatusMessage::RESET_COMPLETED); + } + +} + +void ClaimingHandlerClass::getBLEMacAddressReqHandler() { + uint8_t mac[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + bool activated = false; + ConfiguratorAgent * connectedAgent = _agentManager.getConnectedAgent(); + if(!_agentManager.isAgentEnabled(ConfiguratorAgent::AgentTypes::BLE) || (connectedAgent != nullptr && + connectedAgent->getAgentType() != ConfiguratorAgent::AgentTypes::BLE)) { + activated = true; + BLE.begin(); + } + + HCI.readBdAddr(mac); + + for(int i = 0; i < 3; i++){ + uint8_t byte = mac[i]; + mac[i] = mac[5-i]; + mac[5-i] = byte; + } + if (activated) { + BLE.end(); + } + + ProvisioningOutputMessage outputMsg; + outputMsg.type = MessageOutputType::BLE_MAC_ADDRESS; + outputMsg.m.BLEMacAddress = mac; + _agentManager.sendMsg(outputMsg); +} + +void ClaimingHandlerClass::getProvSketchVersionReqHandler() { + ProvisioningOutputMessage outputMsg; + outputMsg.type = MessageOutputType::PROV_SKETCH_VERSION; + outputMsg.m.provSketchVersion = SKETCH_VERSION; + _agentManager.sendMsg(outputMsg); +} + +void ClaimingHandlerClass::getIdRequestCb() { + DEBUG_VERBOSE("CH Get ID request received"); + _receivedEvent = ClaimingReqEvents::GET_ID; +} +void ClaimingHandlerClass::setTimestamp(uint64_t ts) { + _ts = ts; +} + +void ClaimingHandlerClass::resetStoredCredRequestCb() { + DEBUG_VERBOSE("CH Reset stored credentials request received"); + _receivedEvent = ClaimingReqEvents::RESET; +} + +void ClaimingHandlerClass::getBLEMacAddressRequestCb() { + DEBUG_VERBOSE("CH Get BLE MAC address request received"); + _receivedEvent = ClaimingReqEvents::GET_BLE_MAC_ADDRESS; +} + +void ClaimingHandlerClass::getProvSketchVersionRequestCb() { + DEBUG_VERBOSE("CH Get provisioning sketch version request received"); + _receivedEvent = ClaimingReqEvents::GET_PROV_SKETCH_VERSION; +} + +bool ClaimingHandlerClass::sendStatus(StatusMessage msg) { + ProvisioningOutputMessage statusMsg = { MessageOutputType::STATUS, { msg } }; + return _agentManager.sendMsg(statusMsg); +} diff --git a/examples/utility/Provisioning_2.0/ClaimingHandler.h b/examples/utility/Provisioning_2.0/ClaimingHandler.h new file mode 100644 index 000000000..77f2ebea6 --- /dev/null +++ b/examples/utility/Provisioning_2.0/ClaimingHandler.h @@ -0,0 +1,53 @@ +/* + 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 "Arduino.h" +#include "configuratorAgents/AgentsManager.h" +#include +#include "utility/LEDFeedback.h" + +typedef bool (*ClearStoredCredentialsHandler)(); +class ClaimingHandlerClass { +public: + ClaimingHandlerClass(); + bool begin(SecureElement &secureElement, String &uhwid, ClearStoredCredentialsHandler clearStoredCredentials); + void end(); + void poll(); +private: + String *_uhwid; + enum class ClaimingHandlerStates { + INIT, + END + }; + enum class ClaimingReqEvents { NONE, + GET_ID, + RESET, + GET_BLE_MAC_ADDRESS, + GET_PROV_SKETCH_VERSION}; + static inline ClaimingReqEvents _receivedEvent; + ClaimingHandlerStates _state; + AgentsManagerClass &_agentManager; + LEDFeedbackClass &_ledFeedback; + static inline uint64_t _ts; + SecureElement *_secureElement; + + bool sendStatus(StatusMessage msg); + /* Commands handlers */ + void getIdReqHandler(); + void resetStoredCredReqHandler(); + void getBLEMacAddressReqHandler(); + void getProvSketchVersionReqHandler(); + ClearStoredCredentialsHandler _clearStoredCredentials; + /* Callbacks for receiving commands */ + static void getIdRequestCb(); + static void setTimestamp(uint64_t ts); + static void resetStoredCredRequestCb(); + static void getBLEMacAddressRequestCb(); + static void getProvSketchVersionRequestCb(); +}; diff --git a/examples/utility/Provisioning_2.0/Provisioning_2.0.ino b/examples/utility/Provisioning_2.0/Provisioning_2.0.ino new file mode 100644 index 000000000..3ab02a278 --- /dev/null +++ b/examples/utility/Provisioning_2.0/Provisioning_2.0.ino @@ -0,0 +1,228 @@ +/* + 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 "thingProperties.h" +#include "CSRHandler.h" +#include "ClaimingHandler.h" +#include "SecretsHelper.h" +#include +#include +#include +#include +#include "utility/LEDFeedback.h" + +const char *SKETCH_VERSION = "0.1.0"; + +enum class DeviceState { + HARDWARE_CHECK, + BEGIN, + NETWORK_CONFIG, + CSR, + BEGIN_CLOUD, + RUN, + ERROR + }; + +DeviceState _state = DeviceState::HARDWARE_CHECK; +SecureElement secureElement; + +String uhwid = ""; +bool resetEvent = false; + +CSRHandlerClass CSRHandler; +ClaimingHandlerClass ClaimingHandler; + +bool clearStoredCredentials() { + const uint8_t empty[4] = {0x00,0x00,0x00,0x00}; + if(!NetworkConfigurator.resetStoredConfiguration() || \ + !secureElement.writeSlot(static_cast(SElementArduinoCloudSlot::DeviceId), (byte*)empty, sizeof(empty)) || \ + !secureElement.writeSlot(static_cast(SElementArduinoCloudSlot::CompressedCertificate), (byte*)empty, sizeof(empty))) { + return false; + } + + ArduinoCloud.disconnect(); + resetEvent = true; + return true; + } + +void setup() { + Serial.begin(9600); + + delay(1500); + + setDebugMessageLevel(4); + + initProperties(); + AgentsManagerClass::getInstance().begin(); + LEDFeedbackClass::getInstance().begin(); + DEBUG_INFO("Starting Provisioning"); +} + +void sendStatus(StatusMessage msg) { + ProvisioningOutputMessage outMsg = { MessageOutputType::STATUS, { msg } }; + AgentsManagerClass::getInstance().sendMsg(outMsg); +} + +DeviceState handleHardwareCheck() { + // Init the secure element + if(!secureElement.begin()) { + DEBUG_ERROR("Sketch: Error during secureElement begin!"); + LEDFeedbackClass::getInstance().setMode(LEDFeedbackClass::LEDFeedbackMode::ERROR); + sendStatus(StatusMessage::HW_ERROR_SE_BEGIN); + return DeviceState::ERROR; + } + + if (!secureElement.locked()) { + if (!secureElement.writeConfiguration()) { + DEBUG_ERROR("Sketch: Writing secureElement configuration failed!"); + LEDFeedbackClass::getInstance().setMode(LEDFeedbackClass::LEDFeedbackMode::ERROR); + sendStatus(StatusMessage::HW_ERROR_SE_CONFIG); + return DeviceState::ERROR; + } + + if (!secureElement.lock()) { + DEBUG_ERROR("Sketch: Locking secureElement configuration failed!"); + LEDFeedbackClass::getInstance().setMode(LEDFeedbackClass::LEDFeedbackMode::ERROR); + sendStatus(StatusMessage::HW_ERROR_SE_LOCK); + return DeviceState::ERROR; + } + DEBUG_INFO("secureElement locked successfully"); + } + + FlashFormatter flashFormatter; + // Check if the board storage is properly formatted + if(!flashFormatter.checkAndFormatPartition()) { + DEBUG_ERROR("Sketch: Error partitioning storage"); + LEDFeedbackClass::getInstance().setMode(LEDFeedbackClass::LEDFeedbackMode::ERROR); + sendStatus(StatusMessage::FAIL_TO_PARTITION_STORAGE); + return DeviceState::ERROR; + } + + return DeviceState::BEGIN; +} + +DeviceState handleBegin() { + uhwid = GetUHWID(); + if(uhwid == ""){ + DEBUG_ERROR("Sketch: Error getting UHWID"); + LEDFeedbackClass::getInstance().setMode(LEDFeedbackClass::LEDFeedbackMode::ERROR); + sendStatus(StatusMessage::ERROR_GENERATING_UHWID); + return DeviceState::ERROR; + } + // Scan the network options + NetworkConfigurator.scanNetworkOptions(); + NetworkConfigurator.begin(); + ClaimingHandler.begin(secureElement, uhwid, clearStoredCredentials); + DEBUG_INFO("BLE Available"); + return DeviceState::NETWORK_CONFIG; +} + +DeviceState handleNetworkConfig() { + ClaimingHandler.poll(); + if(resetEvent){ + resetEvent = false; + } + NetworkConfiguratorStates s = NetworkConfigurator.update(); + + DeviceState nextState = _state; + if (s == NetworkConfiguratorStates::CONFIGURED) { + String deviceId = ""; + SElementArduinoCloudDeviceId::read(secureElement, deviceId, SElementArduinoCloudSlot::DeviceId); + + if (deviceId == "") { + CSRHandler.begin(ArduinoIoTPreferredConnection, secureElement, uhwid); + nextState = DeviceState::CSR; + } else { + nextState = DeviceState::BEGIN_CLOUD; + } + } + return nextState; +} + +DeviceState handleCSR() { + NetworkConfigurator.update(); + ClaimingHandler.poll(); + if(resetEvent) { + resetEvent = false; + CSRHandler.end(); + return DeviceState::NETWORK_CONFIG; + } + + DeviceState nextState = _state; + + CSRHandlerClass::CSRHandlerStates res = CSRHandler.poll(); + if (res == CSRHandlerClass::CSRHandlerStates::COMPLETED) { + CSRHandler.end(); + nextState = DeviceState::BEGIN_CLOUD; + } + + return nextState; +} + +DeviceState handleBeginCloud() { + // Close the connection to the peer (App mobile, FE, etc) + NetworkConfigurator.disconnectAgent(); + // Close the BLE connectivity + if (NetworkConfigurator.isAgentEnabled(ConfiguratorAgent::AgentTypes::BLE)) { + NetworkConfigurator.enableAgent(ConfiguratorAgent::AgentTypes::BLE, false); + } + // Connect to Arduino IoT Cloud +#ifdef COMPILE_TEST + ArduinoCloud.begin(ArduinoIoTPreferredConnection, false, "mqtts-sa.iot.oniudra.cc"); +#else + ArduinoCloud.begin(ArduinoIoTPreferredConnection); +#endif + ArduinoCloud.printDebugInfo(); + + return DeviceState::RUN; +} + +void cloudConnectedHandler(bool connected) { + static bool _status = false; + if(connected != _status){ + _status = connected; + if(connected){ + LEDFeedbackClass::getInstance().setMode(LEDFeedbackClass::LEDFeedbackMode::CONNECTED_TO_CLOUD); + } else { + LEDFeedbackClass::getInstance().setMode(LEDFeedbackClass::LEDFeedbackMode::NONE); + } + } +} + +DeviceState handleRun() { + ClaimingHandler.poll(); + if(resetEvent) { + resetEvent = false; + return DeviceState::NETWORK_CONFIG; + } + + DeviceState nextState = _state; + ArduinoCloud.update(); + + cloudConnectedHandler(ArduinoCloud.connected()); + + return nextState; +} + +DeviceState handleError() { + LEDFeedbackClass::getInstance().update(); + AgentsManagerClass::getInstance().update(); + return DeviceState::ERROR; +} + +void loop() { + switch (_state) { + case DeviceState::HARDWARE_CHECK: _state = handleHardwareCheck(); break; + case DeviceState::BEGIN: _state = handleBegin (); break; + case DeviceState::NETWORK_CONFIG : _state = handleNetworkConfig(); break; + case DeviceState::CSR: _state = handleCSR (); break; + case DeviceState::BEGIN_CLOUD: _state = handleBeginCloud (); break; + case DeviceState::RUN: _state = handleRun (); break; + case DeviceState::ERROR: _state = handleError (); break; + default: break; + } +} diff --git a/examples/utility/Provisioning_2.0/SecretsHelper.h b/examples/utility/Provisioning_2.0/SecretsHelper.h new file mode 100644 index 000000000..bdb6354d1 --- /dev/null +++ b/examples/utility/Provisioning_2.0/SecretsHelper.h @@ -0,0 +1,20 @@ +/* + 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 + +inline String GetUHWID() { + UniqueHWId Id; + if (Id.begin()) { + return Id.get(); + } + return ""; +} diff --git a/examples/utility/Provisioning_2.0/thingProperties.h b/examples/utility/Provisioning_2.0/thingProperties.h new file mode 100644 index 000000000..7f51fa6ca --- /dev/null +++ b/examples/utility/Provisioning_2.0/thingProperties.h @@ -0,0 +1,32 @@ +/* + 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/. +*/ +#if defined(ARDUINO_SAMD_MKRGSM1400) || defined(ARDUINO_SAMD_MKRNB1500) || defined(ARDUINO_SAMD_MKRWAN1300) || defined(ARDUINO_SAMD_MKRWAN1310) +#error "Board not supported for Provisioning 2.0" +#endif + +#include +#include +#include +#include "configuratorAgents/agents/BLEAgent.h" +#include "configuratorAgents/agents/SerialAgent.h" + +GenericConnectionHandler ArduinoIoTPreferredConnection; +KVStore kvStore; +BLEAgentClass BLEAgent; +SerialAgentClass SerialAgent; +NetworkConfiguratorClass NetworkConfigurator(ArduinoIoTPreferredConnection); + +void initProperties() { + + NetworkConfigurator.addAgent(BLEAgent); + NetworkConfigurator.addAgent(SerialAgent); + NetworkConfigurator.setStorage(kvStore); + ArduinoCloud.setConfigurator(NetworkConfigurator); +} + + diff --git a/extras/test/CMakeLists.txt b/extras/test/CMakeLists.txt index 5435dfcf1..dfe322324 100644 --- a/extras/test/CMakeLists.txt +++ b/extras/test/CMakeLists.txt @@ -45,6 +45,11 @@ target_include_directories( ${cloudutils_SOURCE_DIR}/src/cbor ) +target_include_directories( + cloudutils INTERFACE + ${cloudutils_SOURCE_DIR}/src/cbor/utils +) + target_include_directories( cloudutils INTERFACE ${cloudutils_SOURCE_DIR}/src/interfaces diff --git a/extras/test/src/test_command_decode.cpp b/extras/test/src/test_command_decode.cpp index 8d75e6c90..1d47c99ee 100644 --- a/extras/test/src/test_command_decode.cpp +++ b/extras/test/src/test_command_decode.cpp @@ -667,6 +667,32 @@ SCENARIO("Test the decoding of command messages") { } } +/****************************************************************************/ + + WHEN("Decode the OtaUpdateCmdDown message without url field") + { + CommandDown command; + + /* + DA 00010100 # tag(65792) + 80 # array(1) + 50 # bytes(16) + C73CB045F9C2434585AFFA36A307BFE7"\xC7<\xB0E\xF9\xC2CE\x85\xAF\xFA6\xA3\a\xBF\xE7" + + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x01, 0x00, 0x81, 0x50, 0xc7, + 0x3c, 0xb0, 0x45, 0xf9, 0xc2, 0x43, 0x45, 0x85, + 0xaf, 0xfa, 0x36, 0xa3, 0x07, 0xbf, 0xe7}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + MessageDecoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful") { + REQUIRE(err == MessageDecoder::Status::Error); + } + } + /****************************************************************************/ WHEN("Decode the OtaBeginUp message") diff --git a/library.properties b/library.properties index 22e037e17..7ab62ce5c 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ArduinoIoTCloud -version=2.5.1 +version=2.6.1 author=Arduino maintainer=Arduino sentence=This library allows connecting to the Arduino IoT Cloud service. @@ -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,stm32 includes=ArduinoIoTCloud.h -depends=Arduino_ConnectionHandler,Arduino_DebugUtils,Arduino_SecureElement,ArduinoMqttClient,ArduinoECCX08,RTCZero,Adafruit SleepyDog Library,ArduinoHttpClient,Arduino_CloudUtils,ArduinoBearSSL +depends=Arduino_ConnectionHandler,Arduino_DebugUtils,Arduino_SecureElement,ArduinoMqttClient,ArduinoECCX08,RTCZero,Adafruit SleepyDog Library,ArduinoHttpClient,Arduino_CloudUtils,ArduinoBearSSL,Arduino_NetworkConfigurator diff --git a/src/AIoTC_Config.h b/src/AIoTC_Config.h index 89f04e20f..a25b81080 100644 --- a/src/AIoTC_Config.h +++ b/src/AIoTC_Config.h @@ -148,6 +148,13 @@ #define BOARD_STM32H7 #endif +#if defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_NICLA_VISION) || defined(ARDUINO_OPTA) || defined(ARDUINO_GIGA) \ + || defined(ARDUINO_UNOR4_WIFI) || defined(ARDUINO_PORTENTA_C33) + #define NETWORK_CONFIGURATOR_ENABLED (1) +#else + #define NETWORK_CONFIGURATOR_ENABLED (0) +#endif + /****************************************************************************** * CONSTANTS ******************************************************************************/ @@ -178,6 +185,6 @@ #define AIOT_CONFIG_LASTVALUES_SYNC_MAX_RETRY_CNT (10UL) #endif -#define AIOT_CONFIG_LIB_VERSION "2.5.1" +#define AIOT_CONFIG_LIB_VERSION "2.6.1" #endif /* ARDUINO_AIOTC_CONFIG_H_ */ diff --git a/src/ArduinoIoTCloud.cpp b/src/ArduinoIoTCloud.cpp index a565cac44..74443ff5b 100644 --- a/src/ArduinoIoTCloud.cpp +++ b/src/ArduinoIoTCloud.cpp @@ -27,6 +27,9 @@ ArduinoIoTCloudClass::ArduinoIoTCloudClass() : _connection{nullptr} +#if NETWORK_CONFIGURATOR_ENABLED +, _configurator{nullptr} +#endif , _time_service(TimeService) , _thing_id{"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"} , _lib_version{AIOT_CONFIG_LIB_VERSION} diff --git a/src/ArduinoIoTCloud.h b/src/ArduinoIoTCloud.h index 7cea9382e..19e94555d 100644 --- a/src/ArduinoIoTCloud.h +++ b/src/ArduinoIoTCloud.h @@ -25,9 +25,12 @@ #include #include +#if NETWORK_CONFIGURATOR_ENABLED + #include +#endif #if defined(DEBUG_ERROR) || defined(DEBUG_WARNING) || defined(DEBUG_INFO) || defined(DEBUG_DEBUG) || defined(DEBUG_VERBOSE) -# include + #include #endif #include "AIoTC_Const.h" @@ -87,7 +90,7 @@ class ArduinoIoTCloudClass virtual void update () = 0; virtual int connected () = 0; virtual void printDebugInfo() = 0; - + virtual void disconnect () { } void push(); bool setTimestamp(String const & prop_name, unsigned long const timestamp); @@ -101,6 +104,9 @@ class ArduinoIoTCloudClass inline unsigned long getInternalTime() { return _time_service.getTime(); } inline unsigned long getLocalTime() { return _time_service.getLocalTime(); } + #if NETWORK_CONFIGURATOR_ENABLED + inline void setConfigurator(NetworkConfiguratorClass & configurator) { _configurator = &configurator; } + #endif void addCallback(ArduinoIoTCloudEvent const event, OnCloudEventCallback callback); #define addProperty( v, ...) addPropertyReal(v, #v, __VA_ARGS__) @@ -146,6 +152,9 @@ class ArduinoIoTCloudClass protected: ConnectionHandler * _connection; + #if NETWORK_CONFIGURATOR_ENABLED + NetworkConfiguratorClass * _configurator; + #endif TimeServiceClass & _time_service; String _thing_id; String _lib_version; diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index dc8bb86d0..31e972882 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -35,7 +35,7 @@ #include /****************************************************************************** - LOCAL MODULE FUNCTIONS + LOCAL MODULE FUNCTIONS ******************************************************************************/ unsigned long getTime() @@ -48,7 +48,7 @@ unsigned long getTime() ******************************************************************************/ ArduinoIoTCloudTCP::ArduinoIoTCloudTCP() -: _state{State::ConnectPhy} +: _state{State::ConfigPhy} , _connection_attempt(0,0) , _message_stream(std::bind(&ArduinoIoTCloudTCP::sendMessage, this, std::placeholders::_1)) , _thing(&_message_stream) @@ -72,36 +72,29 @@ ArduinoIoTCloudTCP::ArduinoIoTCloudTCP() , _get_ota_confirmation{nullptr} #endif /* OTA_ENABLED */ { - + cbor::encoder::iotcloud::commandEncoders(); + cbor::decoder::iotcloud::commandDecoders(); } /****************************************************************************** * PUBLIC MEMBER FUNCTIONS ******************************************************************************/ -int ArduinoIoTCloudTCP::begin(ConnectionHandler & connection, bool const enable_watchdog, String brokerAddress, uint16_t brokerPort) +int ArduinoIoTCloudTCP::begin(ConnectionHandler & connection, bool const enable_watchdog, String brokerAddress, uint16_t brokerPort, bool auto_reconnect) { _connection = &connection; _brokerAddress = brokerAddress; - ArduinoIoTAuthenticationMode authMode = ArduinoIoTAuthenticationMode::CERTIFICATE; + _authMode = ArduinoIoTAuthenticationMode::CERTIFICATE; #if defined (BOARD_HAS_SECRET_KEY) /* If board supports and sketch is configured for username and password login */ if(_password.length()) { - authMode = ArduinoIoTAuthenticationMode::PASSWORD; + _authMode = ArduinoIoTAuthenticationMode::PASSWORD; } #endif - /* Setup broker TLS client */ - _brokerClient.begin(connection, authMode); - -#if OTA_ENABLED - /* Setup OTA TLS client */ - _otaClient.begin(connection); -#endif - /* If board is configured for certificate authentication and mTLS */ - if(authMode == ArduinoIoTAuthenticationMode::CERTIFICATE) + if(_authMode == ArduinoIoTAuthenticationMode::CERTIFICATE) { #if defined(BOARD_HAS_SECURE_ELEMENT) if (!_selement.begin()) @@ -141,18 +134,19 @@ int ArduinoIoTCloudTCP::begin(ConnectionHandler & connection, bool const enable_ _brokerPort = (brokerPort == DEFAULT_BROKER_PORT_AUTO) ? DEFAULT_BROKER_PORT_USER_PASS_AUTH : brokerPort; } - /* Setup TimeService */ - _time_service.begin(_connection); - /* Setup retry timers */ _connection_attempt.begin(AIOT_CONFIG_RECONNECTION_RETRY_DELAY_ms, AIOT_CONFIG_MAX_RECONNECTION_RETRY_DELAY_ms); - return begin(enable_watchdog, _brokerAddress, _brokerPort); + return begin(enable_watchdog, _brokerAddress, _brokerPort, auto_reconnect); } -int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, uint16_t brokerPort) +int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, uint16_t brokerPort, bool auto_reconnect) { + _enable_watchdog = enable_watchdog; _brokerAddress = brokerAddress; _brokerPort = brokerPort; + _auto_reconnect = auto_reconnect; + + _state = State::ConfigPhy; _mqttClient.setClient(_brokerClient); @@ -189,26 +183,18 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, } #endif /* BOARD_HAS_OFFLOADED_ECCX08 */ -#if defined(ARDUINO_UNOWIFIR4) +#if defined (ARDUINO_UNOWIFIR4) if (String(WiFi.firmwareVersion()) < String("0.2.0")) { DEBUG_ERROR("ArduinoIoTCloudTCP::%s In order to connect to Arduino IoT Cloud, WiFi firmware needs to be >= 0.2.0, current %s", __FUNCTION__, WiFi.firmwareVersion()); } #endif - /* Since we do not control what code the user inserts - * between ArduinoIoTCloudTCP::begin() and the first - * call to ArduinoIoTCloudTCP::update() it is wise to - * set a rather large timeout at first. - */ -#if defined (ARDUINO_ARCH_SAMD) || defined (ARDUINO_ARCH_MBED) - if (enable_watchdog) { - /* Initialize watchdog hardware */ - watchdog_enable(); - /* Setup callbacks to feed the watchdog during offloaded network operations (connection/download)*/ - watchdog_enable_network_feed(_connection->getInterface()); +#if NETWORK_CONFIGURATOR_ENABLED + if(_configurator != nullptr){ + _configurator->enableAgent(ConfiguratorAgent::AgentTypes::BLE,false); + _configurator->begin(); } #endif - return 1; } @@ -225,12 +211,16 @@ void ArduinoIoTCloudTCP::update() State next_state = _state; switch (_state) { + case State::ConfigPhy: next_state = handle_ConfigPhy(); break; + case State::Init: next_state = handle_Init(); break; 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::Connected: next_state = handle_Connected(); break; case State::Disconnect: next_state = handle_Disconnect(); break; + case State::Disconnected: break; } + _state = next_state; /* This watchdog feed is actually needed only by the RP2040 Connect because its @@ -241,11 +231,24 @@ void ArduinoIoTCloudTCP::update() watchdog_reset(); #endif + /* Poll the network configurator to check if it is updating the configuration. + * The polling must be performed only if the the first configuration is completed. + */ + #if NETWORK_CONFIGURATOR_ENABLED + if(_configurator != nullptr && _state > State::Init && _configurator->update() == NetworkConfiguratorStates::UPDATING_CONFIG){ + _state = State::ConfigPhy; + } + #endif + #if OTA_ENABLED /* OTA FSM needs to reach the Idle state before being able to run independently from * the mqttClient. The state can be reached only after the mqttClient is connected to * the broker. */ + if(_state <= State::ConnectPhy){ + return; + } + if((_ota.getState() != OTACloudProcessInterface::Resume && _ota.getState() != OTACloudProcessInterface::OtaBegin) || _mqttClient.connected()) { @@ -262,6 +265,9 @@ void ArduinoIoTCloudTCP::update() int ArduinoIoTCloudTCP::connected() { + if (_state <= State::ConnectPhy) { + return 0; + } return _mqttClient.connected(); } @@ -270,12 +276,73 @@ void ArduinoIoTCloudTCP::printDebugInfo() DEBUG_INFO("***** Arduino IoT Cloud - %s *****", AIOT_CONFIG_LIB_VERSION); DEBUG_INFO("Device ID: %s", getDeviceId().c_str()); DEBUG_INFO("MQTT Broker: %s:%d", _brokerAddress.c_str(), _brokerPort); +#if NETWORK_CONFIGURATOR_ENABLED + DEBUG_INFO("Network Configurator: %s", ANetworkConfigurator_LIB_VERSION); +#endif +} + +void ArduinoIoTCloudTCP::disconnect() { + if (_state <= State::ConnectPhy) { + return; + } + + _mqttClient.stop(); + _auto_reconnect = false; + _state = State::Disconnect; } /****************************************************************************** * PRIVATE MEMBER FUNCTIONS ******************************************************************************/ +ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_ConfigPhy() +{ +#if NETWORK_CONFIGURATOR_ENABLED + if (_configurator == nullptr) { + return State::Init; + } + + if(_configurator->update() == NetworkConfiguratorStates::CONFIGURED) { + _configurator->disconnectAgent(); + return State::Init; + } + return State::ConfigPhy; +#else + return State::Init; +#endif +} + +ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Init() +{ + /* Setup broker TLS client */ + /* Setup broker TLS client */ + _brokerClient.begin(*_connection, _authMode); + +#if OTA_ENABLED + /* Setup OTA TLS client */ + _otaClient.begin(*_connection); +#endif + + /* Setup TimeService */ + _time_service.begin(_connection); + + /* Since we do not control what code the user inserts + * between ArduinoIoTCloudTCP::begin() and the first + * call to ArduinoIoTCloudTCP::update() it is wise to + * set a rather large timeout at first. + */ +#if defined (ARDUINO_ARCH_SAMD) || defined (ARDUINO_ARCH_MBED) + if (_enable_watchdog) { + /* Initialize watchdog hardware */ + watchdog_enable(); + /* Setup callbacks to feed the watchdog during offloaded network operations (connection/download)*/ + watchdog_enable_network_feed(_connection->getInterface()); + } +#endif + + return State::ConnectPhy; +} + ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_ConnectPhy() { if (_connection->check() == NetworkConnectionState::CONNECTED) @@ -381,9 +448,13 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Disconnect() DEBUG_INFO("Disconnected from Arduino IoT Cloud"); execCloudEventCallback(ArduinoIoTCloudEvent::DISCONNECT); - /* 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; + if(_auto_reconnect) { + /* 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; + } + + return State::Disconnected; } void ArduinoIoTCloudTCP::onMessage(int length) @@ -630,11 +701,12 @@ int ArduinoIoTCloudTCP::updateCertificate(String authorityKeyIdentifier, String } return 0; } + #endif /****************************************************************************** - * EXTERN DEFINITION - ******************************************************************************/ +* EXTERN DEFINITION +******************************************************************************/ ArduinoIoTCloudTCP ArduinoCloud; diff --git a/src/ArduinoIoTCloudTCP.h b/src/ArduinoIoTCloudTCP.h index 6560a5f70..d59c6de97 100644 --- a/src/ArduinoIoTCloudTCP.h +++ b/src/ArduinoIoTCloudTCP.h @@ -72,9 +72,10 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass virtual void update () override; virtual int connected () override; virtual void printDebugInfo() override; + virtual void disconnect () override; - int begin(ConnectionHandler & connection, bool const enable_watchdog = true, String brokerAddress = DEFAULT_BROKER_ADDRESS, uint16_t brokerPort = DEFAULT_BROKER_PORT_AUTO); - int begin(bool const enable_watchdog = true, String brokerAddress = DEFAULT_BROKER_ADDRESS, uint16_t brokerPort = DEFAULT_BROKER_PORT_AUTO); + int begin(ConnectionHandler & connection, bool const enable_watchdog = true, String brokerAddress = DEFAULT_BROKER_ADDRESS, uint16_t brokerPort = DEFAULT_BROKER_PORT_AUTO, bool auto_reconnect = true); + int begin(bool const enable_watchdog = true, String brokerAddress = DEFAULT_BROKER_ADDRESS, uint16_t brokerPort = DEFAULT_BROKER_PORT_AUTO, bool auto_reconnect = true); #if defined(BOARD_HAS_SECURE_ELEMENT) int updateCertificate(String authorityKeyIdentifier, String serialNumber, String notBefore, String notAfter, String signature); @@ -120,11 +121,14 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass enum class State { + ConfigPhy, + Init, ConnectPhy, SyncTime, ConnectMqttBroker, Connected, Disconnect, + Disconnected, }; State _state; @@ -133,11 +137,14 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass ArduinoCloudThing _thing; ArduinoCloudDevice _device; + ArduinoIoTAuthenticationMode _authMode; String _brokerAddress; uint16_t _brokerPort; uint8_t _mqtt_data_buf[MQTT_TRANSMIT_BUFFER_SIZE]; int _mqtt_data_len; bool _mqtt_data_request_retransmit; + bool _enable_watchdog; + bool _auto_reconnect; #if defined(BOARD_HAS_SECRET_KEY) String _password; @@ -170,6 +177,8 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass 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"); } + State handle_ConfigPhy(); + State handle_Init(); State handle_ConnectPhy(); State handle_SyncTime(); State handle_ConnectMqttBroker(); diff --git a/src/cbor/IoTCloudMessageDecoder.cpp b/src/cbor/IoTCloudMessageDecoder.cpp index e25962219..8deb9e3e5 100644 --- a/src/cbor/IoTCloudMessageDecoder.cpp +++ b/src/cbor/IoTCloudMessageDecoder.cpp @@ -15,30 +15,9 @@ #include #include "IoTCloudMessageDecoder.h" +#include #include -static inline 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; -} - -static inline size_t 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 dest_size; - } - } - - return 0; -} - /****************************************************************************** MESSAGE DECODE FUNCTIONS ******************************************************************************/ @@ -46,8 +25,12 @@ static inline size_t copyCBORByteToArray(CborValue * param, uint8_t * dest, size MessageDecoder::Status ThingUpdateCommandDecoder::decode(CborValue* iter, Message *msg) { ThingUpdateCmd * thingCommand = (ThingUpdateCmd *) msg; + size_t dest_size = sizeof(thingCommand->params.thing_id); + // Message is composed of a single parameter, a string (thing_id) - if (!copyCBORStringToArray(iter, thingCommand->params.thing_id, sizeof(thingCommand->params.thing_id))) { + if (cbor::utils::copyCBORStringToArray( + iter, thingCommand->params.thing_id, + dest_size) == MessageDecoder::Status::Error) { return MessageDecoder::Status::Error; } @@ -57,8 +40,14 @@ MessageDecoder::Status ThingUpdateCommandDecoder::decode(CborValue* iter, Messag MessageDecoder::Status ThingDetachCommandDecoder::decode(CborValue* iter, Message *msg) { ThingDetachCmd * thingCommand = (ThingDetachCmd *) msg; + size_t dest_size = sizeof(thingCommand->params.thing_id); + + // Message is composed of a single parameter, a string (thing_id) - if (!copyCBORStringToArray(iter, thingCommand->params.thing_id, sizeof(thingCommand->params.thing_id))) { + if (cbor::utils::copyCBORStringToArray( + iter, + thingCommand->params.thing_id, + dest_size) == MessageDecoder::Status::Error) { return MessageDecoder::Status::Error; } @@ -125,33 +114,57 @@ MessageDecoder::Status LastValuesUpdateCommandDecoder::decode(CborValue* iter, M } MessageDecoder::Status OtaUpdateCommandDecoder::decode(CborValue* iter, Message *msg) { - CborError error = CborNoError; OtaUpdateCmdDown * ota = (OtaUpdateCmdDown *) msg; + size_t dest_size = sizeof(ota->params.id); // Message is composed 4 parameters: id, url, initialSha, finalSha - if (!copyCBORByteToArray(iter, ota->params.id, sizeof(ota->params.id))) { + + // decoding parameter id + if (cbor::utils::copyCBORByteToArray( + iter, + ota->params.id, + dest_size) == MessageDecoder::Status::Error) { return MessageDecoder::Status::Error; } - error = cbor_value_advance(iter); + // decoding parameter url + if(cbor_value_advance(iter) != CborNoError) { + return MessageDecoder::Status::Error; + } + + dest_size = sizeof(ota->params.url); + + if (cbor::utils::copyCBORStringToArray(iter, + ota->params.url, + dest_size) == MessageDecoder::Status::Error) { + return MessageDecoder::Status::Error; + } - if ((error != CborNoError) || !copyCBORStringToArray(iter, ota->params.url, sizeof(ota->params.url))) { + // decoding parameter initialSha256 + if(cbor_value_advance(iter) != CborNoError) { return MessageDecoder::Status::Error; } - error = cbor_value_advance(iter); + dest_size = sizeof(ota->params.initialSha256); - if ((error != CborNoError) || - copyCBORByteToArray(iter, ota->params.initialSha256, - sizeof(ota->params.initialSha256)) != sizeof(ota->params.initialSha256)) { + if (cbor::utils::copyCBORByteToArray(iter, + ota->params.initialSha256, + dest_size) == MessageDecoder::Status::Error || + dest_size != sizeof(ota->params.initialSha256)) { return MessageDecoder::Status::Error; } - error = cbor_value_advance(iter); + // decoding parameter finalSha256 + if(cbor_value_advance(iter) != CborNoError) { + return MessageDecoder::Status::Error; + } + + dest_size = sizeof(ota->params.finalSha256); - if ((error != CborNoError) || - copyCBORByteToArray(iter, ota->params.finalSha256, - sizeof(ota->params.finalSha256)) != sizeof(ota->params.finalSha256)) { + if (cbor::utils::copyCBORByteToArray(iter, + ota->params.finalSha256, + dest_size) == MessageDecoder::Status::Error || + dest_size != sizeof(ota->params.finalSha256)) { return MessageDecoder::Status::Error; } @@ -163,3 +176,13 @@ static ThingUpdateCommandDecoder thingUpdateCommandDecoder; static ThingDetachCommandDecoder thingDetachCommandDecoder; static LastValuesUpdateCommandDecoder lastValuesUpdateCommandDecoder; static TimezoneCommandDownDecoder timezoneCommandDownDecoder; + +namespace cbor { namespace decoder { namespace iotcloud { + void commandDecoders() { + (void) otaUpdateCommandDecoder; + (void) thingUpdateCommandDecoder; + (void) thingDetachCommandDecoder; + (void) lastValuesUpdateCommandDecoder; + (void) timezoneCommandDownDecoder; + } +}}} diff --git a/src/cbor/IoTCloudMessageDecoder.h b/src/cbor/IoTCloudMessageDecoder.h index 4b444f5c8..f072a2c3a 100644 --- a/src/cbor/IoTCloudMessageDecoder.h +++ b/src/cbor/IoTCloudMessageDecoder.h @@ -63,4 +63,12 @@ class TimezoneCommandDownDecoder: public CBORMessageDecoderInterface { MessageDecoder::Status decode(CborValue* iter, Message *msg) override; }; +namespace cbor { namespace decoder { namespace iotcloud { + /** + * Some link time optimization may exclude these classes to be instantiated + * thus it may be required to reference them from outside of this file + */ + void commandDecoders(); +}}} + #endif /* ARDUINO_CBOR_MESSAGE_DECODER_H_ */ diff --git a/src/cbor/IoTCloudMessageEncoder.cpp b/src/cbor/IoTCloudMessageEncoder.cpp index ed4c6d823..71363cf5e 100644 --- a/src/cbor/IoTCloudMessageEncoder.cpp +++ b/src/cbor/IoTCloudMessageEncoder.cpp @@ -159,3 +159,14 @@ static LastValuesBeginCommandEncoder lastValuesBeginCommandEncoder; static DeviceBeginCommandEncoder deviceBeginCommandEncoder; static OtaProgressCommandUpEncoder otaProgressCommandUpEncoder; static TimezoneCommandUpEncoder timezoneCommandUpEncoder; + +namespace cbor { namespace encoder { namespace iotcloud { + void commandEncoders() { + (void) otaBeginCommandEncoder; + (void) thingBeginCommandEncoder; + (void) lastValuesBeginCommandEncoder; + (void) deviceBeginCommandEncoder; + (void) otaProgressCommandUpEncoder; + (void) timezoneCommandUpEncoder; + } +}}} diff --git a/src/cbor/IoTCloudMessageEncoder.h b/src/cbor/IoTCloudMessageEncoder.h index 99922bc59..9b5ca441d 100644 --- a/src/cbor/IoTCloudMessageEncoder.h +++ b/src/cbor/IoTCloudMessageEncoder.h @@ -71,5 +71,12 @@ class TimezoneCommandUpEncoder: public CBORMessageEncoderInterface { MessageEncoder::Status encode(CborEncoder* encoder, Message *msg) override; }; +namespace cbor { namespace encoder { namespace iotcloud { + /** + * Some link time optimization may exclude these classes to be instantiated + * thus it may be required to reference them from outside of this file + */ + void commandEncoders(); +}}} #endif /* ARDUINO_CBOR_MESSAGE_ENCODER_H_ */