From 851199660d61e40f8749cce5f1245e0b13b19a39 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Mon, 5 Feb 2024 09:12:47 +0100 Subject: [PATCH 01/15] defining encoder/decoder interfaces --- src/interfaces/Decoder.h | 50 +++++++++++++++++++++++++++++++++++++++ src/interfaces/Encoder.h | 51 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 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..ee913414a --- /dev/null +++ b/src/interfaces/Decoder.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 + +// using decoderFactory=std::function; + +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; + + /** + * sets the preallocated empty message structure that needs to be decoded, before iterativley decoding it + * @param Message the message that needs to be decoded, msg should be big enough to accommodate all the required fields + */ + virtual void setMessage(Message* msg) = 0; + + /** + * after having the Message struct set call this function to decode he chunks of buffers provided to this call + * @param buffer the buffer the message will be encoded into + * @param len the length of the provided buffer, value will be updated with the used len of the buffer + * @return SUCCESS: the message is completely encoded + * IN_PROGRESS: the message is encoded correctly so far, provide another buffer to continue + * ERROR: error during the encoding of the message + */ + virtual Status feed(uint8_t* buf, size_t &len) = 0; +}; diff --git a/src/interfaces/Encoder.h b/src/interfaces/Encoder.h new file mode 100644 index 000000000..baeb75225 --- /dev/null +++ b/src/interfaces/Encoder.h @@ -0,0 +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 +#include + +// using encoderFactory=std::function; + +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; + + /** + * sets the message that needs to be encoded, before iterativley encoding it + * @param Message the message that needs to be encoded + */ + virtual void setMessage(Message* msg) = 0; + + /** + * after having the Message struct set call this function to encode the message into a buffer + * this action is performed incrementally into chunks of buffers + * @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: the message is completely encoded + * IN_PROGRESS: the message is encoded correctly so far, provide another buffer to continue + * ERROR: error during the encoding of the message + */ + virtual Status encode(uint8_t* buf, size_t& len) = 0; +}; \ No newline at end of file From 50ed18133b53e48eaefcd4e32b6fa98704114adf Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 17 Apr 2024 17:31:35 +0200 Subject: [PATCH 02/15] implementing CBOR encoder and decoder for Command protocol model --- src/cbor/CBOR.cpp | 56 ++++++++ src/cbor/CBOR.h | 27 ++++ src/cbor/MessageDecoder.cpp | 247 ++++++++++++++++++++++++++++++++++++ src/cbor/MessageDecoder.h | 84 ++++++++++++ src/cbor/MessageEncoder.cpp | 190 +++++++++++++++++++++++++++ src/cbor/MessageEncoder.h | 74 +++++++++++ 6 files changed, 678 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..3765969f3 --- /dev/null +++ b/src/cbor/CBOR.cpp @@ -0,0 +1,56 @@ +#include "CBOR.h" + +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; + } +} \ No newline at end of file diff --git a/src/cbor/CBOR.h b/src/cbor/CBOR.h new file mode 100644 index 000000000..538926d51 --- /dev/null +++ b/src/cbor/CBOR.h @@ -0,0 +1,27 @@ +#pragma once +#include + +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 +}; + +CommandId toCommandId(CBORCommandTag tag); +CBORCommandTag toCBORCommandTag(CommandId id); \ No newline at end of file diff --git a/src/cbor/MessageDecoder.cpp b/src/cbor/MessageDecoder.cpp new file mode 100644 index 000000000..ea455cb06 --- /dev/null +++ b/src/cbor/MessageDecoder.cpp @@ -0,0 +1,247 @@ +// +// This file is part of CBORDecoder +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of CBORDecoder. +// 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 + +#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)) { + return false; + } + + // 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 false; + } + + return true; +} + +// 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)) { + return false; + } + + // 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 false; + } + + return true; +} + +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) { + return ArrayParserState::Error; + } + // Leave the array + if (cbor_value_leave_container(main_iter, array_iter) != CborNoError) { + return ArrayParserState::Error; + } + return ArrayParserState::Complete; +} + +/****************************************************************************** + MESSAGE DECODE FUNCTIONS + ******************************************************************************/ + +CBORMessageDecoder::ArrayParserState CBORMessageDecoder::decodeThingBeginCmd(CborValue * param, Message * message) { + ThingBeginCmd * thingCommand = (ThingBeginCmd *) 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) { + 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; + } + + if (cbor_value_advance(param) != CborNoError) { + return ArrayParserState::Error; + } + + if (!copyCBORStringToArray(param, ota->params.url, sizeof(ota->params.url))) { + return ArrayParserState::Error; + } + + if (cbor_value_advance(param) != CborNoError) { + return ArrayParserState::Error; + } + + if (!copyCBORByteToArray(param, ota->params.initialSha256, sizeof(ota->params.initialSha256))) { + return ArrayParserState::Error; + } + + if (cbor_value_advance(param) != CborNoError) { + return ArrayParserState::Error; + } + + if (!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::decodeThingBeginCmd(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..e8ba099a4 --- /dev/null +++ b/src/cbor/MessageDecoder.h @@ -0,0 +1,84 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// 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_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); + + void setMessage(__attribute__((unused))Message* msg) {} // FIXME not implemented yet + Decoder::Status feed(__attribute__((unused)) uint8_t* buf, __attribute__((unused)) size_t &len) { return Decoder::Status::Error; } // FIXME not implemented yet + +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 decodeThingBeginCmd(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..e60f15079 --- /dev/null +++ b/src/cbor/MessageEncoder.cpp @@ -0,0 +1,190 @@ +/* + 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 "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; + } + + if (error != CborNoError) { + return EncoderState::Error; + } + + return EncoderState::CloseArray; +} + +CBORMessageEncoder::EncoderState CBORMessageEncoder::handle_CloseArray(CborEncoder * encoder, CborEncoder * array_encoder) +{ + + if (cbor_encoder_close_container(encoder, array_encoder) != CborNoError) { + return EncoderState::Error; + } + + return 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..6d39e1681 --- /dev/null +++ b/src/cbor/MessageEncoder.h @@ -0,0 +1,74 @@ +/* + 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_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); + + void setMessage(__attribute__((unused)) Message* msg) {} // FIXME not implemented yet + Encoder::Status encode(__attribute__((unused)) uint8_t* buf, __attribute__((unused)) size_t& len) { return Encoder::Status::Error; } // FIXME not implemented yet +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 9179fc6aa8aca5d7e484274200efcb6add3d080b Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 17 Apr 2024 17:31:59 +0200 Subject: [PATCH 03/15] adding tests for encoder and decoder --- extras/test/CMakeLists.txt | 5 + extras/test/src/test_command_decode.cpp | 177 +++++++++++++++++++ extras/test/src/test_command_encode.cpp | 215 ++++++++++++++++++++++++ 3 files changed, 397 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..dfae8290a --- /dev/null +++ b/extras/test/src/test_command_decode.cpp @@ -0,0 +1,177 @@ +/* + Copyright (c) 2019 Arduino. All rights reserved. +*/ + +/************************************************************************************** + INCLUDE + **************************************************************************************/ + +#include +#include + +#include + +#include +#include + +/************************************************************************************** + TEST CODE + **************************************************************************************/ + +SCENARIO("Test the decoding of command messages") { + /************************************************************************************/ + + // FIXME + // WHEN("Decode the ThingBeginCmd message") + // { + // CommandDown command; + + // /* + + // DA 00010300 # tag(66560) + // 81 # array(1) + // 78 24 # text(36) + // 65343439346435352D383732612D346664322D393634362D393266383739343933393463 # "e4494d55-872a-4fd2-9646-92f87949394c" + + // */ + + // uint8_t const payload[] = {0xDA, 0x00, 0x01, 0x03, 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.thingBeginCmd.params.thing_id, thingIdToMatch) == 0); + // REQUIRE(command.c.id == ThingBeginCmdId); + // } + // } + + /************************************************************************************/ + + 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); + } + + /************************************************************************************/ + + // FIXME + // WHEN("Decode the OtaUpdateCmdDown message") + // { + // CommandDown command; + + // /* + // DA 00010100 # tag(65792) + // 84 # array(4) + // 50 # text(12) + // 000102030405060708090A0B0C0D0E0F + // 75 # text(21) + // 383736313233383736313233383736313233313233 # "876123876123876123123" + // 53 # bytes(19) + // 33303034616162323735313164623332313264 # "3004aab27511db3212d" + // 50 # bytes(16) + // 6A6B6173646B6A686173646B6A687868 # "jkasdkjhasdkjhxh" + + // */ + + // uint8_t const payload[] = {0xDA, 0x00, 0x01, 0x01, 0x00, 0x84, 0x6C, 0x6F, 0x74, 0x61, 0x2D, 0x69, 0x64, 0x2D, 0x31, 0x32, 0x33, 0x34, 0x35, 0x75, 0x38, 0x37, 0x36, 0x31, 0x32, 0x33, 0x38, 0x37, 0x36, 0x31, 0x32, 0x33, 0x38, 0x37, 0x36, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x53, 0x33, 0x30, 0x30, 0x34, 0x61, 0x61, 0x62, 0x32, 0x37, 0x35, 0x31, 0x31, 0x64, 0x62, 0x33, 0x32, 0x31, 0x32, 0x64, 0x50, 0x6A, 0x6B, 0x61, 0x73, 0x64, 0x6B, 0x6A, 0x68, 0x61, 0x73, 0x64, 0x6B, 0x6A, 0x68, 0x78, 0x68}; + + // 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] = {}; + // const char *urlToMatch = "876123876123876123123"; + + // 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)0x33); + // REQUIRE(command.otaUpdateCmdDown.params.initialSha256[1] == (uint8_t)0x30); + // REQUIRE(command.otaUpdateCmdDown.params.initialSha256[2] == (uint8_t)0x30); + // REQUIRE(command.otaUpdateCmdDown.params.initialSha256[3] == (uint8_t)0x34); + // REQUIRE(command.otaUpdateCmdDown.params.initialSha256[4] == (uint8_t)0x61); + // REQUIRE(command.otaUpdateCmdDown.params.initialSha256[5] == (uint8_t)0x61); + // REQUIRE(command.otaUpdateCmdDown.params.initialSha256[6] == (uint8_t)0x62); + // REQUIRE(command.otaUpdateCmdDown.params.initialSha256[7] == (uint8_t)0x32); + + // // Final SHA256 check + // REQUIRE(command.otaUpdateCmdDown.params.finalSha256[0] == (uint8_t)0x6A); + // REQUIRE(command.otaUpdateCmdDown.params.finalSha256[1] == (uint8_t)0x6B); + // REQUIRE(command.otaUpdateCmdDown.params.finalSha256[2] == (uint8_t)0x61); + // REQUIRE(command.otaUpdateCmdDown.params.finalSha256[3] == (uint8_t)0x73); + // REQUIRE(command.otaUpdateCmdDown.params.finalSha256[4] == (uint8_t)0x64); + // REQUIRE(command.otaUpdateCmdDown.params.finalSha256[5] == (uint8_t)0x6B); + + // REQUIRE(command.c.id == OtaUpdateCmdDownId); + // } + // } +} diff --git a/extras/test/src/test_command_encode.cpp b/extras/test/src/test_command_encode.cpp new file mode 100644 index 000000000..05ad70ca2 --- /dev/null +++ b/extras/test/src/test_command_encode.cpp @@ -0,0 +1,215 @@ +/* + Copyright (c) 2019 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); + } + } +} From 105ca85a062f5a29f53accb410af94b328fa6517 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 19 Mar 2024 17:09:11 +0100 Subject: [PATCH 04/15] Extend Commands.h to include new Command protocol model --- src/message/Commands.h | 96 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 2 deletions(-) diff --git a/src/message/Commands.h b/src/message/Commands.h index 0f9cdac20..36ded0dc8 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,76 @@ struct Command { }; typedef Command Message; + +struct LastValuesBeginCmd { + Command c; +}; + +struct OtaBeginUp { + Command c; + struct { + uint8_t sha [SHA256_SIZE]; + } params; +}; + +struct DeviceBeginCmd { + Command c; + struct { + char lib_version[MAX_LIB_VERSION_SIZE]; + } params; +}; + +struct OtaProgressCmdUp { + Command c; + struct { + uint8_t id[ID_SIZE]; + uint8_t state; + int32_t state_data; + uint64_t time; + } params; +}; + +struct TimezoneCommandUp { + Command c; +}; + +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 ThingBeginCmd { + Command c; + struct { + char thing_id[THING_ID_SIZE]; + } params; +}; + +struct LastValuesUpdateCmd { + Command c; + struct { + uint8_t * last_values; + size_t length; + } params; +}; + +struct TimezoneCommandDown { + Command c; + struct { + int32_t offset; + uint32_t until; + } params; +}; + +union CommandDown { + struct Command c; + struct OtaUpdateCmdDown otaUpdateCmdDown; + struct ThingBeginCmd thingBeginCmd; + struct LastValuesUpdateCmd lastValuesUpdateCmd; + struct TimezoneCommandDown timezoneCommandDown; +}; From 4cef3d5d5ea542a8d33b8633d92e9f25f464bac7 Mon Sep 17 00:00:00 2001 From: pennam Date: Thu, 29 Feb 2024 17:25:28 +0100 Subject: [PATCH 05/15] ArduinoIoTCloudTCP: switch to messages --- src/ArduinoIoTCloudTCP.cpp | 212 +++++++++++++++++-------------------- src/ArduinoIoTCloudTCP.h | 20 ++-- 2 files changed, 108 insertions(+), 124 deletions(-) diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index e9ccd4a94..521a92d40 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 subsribe 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; } @@ -341,6 +346,7 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Connected() { if (!_mqttClient.connected() || !_thing.connected() || !_device.connected()) { + Serial.println("State::Disconnect"); return State::Disconnect; } @@ -439,28 +445,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 +452,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.thingBeginCmd.params.thing_id)) { + _thing_id = String(command.thingBeginCmd.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 +566,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 +581,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 +592,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 +604,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 fc24b5b98d632db39be87851b8ce8e578a44fb1f Mon Sep 17 00:00:00 2001 From: pennam Date: Wed, 20 Mar 2024 10:43:43 +0100 Subject: [PATCH 06/15] 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 86569e8b6..3e2510154 100644 --- a/src/ArduinoIoTCloudDevice.cpp +++ b/src/ArduinoIoTCloudDevice.cpp @@ -109,12 +109,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 f9da01ed1550f3a966f9517c541dfea604c42ef2 Mon Sep 17 00:00:00 2001 From: pennam Date: Tue, 19 Mar 2024 10:52:32 +0100 Subject: [PATCH 07/15] BearSSLClient: allow configuration after object creation --- src/tls/BearSSLClient.cpp | 21 +++++++++++++++------ src/tls/BearSSLClient.h | 7 ++++++- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/tls/BearSSLClient.cpp b/src/tls/BearSSLClient.cpp index cdc58794f..e778a0efb 100644 --- a/src/tls/BearSSLClient.cpp +++ b/src/tls/BearSSLClient.cpp @@ -34,11 +34,20 @@ #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; +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), @@ -266,8 +275,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 +287,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 e291227a4a23e8fd7ba778bfcf2bfb7674a69dce Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 16 Apr 2024 16:29:45 +0200 Subject: [PATCH 08/15] BearSSLClient: stop issue 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 e778a0efb..cee922102 100644 --- a/src/tls/BearSSLClient.cpp +++ b/src/tls/BearSSLClient.cpp @@ -34,11 +34,10 @@ #include "BearSSLClient.h" -bool BearSSLClient::_sslio_closing = false; - BearSSLClient::BearSSLClient() : _noSNI(false), - _get_time_func(nullptr) + _get_time_func(nullptr), + _sslio_closing(false) { _ecKey.curve = 0; _ecKey.x = NULL; @@ -169,7 +168,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); } @@ -311,7 +310,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); @@ -332,12 +331,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; } @@ -367,12 +367,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 0f229e8279b52c4cebab850b5b7385653cf1c8fe Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 16 Apr 2024 16:31:53 +0200 Subject: [PATCH 09/15] 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 cee922102..74181ff3f 100644 --- a/src/tls/BearSSLClient.cpp +++ b/src/tls/BearSSLClient.cpp @@ -193,7 +193,7 @@ uint8_t BearSSLClient::connected() BearSSLClient::operator bool() { - return (*_client); + return (*_client); } void BearSSLClient::setInsecure(SNI insecure) @@ -350,7 +350,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 7db6c965b77f949561b85f08f889d0e761be7b9c Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 16 Apr 2024 16:39:08 +0200 Subject: [PATCH 10/15] 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 ca36fc4faec5cfe22d37d09d11c2050859ef644f Mon Sep 17 00:00:00 2001 From: pennam Date: Tue, 19 Mar 2024 09:28:15 +0100 Subject: [PATCH 11/15] Add TLSClientMqtt --- src/tls/utility/TLSClientMqtt.cpp | 61 +++++++++++++++++++++++++++ src/tls/utility/TLSClientMqtt.h | 69 +++++++++++++++++++++++++++++++ 2 files changed, 130 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..d5f5830d1 --- /dev/null +++ b/src/tls/utility/TLSClientMqtt.cpp @@ -0,0 +1,61 @@ +/* + 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) + +#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) + +#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 740b9d88f0bdb2ff4f0fa66d1eb43b3d6fe4e6f3 Mon Sep 17 00:00:00 2001 From: pennam Date: Mon, 25 Mar 2024 15:57:35 +0100 Subject: [PATCH 12/15] Add TLSClientOta --- src/tls/utility/TLSClientOta.cpp | 60 ++++++++++++++++++++ src/tls/utility/TLSClientOta.h | 96 ++++++++++++++++++++++++++++++++ 2 files changed, 156 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..f020a2ac4 --- /dev/null +++ b/src/tls/utility/TLSClientOta.cpp @@ -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/. +*/ + +#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) + +#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) + +#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 59955c2d2dab559b2f48dc63d45c6ae70cd01b5b Mon Sep 17 00:00:00 2001 From: pennam Date: Mon, 25 Mar 2024 15:54:19 +0100 Subject: [PATCH 13/15] 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 bcaf3a4e17a7da9c8ffca023e3880b6a571b401c Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 17 Apr 2024 17:13:45 +0200 Subject: [PATCH 14/15] removing x509_crt_bundle_len since not used anywhere and causing issues with esp32 --- 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 e6bc311917b141a3207c41db03c20d60e32aff62 Mon Sep 17 00:00:00 2001 From: pennam Date: Tue, 9 Apr 2024 09:46:42 +0200 Subject: [PATCH 15/15] 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)