From 8bbc4e7d84fa82bef3a9b7b7c83efea54f1cdbde Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Mon, 5 Feb 2024 09:12:47 +0100 Subject: [PATCH 1/6] Defining encoder/decoder interfaces --- src/interfaces/Decoder.h | 41 ++++++++++++++++++++++++++++++++++++++++ src/interfaces/Encoder.h | 40 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 src/interfaces/Decoder.h create mode 100644 src/interfaces/Encoder.h diff --git a/src/interfaces/Decoder.h b/src/interfaces/Decoder.h new file mode 100644 index 000000000..fc725ec8b --- /dev/null +++ b/src/interfaces/Decoder.h @@ -0,0 +1,41 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +/****************************************************************************** + * INCLUDES + ******************************************************************************/ + +#include +#include + +/****************************************************************************** + * CLASS DECLARATION + ******************************************************************************/ + +class Decoder { +public: + enum Status: uint8_t { + Complete, + InProgress, + Error + }; + + /** + * Decode a buffer into a provided message structure + * @param msg: the message structure that is going to be filled with data provided in the buffer + * @param buf: the incoming buffer that needs to be decoded + * @param len: the length of the incoming buffer, value will be updated with the used len of the buffer + * @return SUCCESS: if the message is decoded correctly + * ERROR: if the message wasn't decoded correctly + */ + virtual Status decode(Message* msg, const uint8_t* const buf, size_t &len) = 0; +}; diff --git a/src/interfaces/Encoder.h b/src/interfaces/Encoder.h new file mode 100644 index 000000000..38fbe0edd --- /dev/null +++ b/src/interfaces/Encoder.h @@ -0,0 +1,40 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +/****************************************************************************** + * INCLUDES + ******************************************************************************/ +#include +#include + +/****************************************************************************** + * CLASS DECLARATION + ******************************************************************************/ + +class Encoder { +public: + enum Status: uint8_t { + Complete, + InProgress, + Error + }; + + /** + * Encode a message into a buffer in a single shot + * @param msg: the message that needs to be encoded + * @param buf: the buffer the message will be encoded into + * @param len: the length of the provided buffer, value will be updated with the consumed len of the buffer + * @return SUCCESS: if the message is encoded correctly + * ERROR: error during the encoding of the message + */ + virtual Status encode(Message* msg, uint8_t* buf, size_t& len) = 0; +}; From 06e2adc0dde03f4c58480508ee409255ee40e0c7 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 17 Apr 2024 17:31:35 +0200 Subject: [PATCH 2/6] CBOR encoder and decoder implementation for Command protocol model --- src/cbor/CBOR.cpp | 73 ++++++++++++ src/cbor/CBOR.h | 49 ++++++++ src/cbor/MessageDecoder.cpp | 230 ++++++++++++++++++++++++++++++++++++ src/cbor/MessageDecoder.h | 74 ++++++++++++ src/cbor/MessageEncoder.cpp | 175 +++++++++++++++++++++++++++ src/cbor/MessageEncoder.h | 65 ++++++++++ 6 files changed, 666 insertions(+) create mode 100644 src/cbor/CBOR.cpp create mode 100644 src/cbor/CBOR.h create mode 100644 src/cbor/MessageDecoder.cpp create mode 100644 src/cbor/MessageDecoder.h create mode 100644 src/cbor/MessageEncoder.cpp create mode 100644 src/cbor/MessageEncoder.h diff --git a/src/cbor/CBOR.cpp b/src/cbor/CBOR.cpp new file mode 100644 index 000000000..ced5e3e7f --- /dev/null +++ b/src/cbor/CBOR.cpp @@ -0,0 +1,73 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include "CBOR.h" + +/****************************************************************************** + * FUNCTION DEFINITION + ******************************************************************************/ + +CommandId toCommandId(CBORCommandTag tag) { + switch(tag) { + case CBORCommandTag::CBOROtaBeginUp: + return CommandId::OtaBeginUpId; + case CBORCommandTag::CBORThingBeginCmd: + return CommandId::ThingBeginCmdId; + case CBORCommandTag::CBORLastValuesBeginCmd: + return CommandId::LastValuesBeginCmdId; + case CBORCommandTag::CBORDeviceBeginCmd: + return CommandId::DeviceBeginCmdId; + case CBORCommandTag::CBOROtaProgressCmdUp: + return CommandId::OtaProgressCmdUpId; + case CBORCommandTag::CBORTimezoneCommandUp: + return CommandId::TimezoneCommandUpId; + case CBORCommandTag::CBOROtaUpdateCmdDown: + return CommandId::OtaUpdateCmdDownId; + case CBORCommandTag::CBORThingUpdateCmd: + return CommandId::ThingUpdateCmdId; + case CBORCommandTag::CBORLastValuesUpdate: + return CommandId::LastValuesUpdateCmdId; + case CBORCommandTag::CBORTimezoneCommandDown: + return CommandId::TimezoneCommandDownId; + default: + return CommandId::UnknownCmdId; + } +} + +CBORCommandTag toCBORCommandTag(CommandId id) { + switch(id) { + case CommandId::OtaBeginUpId: + return CBORCommandTag::CBOROtaBeginUp; + case CommandId::ThingBeginCmdId: + return CBORCommandTag::CBORThingBeginCmd; + case CommandId::LastValuesBeginCmdId: + return CBORCommandTag::CBORLastValuesBeginCmd; + case CommandId::DeviceBeginCmdId: + return CBORCommandTag::CBORDeviceBeginCmd; + case CommandId::OtaProgressCmdUpId: + return CBORCommandTag::CBOROtaProgressCmdUp; + case CommandId::TimezoneCommandUpId: + return CBORCommandTag::CBORTimezoneCommandUp; + case CommandId::OtaUpdateCmdDownId: + return CBORCommandTag::CBOROtaUpdateCmdDown; + case CommandId::ThingUpdateCmdId: + return CBORCommandTag::CBORThingUpdateCmd; + case CommandId::LastValuesUpdateCmdId: + return CBORCommandTag::CBORLastValuesUpdate; + case CommandId::TimezoneCommandDownId: + return CBORCommandTag::CBORTimezoneCommandDown; + default: + return CBORCommandTag::CBORUnknownCmdTag; + } +} diff --git a/src/cbor/CBOR.h b/src/cbor/CBOR.h new file mode 100644 index 000000000..999570455 --- /dev/null +++ b/src/cbor/CBOR.h @@ -0,0 +1,49 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ +#include + +/****************************************************************************** + TYPEDEF + ******************************************************************************/ + +enum CBORCommandTag: uint64_t { + // Commands UP + CBOROtaBeginUp = 0x010000, + CBORThingBeginCmd = 0x010300, + CBORLastValuesBeginCmd = 0x010500, + CBORDeviceBeginCmd = 0x010700, + CBOROtaProgressCmdUp = 0x010200, + CBORTimezoneCommandUp = 0x010800, + + // Commands DOWN + CBOROtaUpdateCmdDown = 0x010100, + CBORThingUpdateCmd = 0x010400, + CBORLastValuesUpdate = 0x010600, + CBORTimezoneCommandDown = 0x010900, + + // Unknown Command Tag https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml + CBORUnknownCmdTag16b = 0xffff, // invalid tag + CBORUnknownCmdTag32b = 0xffffffff, // invalid tag + CBORUnknownCmdTag64b = 0xffffffffffffffff, // invalid tag + CBORUnknownCmdTag = CBORUnknownCmdTag32b +}; + +/****************************************************************************** + * FUNCTION DECLARATION + ******************************************************************************/ + +CommandId toCommandId(CBORCommandTag tag); +CBORCommandTag toCBORCommandTag(CommandId id); diff --git a/src/cbor/MessageDecoder.cpp b/src/cbor/MessageDecoder.cpp new file mode 100644 index 000000000..c500ceae9 --- /dev/null +++ b/src/cbor/MessageDecoder.cpp @@ -0,0 +1,230 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include + +#undef max +#undef min +#include + +#include "MessageDecoder.h" +#include + +/****************************************************************************** + PUBLIC MEMBER FUNCTIONS + ******************************************************************************/ + +Decoder::Status CBORMessageDecoder::decode(Message * message, uint8_t const * const payload, size_t& length) +{ + CborValue main_iter, array_iter; + CborTag tag; + CborParser parser; + + if (cbor_parser_init(payload, length, 0, &parser, &main_iter) != CborNoError) + return Decoder::Status::Error; + + if (main_iter.type != CborTagType) + return Decoder::Status::Error; + + if (cbor_value_get_tag(&main_iter, &tag) == CborNoError) { + message->id = toCommandId(CBORCommandTag(tag)); + } + + if (cbor_value_advance(&main_iter) != CborNoError) { + return Decoder::Status::Error; + } + + ArrayParserState current_state = ArrayParserState::EnterArray, + next_state = ArrayParserState::Error; + + while (current_state != ArrayParserState::Complete) { + switch (current_state) { + case ArrayParserState::EnterArray : next_state = handle_EnterArray(&main_iter, &array_iter); break; + case ArrayParserState::ParseParam : next_state = handle_Param(&array_iter, message); break; + case ArrayParserState::LeaveArray : next_state = handle_LeaveArray(&main_iter, &array_iter); break; + case ArrayParserState::Complete : return Decoder::Status::Complete; + case ArrayParserState::MessageNotSupported : return Decoder::Status::Error; + case ArrayParserState::Error : return Decoder::Status::Error; + } + + current_state = next_state; + } + + return Decoder::Status::Complete; +} + +/****************************************************************************** + PRIVATE MEMBER FUNCTIONS + ******************************************************************************/ + +bool copyCBORStringToArray(CborValue * param, char * dest, size_t dest_size) { + if (cbor_value_is_text_string(param)) { + // NOTE: keep in mind that _cbor_value_copy_string tries to put a \0 at the end of the string + if(_cbor_value_copy_string(param, dest, &dest_size, NULL) == CborNoError) { + return true; + } + } + + return false; +} + +// FIXME dest_size should be also returned, the copied byte array can have a different size from the starting one +// for the time being we need this on SHA256 only +bool copyCBORByteToArray(CborValue * param, uint8_t * dest, size_t dest_size) { + if (cbor_value_is_byte_string(param)) { + // NOTE: keep in mind that _cbor_value_copy_string tries to put a \0 at the end of the string + if(_cbor_value_copy_string(param, dest, &dest_size, NULL) == CborNoError) { + return true; + } + } + + return false; +} + +CBORMessageDecoder::ArrayParserState CBORMessageDecoder::handle_EnterArray(CborValue * main_iter, CborValue * array_iter) { + ArrayParserState next_state = ArrayParserState::Error; + if (cbor_value_get_type(main_iter) == CborArrayType) { + if (cbor_value_enter_container(main_iter, array_iter) == CborNoError) { + next_state = ArrayParserState::ParseParam; + } + } + + return next_state; +} + +CBORMessageDecoder::ArrayParserState CBORMessageDecoder::handle_LeaveArray(CborValue * main_iter, CborValue * array_iter) { + // Advance to the next parameter (the last one in the array) + if (cbor_value_advance(array_iter) == CborNoError) { + // Leave the array + if (cbor_value_leave_container(main_iter, array_iter) == CborNoError) { + return ArrayParserState::Complete; + } + } + + return ArrayParserState::Error; +} + +/****************************************************************************** + MESSAGE DECODE FUNCTIONS + ******************************************************************************/ + +CBORMessageDecoder::ArrayParserState CBORMessageDecoder::decodeThingUpdateCmd(CborValue * param, Message * message) { + ThingUpdateCmd * thingCommand = (ThingUpdateCmd *) message; + + // Message is composed of a single parameter, a string (thing_id) + if (!copyCBORStringToArray(param, thingCommand->params.thing_id, sizeof(thingCommand->params.thing_id))) { + return ArrayParserState::Error; + } + + return ArrayParserState::LeaveArray; +} + +CBORMessageDecoder::ArrayParserState CBORMessageDecoder::decodeTimezoneCommandDown(CborValue * param, Message * message) { + TimezoneCommandDown * setTz = (TimezoneCommandDown *) message; + + // Message is composed of 2 parameters, offset 32-bit signed integer and until 32-bit unsigned integer + // Get offset + if (cbor_value_is_integer(param)) { + int64_t val = 0; + if (cbor_value_get_int64(param, &val) == CborNoError) { + setTz->params.offset = static_cast(val); + } + } + + // Next + if (cbor_value_advance(param) != CborNoError) { + return ArrayParserState::Error; + } + + // Get until + if (cbor_value_is_integer(param)) { + uint64_t val = 0; + if (cbor_value_get_uint64(param, &val) == CborNoError) { + setTz->params.until = static_cast(val); + } + } + + return ArrayParserState::LeaveArray; +} + +CBORMessageDecoder::ArrayParserState CBORMessageDecoder::decodeLastValuesUpdateCmd(CborValue * param, Message * message) { + LastValuesUpdateCmd * setLv = (LastValuesUpdateCmd *) message; + + // Message is composed by a single parameter, a variable length byte array. + if (cbor_value_is_byte_string(param)) { + // Cortex M0 is not able to assign a value to pointed memory that is not 32bit aligned + // we use a support variable to cope with that + size_t s; + if (cbor_value_dup_byte_string(param, &setLv->params.last_values, &s, NULL) != CborNoError) { + return ArrayParserState::Error; + } + + setLv->params.length = s; + } + + return ArrayParserState::LeaveArray; +} + +CBORMessageDecoder::ArrayParserState CBORMessageDecoder::decodeOtaUpdateCmdDown(CborValue * param, Message * message) { + CborError error = CborNoError; + OtaUpdateCmdDown * ota = (OtaUpdateCmdDown *) message; + + // Message is composed 4 parameters: id, url, initialSha, finalSha + if (!copyCBORByteToArray(param, ota->params.id, sizeof(ota->params.id))) { + return ArrayParserState::Error; + } + + error = cbor_value_advance(param); + + if ((error != CborNoError) || !copyCBORStringToArray(param, ota->params.url, sizeof(ota->params.url))) { + return ArrayParserState::Error; + } + + error = cbor_value_advance(param); + + if ((error != CborNoError) || !copyCBORByteToArray(param, ota->params.initialSha256, sizeof(ota->params.initialSha256))) { + return ArrayParserState::Error; + } + + error = cbor_value_advance(param); + + if ((error != CborNoError) || !copyCBORByteToArray(param, ota->params.finalSha256, sizeof(ota->params.finalSha256))) { + return ArrayParserState::Error; + } + + return ArrayParserState::LeaveArray; +} + +CBORMessageDecoder::ArrayParserState CBORMessageDecoder::handle_Param(CborValue * param, Message * message) { + + switch (message->id) + { + case CommandId::ThingUpdateCmdId: + return CBORMessageDecoder::decodeThingUpdateCmd(param, message); + + case CommandId::TimezoneCommandDownId: + return CBORMessageDecoder::decodeTimezoneCommandDown(param, message); + + case CommandId::LastValuesUpdateCmdId: + return CBORMessageDecoder::decodeLastValuesUpdateCmd(param, message); + + case CommandId::OtaUpdateCmdDownId: + return CBORMessageDecoder::decodeOtaUpdateCmdDown(param, message); + + default: + return ArrayParserState::MessageNotSupported; + } + + return ArrayParserState::LeaveArray; +} diff --git a/src/cbor/MessageDecoder.h b/src/cbor/MessageDecoder.h new file mode 100644 index 000000000..712289c9d --- /dev/null +++ b/src/cbor/MessageDecoder.h @@ -0,0 +1,74 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef ARDUINO_CBOR_MESSAGE_DECODER_H_ +#define ARDUINO_CBOR_MESSAGE_DECODER_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include + +#undef max +#undef min +#include + +#include "CBOR.h" +#include "../interfaces/Decoder.h" +#include "lib/tinycbor/cbor-lib.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + +class CBORMessageDecoder: public Decoder +{ +public: + CBORMessageDecoder() { } + CBORMessageDecoder(CBORMessageDecoder const &) { } + + /* decode a CBOR payload received from the cloud */ + Decoder::Status decode(Message * msg, uint8_t const * const payload, size_t& length); + +private: + + enum class DecoderState { + Success, + MessageNotSupported, + MalformedMessage, + Error + }; + + enum class ArrayParserState { + EnterArray, + ParseParam, + LeaveArray, + Complete, + Error, + MessageNotSupported + }; + + ArrayParserState handle_EnterArray(CborValue * main_iter, CborValue * array_iter); + ArrayParserState handle_Param(CborValue * param, Message * message); + ArrayParserState handle_LeaveArray(CborValue * main_iter, CborValue * array_iter); + + bool ifNumericConvertToDouble(CborValue * value_iter, double * numeric_val); + double convertCborHalfFloatToDouble(uint16_t const half_val); + + // Message specific decoders + ArrayParserState decodeThingUpdateCmd(CborValue * param, Message * message); + ArrayParserState decodeTimezoneCommandDown(CborValue * param, Message * message); + ArrayParserState decodeLastValuesUpdateCmd(CborValue * param, Message * message); + ArrayParserState decodeOtaUpdateCmdDown(CborValue * param, Message * message); + +}; + +#endif /* ARDUINO_CBOR_MESSAGE_DECODER_H_ */ diff --git a/src/cbor/MessageEncoder.cpp b/src/cbor/MessageEncoder.cpp new file mode 100644 index 000000000..cdeb0f8ed --- /dev/null +++ b/src/cbor/MessageEncoder.cpp @@ -0,0 +1,175 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include "CBOREncoder.h" + +#undef max +#undef min +#include +#include + +#include "lib/tinycbor/cbor-lib.h" +#include "MessageEncoder.h" + +/****************************************************************************** + * PUBLIC MEMBER FUNCTIONS + ******************************************************************************/ + +Encoder::Status CBORMessageEncoder::encode(Message * message, uint8_t * data, size_t& len) +{ + EncoderState current_state = EncoderState::EncodeTag, + next_state = EncoderState::Error; + + CborEncoder encoder; + CborEncoder arrayEncoder; + + cbor_encoder_init(&encoder, data, len, 0); + + while (current_state != EncoderState::Complete) { + + switch (current_state) { + case EncoderState::EncodeTag : next_state = handle_EncodeTag(&encoder, message); break; + case EncoderState::EncodeArray : next_state = handle_EncodeArray(&encoder, &arrayEncoder, message); break; + case EncoderState::EncodeParam : next_state = handle_EncodeParam(&arrayEncoder, message); break; + case EncoderState::CloseArray : next_state = handle_CloseArray(&encoder, &arrayEncoder); break; + case EncoderState::Complete : /* Nothing to do */ break; + case EncoderState::MessageNotSupported : + case EncoderState::Error : return Encoder::Status::Error; + } + + current_state = next_state; + } + + len = cbor_encoder_get_buffer_size(&encoder, data); + + return Encoder::Status::Complete; +} + +/****************************************************************************** + PRIVATE MEMBER FUNCTIONS + ******************************************************************************/ + +CBORMessageEncoder::EncoderState CBORMessageEncoder::handle_EncodeTag(CborEncoder * encoder, Message * message) +{ + CborTag commandTag = toCBORCommandTag(message->id); + if (commandTag == CBORCommandTag::CBORUnknownCmdTag16b || + commandTag == CBORCommandTag::CBORUnknownCmdTag32b || + commandTag == CBORCommandTag::CBORUnknownCmdTag64b || + cbor_encode_tag(encoder, commandTag) != CborNoError) { + return EncoderState::Error; + } + + return EncoderState::EncodeArray; +} + +CBORMessageEncoder::EncoderState CBORMessageEncoder::handle_EncodeArray(CborEncoder * encoder, CborEncoder * array_encoder, Message * message) +{ + // Set array size based on the message id + size_t array_size = 0; + switch (message->id) + { + case CommandId::OtaBeginUpId: + array_size = 1; + break; + case CommandId::ThingBeginCmdId: + array_size = 1; + break; + case CommandId::DeviceBeginCmdId: + array_size = 1; + break; + case CommandId::LastValuesBeginCmdId: + break; + case CommandId::OtaProgressCmdUpId: + array_size = 4; + break; + case CommandId::TimezoneCommandUpId: + break; + default: + return EncoderState::MessageNotSupported; + } + + // Start an array with fixed width based on message type + if (cbor_encoder_create_array(encoder, array_encoder, array_size) != CborNoError){ + return EncoderState::Error; + } + + return EncoderState::EncodeParam; +} + +CBORMessageEncoder::EncoderState CBORMessageEncoder::handle_EncodeParam(CborEncoder * array_encoder, Message * message) +{ + CborError error = CborNoError; + switch (message->id) + { + case CommandId::OtaBeginUpId: + error = CBORMessageEncoder::encodeOtaBeginUp(array_encoder, message); + break; + case CommandId::ThingBeginCmdId: + error = CBORMessageEncoder::encodeThingBeginCmd(array_encoder, message); + break; + case CommandId::DeviceBeginCmdId: + error = CBORMessageEncoder::encodeDeviceBeginCmd(array_encoder, message); + break; + case CommandId::LastValuesBeginCmdId: + break; + case CommandId::OtaProgressCmdUpId: + error = CBORMessageEncoder::encodeOtaProgressCmdUp(array_encoder, message); + break; + case CommandId::TimezoneCommandUpId: + break; + default: + return EncoderState::MessageNotSupported; + } + + return (error != CborNoError) ? EncoderState::Error : EncoderState::CloseArray; +} + +CBORMessageEncoder::EncoderState CBORMessageEncoder::handle_CloseArray(CborEncoder * encoder, CborEncoder * array_encoder) +{ + CborError error = cbor_encoder_close_container(encoder, array_encoder); + + return (error != CborNoError) ? EncoderState::Error : EncoderState::Complete; +} + +// Message specific encoders +CborError CBORMessageEncoder::encodeOtaBeginUp(CborEncoder * array_encoder, Message * message) +{ + OtaBeginUp * otaBeginUp = (OtaBeginUp *) message; + CHECK_CBOR(cbor_encode_byte_string(array_encoder, otaBeginUp->params.sha, SHA256_SIZE)); + return CborNoError; +} + +CborError CBORMessageEncoder::encodeThingBeginCmd(CborEncoder * array_encoder, Message * message) +{ + ThingBeginCmd * thingBeginCmd = (ThingBeginCmd *) message; + CHECK_CBOR(cbor_encode_text_stringz(array_encoder, thingBeginCmd->params.thing_id)); + return CborNoError; +} + +CborError CBORMessageEncoder::encodeDeviceBeginCmd(CborEncoder * array_encoder, Message * message) +{ + DeviceBeginCmd * deviceBeginCmd = (DeviceBeginCmd *) message; + CHECK_CBOR(cbor_encode_text_stringz(array_encoder, deviceBeginCmd->params.lib_version)); + return CborNoError; +} + +CborError CBORMessageEncoder::encodeOtaProgressCmdUp(CborEncoder * array_encoder, Message * message) +{ + OtaProgressCmdUp * ota = (OtaProgressCmdUp *)message; + CHECK_CBOR(cbor_encode_byte_string(array_encoder, ota->params.id, ID_SIZE)); + CHECK_CBOR(cbor_encode_simple_value(array_encoder, ota->params.state)); + CHECK_CBOR(cbor_encode_int(array_encoder, ota->params.state_data)); + CHECK_CBOR(cbor_encode_uint(array_encoder, ota->params.time)); + return CborNoError; +} diff --git a/src/cbor/MessageEncoder.h b/src/cbor/MessageEncoder.h new file mode 100644 index 000000000..86792eb19 --- /dev/null +++ b/src/cbor/MessageEncoder.h @@ -0,0 +1,65 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef ARDUINO_CBOR_MESSAGE_ENCODER_H_ +#define ARDUINO_CBOR_MESSAGE_ENCODER_H_ + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include + +#undef max +#undef min +#include + +#include "CBOR.h" +#include "../interfaces/Encoder.h" +#include "lib/tinycbor/cbor-lib.h" + +/****************************************************************************** + * CLASS DECLARATION + ******************************************************************************/ + +class CBORMessageEncoder: public Encoder +{ + +public: + CBORMessageEncoder() { } + CBORMessageEncoder(CborEncoder const &) { } + Encoder::Status encode(Message * message, uint8_t * data, size_t& len); + +private: + + enum class EncoderState + { + EncodeTag, + EncodeArray, + EncodeParam, + CloseArray, + MessageNotSupported, + Complete, + Error + }; + + EncoderState handle_EncodeTag(CborEncoder * encoder, Message * message); + EncoderState handle_EncodeArray(CborEncoder * encoder, CborEncoder * array_encoder, Message * message); + EncoderState handle_EncodeParam(CborEncoder * array_encoder, Message * message); + EncoderState handle_CloseArray(CborEncoder * encoder, CborEncoder * array_encoder); + + // Message specific encoders + CborError encodeThingBeginCmd(CborEncoder * array_encoder, Message * message); + CborError encodeOtaBeginUp(CborEncoder * array_encoder, Message * message); + CborError encodeDeviceBeginCmd(CborEncoder * array_encoder, Message * message); + CborError encodeOtaProgressCmdUp(CborEncoder * array_encoder, Message * message); +}; + +#endif /* ARDUINO_CBOR_MESSAGE_ENCODER_H_ */ From 9c57868f715059feec2c02aa093c59cf729bd87d Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 17 Apr 2024 17:31:59 +0200 Subject: [PATCH 3/6] Add tests for encoder and decoder --- extras/test/CMakeLists.txt | 5 + extras/test/src/test_command_decode.cpp | 736 ++++++++++++++++++++++++ extras/test/src/test_command_encode.cpp | 322 +++++++++++ 3 files changed, 1063 insertions(+) create mode 100644 extras/test/src/test_command_decode.cpp create mode 100644 extras/test/src/test_command_encode.cpp diff --git a/extras/test/CMakeLists.txt b/extras/test/CMakeLists.txt index 2588c27c0..2aab9ab9b 100644 --- a/extras/test/CMakeLists.txt +++ b/extras/test/CMakeLists.txt @@ -36,6 +36,8 @@ set(TEST_SRCS src/test_CloudSchedule.cpp src/test_decode.cpp src/test_encode.cpp + src/test_command_decode.cpp + src/test_command_encode.cpp src/test_publishEvery.cpp src/test_publishOnChange.cpp src/test_publishOnChangeRateLimit.cpp @@ -55,6 +57,9 @@ set(TEST_DUT_SRCS ../../src/property/PropertyContainer.cpp ../../src/cbor/CBORDecoder.cpp ../../src/cbor/CBOREncoder.cpp + ../../src/cbor/MessageDecoder.cpp + ../../src/cbor/MessageEncoder.cpp + ../../src/cbor/CBOR.cpp ../../src/cbor/lib/tinycbor/src/cborencoder.c ../../src/cbor/lib/tinycbor/src/cborencoder_close_container_checked.c ../../src/cbor/lib/tinycbor/src/cborerrorstrings.c diff --git a/extras/test/src/test_command_decode.cpp b/extras/test/src/test_command_decode.cpp new file mode 100644 index 000000000..818d02136 --- /dev/null +++ b/extras/test/src/test_command_decode.cpp @@ -0,0 +1,736 @@ +/* + Copyright (c) 2024 Arduino. All rights reserved. +*/ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include + +#include + +#include +#include + +/****************************************************************************** + TEST CODE + ******************************************************************************/ + +SCENARIO("Test the decoding of command messages") { + /****************************************************************************/ + + WHEN("Decode the ThingUpdateCmdId message") + { + CommandDown command; + /* + DA 00010400 # tag(66560) + 81 # array(1) + 78 24 # text(36) + 65343439346435352D383732612D346664322D393634362D393266383739343933393463 # "e4494d55-872a-4fd2-9646-92f87949394c" + */ + uint8_t const payload[] = {0xDA, 0x00, 0x01, 0x04, 0x00, 0x81, 0x78, 0x24, + 0x65, 0x34, 0x34, 0x39, 0x34, 0x64, 0x35, 0x35, + 0x2D, 0x38, 0x37, 0x32, 0x61, 0x2D, 0x34, 0x66, + 0x64, 0x32, 0x2D, 0x39, 0x36, 0x34, 0x36, 0x2D, + 0x39, 0x32, 0x66, 0x38, 0x37, 0x39, 0x34, 0x39, + 0x33, 0x39, 0x34, 0x63}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + const char *thingIdToMatch = "e4494d55-872a-4fd2-9646-92f87949394c"; + + THEN("The decode is successful") { + REQUIRE(err == Decoder::Status::Complete); + REQUIRE(strcmp(command.thingUpdateCmd.params.thing_id, thingIdToMatch) == 0); + REQUIRE(command.c.id == ThingUpdateCmdId); + } + } + + /****************************************************************************/ + + WHEN("Decode the ThingUpdateCmdId message containing a number instead of a string") + { + CommandDown command; + /* + DA 00010400 # tag(66560) + 81 # array(1) + 1A 65DCB821 # unsigned(1708963873) + */ + uint8_t const payload[] = {0xDA, 0x00, 0x01, 0x04, 0x00, 0x81, 0x1A, 0x65, + 0xDC, 0xB8, 0x21}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode the SetTimezoneCommand message") + { + CommandDown command; + + /* + DA 00010764 # tag(67840) + 82 # array(2) + 1A 65DCB821 # unsigned(1708963873) + 1A 78ACA191 # unsigned(2024579473) + */ + + uint8_t const payload[] = {0xDA, 0x00, 0x01, 0x09, 0x00, 0x82, 0x1A, 0x65, + 0xDC, 0xB8, 0x21, 0x1A, 0x78, 0xAC, 0xA1, 0x91}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is successful") { + REQUIRE(err == Decoder::Status::Complete); + REQUIRE(command.timezoneCommandDown.params.offset == (uint32_t)1708963873); + REQUIRE(command.timezoneCommandDown.params.until == (uint32_t)2024579473); + REQUIRE(command.c.id == TimezoneCommandDownId); + } + } + + /****************************************************************************/ + + WHEN("Decode the LastValuesUpdateCmd message") + { + CommandDown command; + + /* + DA 00010600 # tag(67072) + 81 # array(1) + 4D # bytes(13) + 00010203040506070809101112 # "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\u0010\u0011\u0012" + + */ + + uint8_t const payload[] = {0xDA, 0x00, 0x01, 0x06, 0x00, 0x81, 0x4D, 0x00, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x10, 0x11, 0x12}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is successful") { + REQUIRE(err == Decoder::Status::Complete); + REQUIRE(command.lastValuesUpdateCmd.params.length == 13); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[0] == (uint8_t)0x00); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[1] == (uint8_t)0x01); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[2] == (uint8_t)0x02); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[3] == (uint8_t)0x03); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[4] == (uint8_t)0x04); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[5] == (uint8_t)0x05); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[6] == (uint8_t)0x06); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[7] == (uint8_t)0x07); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[8] == (uint8_t)0x08); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[9] == (uint8_t)0x09); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[10] == (uint8_t)0x10); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[11] == (uint8_t)0x11); + REQUIRE(command.lastValuesUpdateCmd.params.last_values[12] == (uint8_t)0x12); + REQUIRE(command.c.id == LastValuesUpdateCmdId); + } + free(command.lastValuesUpdateCmd.params.last_values); + } + + /****************************************************************************/ + + WHEN("Decode the OtaUpdateCmdDown message") + { + CommandDown command; + + /* + DA 00010100 # tag(65792) + 84 # array(4) + 50 # bytes(16) + C73CB045F9C2434585AFFA36A307BFE7"\xC7<\xB0E\xF9\xC2CE\x85\xAF\xFA6\xA3\a\xBF\xE7" + 78 72 # text(141) + 68747470733A2F2F626F617264732D69 + 6E742E6F6E69756472612E63632F7374 + 6F726167652F6669726D776172652F76 + 312F6466316561633963376264363334 + 37336666666231313766393837333730 + 33653465633935353933316532363766 + 32363236326230393439626331366463 + 3439 # "https://boards-int.oniudra.cc/storage/firmware/v1/df1eac9c7bd63473fffb117f9873703e4ec955931e267f26262b0949bc16dc49" + 58 20 # bytes(32) + 00000000000000000000000000000000 + 00000000000000000000000000000000# "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000" + 58 20 # bytes(32) + DF1EAC9C7BD63473FFFB117F9873703E + 4EC955931E267F26262B0949BC16DC49# "\xDF\u001E\xAC\x9C{\xD64s\xFF\xFB\u0011\u007F\x98sp>N\xC9U\x93\u001E&\u007F&&+\tI\xBC\u0016\xDCI" + + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x01, 0x00, 0x84, 0x50, 0xc7, + 0x3c, 0xb0, 0x45, 0xf9, 0xc2, 0x43, 0x45, 0x85, + 0xaf, 0xfa, 0x36, 0xa3, 0x07, 0xbf, 0xe7, 0x78, + 0x72, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, + 0x2f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x2d, + 0x69, 0x6e, 0x74, 0x2e, 0x6f, 0x6e, 0x69, 0x75, + 0x64, 0x72, 0x61, 0x2e, 0x63, 0x63, 0x2f, 0x73, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x66, + 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2f, + 0x76, 0x31, 0x2f, 0x64, 0x66, 0x31, 0x65, 0x61, + 0x63, 0x39, 0x63, 0x37, 0x62, 0x64, 0x36, 0x33, + 0x34, 0x37, 0x33, 0x66, 0x66, 0x66, 0x62, 0x31, + 0x31, 0x37, 0x66, 0x39, 0x38, 0x37, 0x33, 0x37, + 0x30, 0x33, 0x65, 0x34, 0x65, 0x63, 0x39, 0x35, + 0x35, 0x39, 0x33, 0x31, 0x65, 0x32, 0x36, 0x37, + 0x66, 0x32, 0x36, 0x32, 0x36, 0x32, 0x62, 0x30, + 0x39, 0x34, 0x39, 0x62, 0x63, 0x31, 0x36, 0x64, + 0x63, 0x34, 0x39, 0x58, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x20, 0xdf, + 0x1e, 0xac, 0x9c, 0x7b, 0xd6, 0x34, 0x73, 0xff, + 0xfb, 0x11, 0x7f, 0x98, 0x73, 0x70, 0x3e, 0x4e, + 0xc9, 0x55, 0x93, 0x1e, 0x26, 0x7f, 0x26, 0x26, + 0x2b, 0x09, 0x49, 0xbc, 0x16, 0xdc, 0x49}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + uint8_t otaIdToMatch[ID_SIZE] = { 0xC7, 0x3C, 0xB0, 0x45, 0xF9, 0xC2, 0x43, 0x45, + 0x85, 0xAF, 0xFA, 0x36, 0xA3, 0x07, 0xBF, 0xE7}; + const char *urlToMatch = "https://boards-int.oniudra.cc/storage/firmware/v1/df1eac9c7bd63473fffb117f9873703e4ec955931e267f26262b0949bc16dc49"; + + THEN("The decode is successful") { + REQUIRE(err == Decoder::Status::Complete); + REQUIRE(memcmp(command.otaUpdateCmdDown.params.id, otaIdToMatch, ID_SIZE) == 0); + REQUIRE(strcmp(command.otaUpdateCmdDown.params.url, urlToMatch) == 0); + // Initial SHA256 check + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[0] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[1] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[2] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[3] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[4] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[5] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[6] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[7] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[8] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[9] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[10] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[11] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[12] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[13] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[14] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[15] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[16] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[17] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[18] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[19] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[20] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[21] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[22] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[23] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[24] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[25] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[26] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[27] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[28] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[29] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[30] == (uint8_t)0x00); + REQUIRE(command.otaUpdateCmdDown.params.initialSha256[31] == (uint8_t)0x00); + + // Final SHA256 check + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[0] == (uint8_t)0xdf); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[1] == (uint8_t)0x1e); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[2] == (uint8_t)0xac); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[3] == (uint8_t)0x9c); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[4] == (uint8_t)0x7b); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[5] == (uint8_t)0xd6); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[6] == (uint8_t)0x34); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[7] == (uint8_t)0x73); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[8] == (uint8_t)0xff); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[9] == (uint8_t)0xfb); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[10] == (uint8_t)0x11); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[11] == (uint8_t)0x7f); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[12] == (uint8_t)0x98); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[13] == (uint8_t)0x73); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[14] == (uint8_t)0x70); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[15] == (uint8_t)0x3e); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[16] == (uint8_t)0x4e); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[17] == (uint8_t)0xc9); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[18] == (uint8_t)0x55); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[19] == (uint8_t)0x93); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[20] == (uint8_t)0x1e); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[21] == (uint8_t)0x26); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[22] == (uint8_t)0x7f); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[23] == (uint8_t)0x26); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[24] == (uint8_t)0x26); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[25] == (uint8_t)0x2b); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[26] == (uint8_t)0x09); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[27] == (uint8_t)0x49); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[28] == (uint8_t)0xbc); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[29] == (uint8_t)0x16); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[30] == (uint8_t)0xdc); + REQUIRE(command.otaUpdateCmdDown.params.finalSha256[31] == (uint8_t)0x49); + + REQUIRE(command.c.id == OtaUpdateCmdDownId); + } + } + +/****************************************************************************/ + + WHEN("Decode the OtaUpdateCmdDown message with out of order fields 1") + { + CommandDown command; + + /* + DA 00010100 # tag(65792) + 84 # array(4) + 78 72 # text(141) + 68747470733A2F2F626F617264732D69 + 6E742E6F6E69756472612E63632F7374 + 6F726167652F6669726D776172652F76 + 312F6466316561633963376264363334 + 37336666666231313766393837333730 + 33653465633935353933316532363766 + 32363236326230393439626331366463 + 3439 # "https://boards-int.oniudra.cc/storage/firmware/v1/df1eac9c7bd63473fffb117f9873703e4ec955931e267f26262b0949bc16dc49" + 50 # bytes(16) + C73CB045F9C2434585AFFA36A307BFE7"\xC7<\xB0E\xF9\xC2CE\x85\xAF\xFA6\xA3\a\xBF\xE7" + 58 20 # bytes(32) + 00000000000000000000000000000000 + 00000000000000000000000000000000# "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000" + 58 20 # bytes(32) + DF1EAC9C7BD63473FFFB117F9873703E + 4EC955931E267F26262B0949BC16DC49# "\xDF\u001E\xAC\x9C{\xD64s\xFF\xFB\u0011\u007F\x98sp>N\xC9U\x93\u001E&\u007F&&+\tI\xBC\u0016\xDCI" + + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x01, 0x00, 0x84, 0x78, 0x72, + 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, + 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x2d, 0x69, + 0x6e, 0x74, 0x2e, 0x6f, 0x6e, 0x69, 0x75, 0x64, + 0x72, 0x61, 0x2e, 0x63, 0x63, 0x2f, 0x73, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x66, 0x69, + 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2f, 0x76, + 0x31, 0x2f, 0x64, 0x66, 0x31, 0x65, 0x61, 0x63, + 0x39, 0x63, 0x37, 0x62, 0x64, 0x36, 0x33, 0x34, + 0x37, 0x33, 0x66, 0x66, 0x66, 0x62, 0x31, 0x31, + 0x37, 0x66, 0x39, 0x38, 0x37, 0x33, 0x37, 0x30, + 0x33, 0x65, 0x34, 0x65, 0x63, 0x39, 0x35, 0x35, + 0x39, 0x33, 0x31, 0x65, 0x32, 0x36, 0x37, 0x66, + 0x32, 0x36, 0x32, 0x36, 0x32, 0x62, 0x30, 0x39, + 0x34, 0x39, 0x62, 0x63, 0x31, 0x36, 0x64, 0x63, + 0x34, 0x39, 0x50, 0xc7, 0x3c, 0xb0, 0x45, 0xf9, + 0xc2, 0x43, 0x45, 0x85, 0xaf, 0xfa, 0x36, 0xa3, + 0x07, 0xbf, 0xe7, 0x58, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x20, 0xdf, + 0x1e, 0xac, 0x9c, 0x7b, 0xd6, 0x34, 0x73, 0xff, + 0xfb, 0x11, 0x7f, 0x98, 0x73, 0x70, 0x3e, 0x4e, + 0xc9, 0x55, 0x93, 0x1e, 0x26, 0x7f, 0x26, 0x26, + 0x2b, 0x09, 0x49, 0xbc, 0x16, 0xdc, 0x49}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is successful") { + REQUIRE(err == Decoder::Status::Error); + } + } + +/****************************************************************************/ + + WHEN("Decode the OtaUpdateCmdDown message with out of order fields 2") + { + CommandDown command; + + /* + DA 00010100 # tag(65792) + 84 # array(4) + 50 # bytes(16) + C73CB045F9C2434585AFFA36A307BFE7"\xC7<\xB0E\xF9\xC2CE\x85\xAF\xFA6\xA3\a\xBF\xE7" + 58 20 # bytes(32) + 00000000000000000000000000000000 + 00000000000000000000000000000000# "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000" + 78 72 # text(141) + 68747470733A2F2F626F617264732D69 + 6E742E6F6E69756472612E63632F7374 + 6F726167652F6669726D776172652F76 + 312F6466316561633963376264363334 + 37336666666231313766393837333730 + 33653465633935353933316532363766 + 32363236326230393439626331366463 + 3439 # "https://boards-int.oniudra.cc/storage/firmware/v1/df1eac9c7bd63473fffb117f9873703e4ec955931e267f26262b0949bc16dc49" + 58 20 # bytes(32) + DF1EAC9C7BD63473FFFB117F9873703E + 4EC955931E267F26262B0949BC16DC49# "\xDF\u001E\xAC\x9C{\xD64s\xFF\xFB\u0011\u007F\x98sp>N\xC9U\x93\u001E&\u007F&&+\tI\xBC\u0016\xDCI" + + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x01, 0x00, 0x84, 0x50, 0xc7, + 0x3c, 0xb0, 0x45, 0xf9, 0xc2, 0x43, 0x45, 0x85, + 0xaf, 0xfa, 0x36, 0xa3, 0x07, 0xbf, 0xe7, 0x58, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x78, 0x72, 0x68, 0x74, 0x74, 0x70, 0x73, + 0x3a, 0x2f, 0x2f, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x73, 0x2d, 0x69, 0x6e, 0x74, 0x2e, 0x6f, 0x6e, + 0x69, 0x75, 0x64, 0x72, 0x61, 0x2e, 0x63, 0x63, + 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x2f, 0x66, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, + 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x66, 0x31, + 0x65, 0x61, 0x63, 0x39, 0x63, 0x37, 0x62, 0x64, + 0x36, 0x33, 0x34, 0x37, 0x33, 0x66, 0x66, 0x66, + 0x62, 0x31, 0x31, 0x37, 0x66, 0x39, 0x38, 0x37, + 0x33, 0x37, 0x30, 0x33, 0x65, 0x34, 0x65, 0x63, + 0x39, 0x35, 0x35, 0x39, 0x33, 0x31, 0x65, 0x32, + 0x36, 0x37, 0x66, 0x32, 0x36, 0x32, 0x36, 0x32, + 0x62, 0x30, 0x39, 0x34, 0x39, 0x62, 0x63, 0x31, + 0x36, 0x64, 0x63, 0x34, 0x39, 0x58, 0x20, 0xdf, + 0x1e, 0xac, 0x9c, 0x7b, 0xd6, 0x34, 0x73, 0xff, + 0xfb, 0x11, 0x7f, 0x98, 0x73, 0x70, 0x3e, 0x4e, + 0xc9, 0x55, 0x93, 0x1e, 0x26, 0x7f, 0x26, 0x26, + 0x2b, 0x09, 0x49, 0xbc, 0x16, 0xdc, 0x49}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is successful") { + REQUIRE(err == Decoder::Status::Error); + } + } + +/****************************************************************************/ + + WHEN("Decode the OtaUpdateCmdDown message with corrupted fields 1") + { + CommandDown command; + + /* + DA 00010100 # tag(65792) + 84 # array(4) + 50 # bytes(16) + C73CB045F9C2434585AFFA36A307BFE7"\xC7<\xB0E\xF9\xC2CE\x85\xAF\xFA6\xA3\a\xBF\xE7" + 78 72 # text(141) + 68747470733A2F2F626F617264732D69 + 6E742E6F6E69756472612E63632F7374 + 6F726167652F6669726D776172652F76 + 312F6466316561633963376264363334 + 37336666666231313766393837333730 + 33653465633935353933316532363766 + 32363236326230393439626331366463 + 3439 # "https://boards-int.oniudra.cc/storage/firmware/v1/df1eac9c7bd63473fffb117f9873703e4ec955931e267f26262b0949bc16dc49" + 1A 65DCB821 # unsigned(1708963873) + 58 20 # bytes(32) + DF1EAC9C7BD63473FFFB117F9873703E + 4EC955931E267F26262B0949BC16DC49# "\xDF\u001E\xAC\x9C{\xD64s\xFF\xFB\u0011\u007F\x98sp>N\xC9U\x93\u001E&\u007F&&+\tI\xBC\u0016\xDCI" + + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x01, 0x00, 0x84, 0x50, 0xc7, + 0x3c, 0xb0, 0x45, 0xf9, 0xc2, 0x43, 0x45, 0x85, + 0xaf, 0xfa, 0x36, 0xa3, 0x07, 0xbf, 0xe7, 0x78, + 0x72, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, + 0x2f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x2d, + 0x69, 0x6e, 0x74, 0x2e, 0x6f, 0x6e, 0x69, 0x75, + 0x64, 0x72, 0x61, 0x2e, 0x63, 0x63, 0x2f, 0x73, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x66, + 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2f, + 0x76, 0x31, 0x2f, 0x64, 0x66, 0x31, 0x65, 0x61, + 0x63, 0x39, 0x63, 0x37, 0x62, 0x64, 0x36, 0x33, + 0x34, 0x37, 0x33, 0x66, 0x66, 0x66, 0x62, 0x31, + 0x31, 0x37, 0x66, 0x39, 0x38, 0x37, 0x33, 0x37, + 0x30, 0x33, 0x65, 0x34, 0x65, 0x63, 0x39, 0x35, + 0x35, 0x39, 0x33, 0x31, 0x65, 0x32, 0x36, 0x37, + 0x66, 0x32, 0x36, 0x32, 0x36, 0x32, 0x62, 0x30, + 0x39, 0x34, 0x39, 0x62, 0x63, 0x31, 0x36, 0x64, + 0x63, 0x34, 0x39, 0x1A, 0x65, 0xDC, 0xB8, 0x21, + 0x58, 0x20, 0xdf, 0x1e, 0xac, 0x9c, 0x7b, 0xd6, + 0x34, 0x73, 0xff, 0xfb, 0x11, 0x7f, 0x98, 0x73, + 0x70, 0x3e, 0x4e, 0xc9, 0x55, 0x93, 0x1e, 0x26, + 0x7f, 0x26, 0x26, 0x2b, 0x09, 0x49, 0xbc, 0x16, + 0xdc, 0x49}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is successful") { + REQUIRE(err == Decoder::Status::Error); + } + } + +/****************************************************************************/ + + WHEN("Decode the OtaUpdateCmdDown message with corrupted fields 2") + { + CommandDown command; + + /* + DA 00010100 # tag(65792) + 84 # array(4) + 50 # bytes(16) + C73CB045F9C2434585AFFA36A307BFE7"\xC7<\xB0E\xF9\xC2CE\x85\xAF\xFA6\xA3\a\xBF\xE7" + 78 72 # text(141) + 68747470733A2F2F626F617264732D69 + 6E742E6F6E69756472612E63632F7374 + 6F726167652F6669726D776172652F76 + 312F6466316561633963376264363334 + 37336666666231313766393837333730 + 33653465633935353933316532363766 + 32363236326230393439626331366463 + 3439 # "https://boards-int.oniudra.cc/storage/firmware/v1/df1eac9c7bd63473fffb117f9873703e4ec955931e267f26262b0949bc16dc49" + 58 20 # bytes(32) + DF1EAC9C7BD63473FFFB117F9873703E + 4EC955931E267F26262B0949BC16DC49# "\xDF\u001E\xAC\x9C{\xD64s\xFF\xFB\u0011\u007F\x98sp>N\xC9U\x93\u001E&\u007F&&+\tI\xBC\u0016\xDCI" + 1A 65DCB821 # unsigned(1708963873) + + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x01, 0x00, 0x84, 0x50, 0xc7, + 0x3c, 0xb0, 0x45, 0xf9, 0xc2, 0x43, 0x45, 0x85, + 0xaf, 0xfa, 0x36, 0xa3, 0x07, 0xbf, 0xe7, 0x78, + 0x72, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, + 0x2f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x2d, + 0x69, 0x6e, 0x74, 0x2e, 0x6f, 0x6e, 0x69, 0x75, + 0x64, 0x72, 0x61, 0x2e, 0x63, 0x63, 0x2f, 0x73, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x66, + 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2f, + 0x76, 0x31, 0x2f, 0x64, 0x66, 0x31, 0x65, 0x61, + 0x63, 0x39, 0x63, 0x37, 0x62, 0x64, 0x36, 0x33, + 0x34, 0x37, 0x33, 0x66, 0x66, 0x66, 0x62, 0x31, + 0x31, 0x37, 0x66, 0x39, 0x38, 0x37, 0x33, 0x37, + 0x30, 0x33, 0x65, 0x34, 0x65, 0x63, 0x39, 0x35, + 0x35, 0x39, 0x33, 0x31, 0x65, 0x32, 0x36, 0x37, + 0x66, 0x32, 0x36, 0x32, 0x36, 0x32, 0x62, 0x30, + 0x39, 0x34, 0x39, 0x62, 0x63, 0x31, 0x36, 0x64, + 0x63, 0x34, 0x39, 0x58, 0x20, 0xdf, 0x1e, 0xac, + 0x9c, 0x7b, 0xd6, 0x34, 0x73, 0xff, 0xfb, 0x11, + 0x7f, 0x98, 0x73, 0x70, 0x3e, 0x4e, 0xc9, 0x55, + 0x93, 0x1e, 0x26, 0x7f, 0x26, 0x26, 0x2b, 0x09, + 0x49, 0xbc, 0x16, 0xdc, 0x49, 0x1A, 0x65, 0xDC, + 0xB8, 0x21}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is successful") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode the OtaBeginUp message") + { + CommandDown command; + /* + DA 00010000 # tag(65536) + 81 # array(1) + 58 20 # bytes(32) + 01020304 + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x00, 0x00, 0x81, 0x58, 0x20, + 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful - OtaBeginUp is not supported") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode the ThingBeginCmd message") + { + CommandDown command; + /* + DA 00010300 # tag(66304) + 81 # array(1) + 68 # text(8) + 7468696E675F6964 # "thing_id" + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x03, 0x00, 0x81, 0x68, 0x74, + 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful - ThingBeginCmd is not supported") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode the LastValuesBeginCmd message") + { + CommandDown command; + /* + DA 00010500 # tag(66816) + 80 # array(0) + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x05, 0x00, 0x80}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful - LastValuesBeginCmd is not supported") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode the DeviceBeginCmd message") + { + CommandDown command; + /* + DA 00010700 # tag(67328) + 81 # array(1) + 65 # text(5) + 322E302E30 # "2.0.0" + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x07, 0x00, 0x81, 0x65, 0x32, + 0x2e, 0x30, 0x2e, 0x30}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful - DeviceBeginCmd is not supported") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode the OtaProgressCmdUp message") + { + CommandDown command; + /* + DA 00010200 # tag(66048) + 84 # array(4) + 50 # bytes(16) + 000102030405060708090A0B0C0D0E0F # "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f" + E1 # primitive(1) + 20 # negative(0) + 18 64 # unsigned(100) + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x02, 0x00, 0x84, 0x50, 0x00, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xe1, + 0x20, 0x18, 0x64}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful - OtaProgressCmdUp is not supported") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode the TimezoneCommandUp message") + { + CommandDown command; + /* + DA 00010800 # tag(67584) + 80 # array(0) + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x08, 0x00, 0x80}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful - TimezoneCommandUp is not supported") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode a message with invalid CBOR tag") + { + CommandDown command; + + /* + DA ffffffff # invalid tag + 82 # array(2) + 1A 65DCB821 # unsigned(1708963873) + 1A 78ACA191 # unsigned(2024579473) + */ + + uint8_t const payload[] = {0xDA, 0xff, 0xff, 0xff, 0xff, 0x82, 0x1A, 0x65, + 0xDC, 0xB8, 0x21, 0x1A, 0x78, 0xAC, 0xA1, 0x91}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode a message not starting with a CBOR tag") + { + CommandDown command; + + /* + 82 # array(2) + 1A 65DCB821 # unsigned(1708963873) + 1A 78ACA191 # unsigned(2024579473) + */ + + uint8_t const payload[] = {0x82, 0x1A, 0x65, 0xDC, 0xB8, 0x21, 0x1A, 0x78, + 0xAC, 0xA1, 0x91}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful") { + REQUIRE(err == Decoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Decode an invalid CBOR message") + { + CommandDown command; + + uint8_t const payload[] = {0xFF}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + Decoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful") { + REQUIRE(err == Decoder::Status::Error); + } + } + +} diff --git a/extras/test/src/test_command_encode.cpp b/extras/test/src/test_command_encode.cpp new file mode 100644 index 000000000..7e31c1487 --- /dev/null +++ b/extras/test/src/test_command_encode.cpp @@ -0,0 +1,322 @@ +/* + Copyright (c) 2024 Arduino. All rights reserved. +*/ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include + +#include + +#include +#include + +/****************************************************************************** + TEST CODE + ******************************************************************************/ + +SCENARIO("Test the encoding of command messages") { + /****************************************************************************/ + + WHEN("Encode the OtaBeginUp message") + { + OtaBeginUp command; + uint8_t sha[SHA256_SIZE] = {0x01, 0x02, 0x03, 0x04}; + memcpy(command.params.sha, sha, SHA256_SIZE); + + command.c.id = CommandId::OtaBeginUpId; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + uint8_t expected_result[] = { + 0xda, 0x00, 0x01, 0x00, 0x00, 0x81, 0x58, 0x20, + 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + // Test the encoding is + // DA 00010000 # tag(65536) + // 81 # array(1) + // 58 20 # bytes(32) + // 01020304 + THEN("The encoding is successful") { + REQUIRE(err == Encoder::Status::Complete); + REQUIRE(bytes_encoded == sizeof(expected_result)); + REQUIRE(memcmp(buffer, expected_result, sizeof(expected_result)) == 0); + } + } + + + /****************************************************************************/ + + WHEN("Encode the ThingBeginCmd message") + { + ThingBeginCmd command; + String thing_id = "thing_id"; + strcpy(command.params.thing_id, thing_id.c_str()); + + command.c.id = CommandId::ThingBeginCmdId; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + uint8_t expected_result[] = { + 0xda, 0x00, 0x01, 0x03, 0x00, 0x81, 0x68, 0x74, + 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64 + }; + + // Test the encoding is + // DA 00010300 # tag(66304) + // 81 # array(1) + // 68 # text(8) + // 7468696E675F6964 # "thing_id" + + THEN("The encoding is successful") { + REQUIRE(err == Encoder::Status::Complete); + REQUIRE(bytes_encoded == sizeof(expected_result)); + REQUIRE(memcmp(buffer, expected_result, sizeof(expected_result)) == 0); + } + } + + /****************************************************************************/ + + WHEN("Encode the LastValuesBeginCmd message") + { + LastValuesBeginCmd command; + command.c.id = CommandId::LastValuesBeginCmdId; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + uint8_t expected_result[] = { + 0xda, 0x00, 0x01, 0x05, 0x00, 0x80 + }; + + // Test the encoding is + // DA 00010500 # tag(66816) + // 80 # array(0) + THEN("The encoding is successful") { + REQUIRE(err == Encoder::Status::Complete); + REQUIRE(bytes_encoded == sizeof(expected_result)); + REQUIRE(memcmp(buffer, expected_result, sizeof(expected_result)) == 0); + } + } + + /**************************************************************************/ + + WHEN("Encode the DeviceBeginCmd message") + { + DeviceBeginCmd command; + String lib_version = "2.0.0"; + strcpy(command.params.lib_version, lib_version.c_str()); + + command.c.id = CommandId::DeviceBeginCmdId; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + uint8_t expected_result[] = { + 0xda, 0x00, 0x01, 0x07, 0x00, 0x81, 0x65, 0x32, + 0x2e, 0x30, 0x2e, 0x30 + }; + + // Test the encoding is + // DA 00010700 # tag(67328) + // 81 # array(1) + // 65 # text(5) + // 322E302E30 # "2.0.0" + THEN("The encoding is successful") { + REQUIRE(err == Encoder::Status::Complete); + REQUIRE(bytes_encoded == sizeof(expected_result)); + REQUIRE(memcmp(buffer, expected_result, sizeof(expected_result)) == 0); + } + } + + /****************************************************************************/ + + WHEN("Encode the OtaProgressCmdUp message") + { + OtaProgressCmdUp command; + command.params.time = 2; + + uint8_t id[ID_SIZE] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}; + memcpy(command.params.id, id, ID_SIZE); + command.params.state = 1; + command.params.state_data = -1; + command.params.time = 100; + + command.c.id = CommandId::OtaProgressCmdUpId; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + uint8_t expected_result[] = { + 0xda, 0x00, 0x01, 0x02, 0x00, 0x84, 0x50, 0x00, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xe1, + 0x20, 0x18, 0x64 + }; + + // Test the encoding is + // DA 00010200 # tag(66048) + // 84 # array(4) + // 50 # bytes(16) + // 000102030405060708090A0B0C0D0E0F # "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f" + // E1 # primitive(1) + // 20 # negative(0) + // 18 64 # unsigned(100) + THEN("The encoding is successful") { + REQUIRE(err == Encoder::Status::Complete); + REQUIRE(bytes_encoded == sizeof(expected_result)); + REQUIRE(memcmp(buffer, expected_result, sizeof(expected_result)) == 0); + } + } + + /****************************************************************************/ + + WHEN("Encode the TimezoneCommandUp message") + { + TimezoneCommandUp command; + command.c.id = CommandId::TimezoneCommandUpId; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + uint8_t expected_result[] = { + 0xda, 0x00, 0x01, 0x08, 0x00, 0x80 + }; + + // Test the encoding is + // DA 00010800 # tag(67584) + // 80 # array(0) + THEN("The encoding is successful") { + REQUIRE(err == Encoder::Status::Complete); + REQUIRE(bytes_encoded == sizeof(expected_result)); + REQUIRE(memcmp(buffer, expected_result, sizeof(expected_result)) == 0); + } + } + + /****************************************************************************/ + + WHEN("Encode the ThingUpdateCmdId message") + { + ThingUpdateCmd command; + command.c.id = CommandId::ThingUpdateCmdId; + + String thing_id = "e4494d55-872a-4fd2-9646-92f87949394c"; + strcpy(command.params.thing_id, thing_id.c_str()); + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + THEN("The encoding is unsuccessful - ThingUpdateCmdId is not supported") { + REQUIRE(err == Encoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Encode the SetTimezoneCommand message") + { + TimezoneCommandDown command; + command.c.id = CommandId::TimezoneCommandDownId; + + command.params.offset = 1708963873; + command.params.until = 2024579473; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + THEN("The encoding is unsuccessful - SetTimezoneCommand is not supported") { + REQUIRE(err == Encoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Encode the LastValuesUpdateCmd message") + { + LastValuesUpdateCmd command; + command.c.id = CommandId::LastValuesUpdateCmdId; + + command.params.length = 13; + uint8_t last_values[13] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x10, 0x11, 0x12}; + command.params.last_values = last_values; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + THEN("The encoding is unsuccessful - LastValuesUpdateCmd is not supported") { + REQUIRE(err == Encoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Encode the OtaUpdateCmdDown message") + { + OtaUpdateCmdDown command; + command.c.id = CommandId::OtaUpdateCmdDownId; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + THEN("The encoding is unsuccessful - OtaUpdateCmdDown is not supported") { + REQUIRE(err == Encoder::Status::Error); + } + } + + /****************************************************************************/ + + WHEN("Encode a message with unknown command Id") + { + OtaUpdateCmdDown command; + command.c.id = CommandId::UnknownCmdId; + + uint8_t buffer[512]; + size_t bytes_encoded = sizeof(buffer); + + CBORMessageEncoder encoder; + Encoder::Status err = encoder.encode((Message*)&command, buffer, bytes_encoded); + + THEN("The encoding is unsuccessful - UnknownCmdId is not supported") { + REQUIRE(err == Encoder::Status::Error); + } + } +} From 04d7bf89e07ba8232ce22030d6fe55e50c555f98 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 19 Mar 2024 17:09:11 +0100 Subject: [PATCH 4/6] Extend Commands.h to include new Command protocol model --- src/message/Commands.h | 103 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 2 deletions(-) diff --git a/src/message/Commands.h b/src/message/Commands.h index 0f9cdac20..ce71f9c81 100644 --- a/src/message/Commands.h +++ b/src/message/Commands.h @@ -18,10 +18,20 @@ #include /****************************************************************************** - * TYPEDEF + * DEFINE ******************************************************************************/ -enum CommandId : uint16_t { +#define THING_ID_SIZE 37 +#define SHA256_SIZE 32 +#define URL_SIZE 256 +#define ID_SIZE 16 +#define MAX_LIB_VERSION_SIZE 10 + +/****************************************************************************** + TYPEDEF + ******************************************************************************/ + +enum CommandId: uint32_t { /* Device commands */ DeviceBeginCmdId, @@ -39,6 +49,15 @@ enum CommandId : uint16_t { /* Generic commands */ ResetCmdId, + /* OTA commands */ + OtaBeginUpId, + OtaProgressCmdUpId, + OtaUpdateCmdDownId, + + /* Timezone commands */ + TimezoneCommandUpId, + TimezoneCommandDownId, + /* Unknown command id */ UnknownCmdId }; @@ -48,3 +67,83 @@ struct Command { }; typedef Command Message; + +struct DeviceBeginCmd { + Command c; + struct { + char lib_version[MAX_LIB_VERSION_SIZE]; + } params; +}; + +struct ThingBeginCmd { + Command c; + struct { + char thing_id[THING_ID_SIZE]; + } params; +}; + +struct ThingUpdateCmd { + Command c; + struct { + char thing_id[THING_ID_SIZE]; + } params; +}; + +struct LastValuesBeginCmd { + Command c; +}; + +struct LastValuesUpdateCmd { + Command c; + struct { + uint8_t * last_values; + size_t length; + } params; +}; + +struct OtaBeginUp { + Command c; + struct { + uint8_t sha [SHA256_SIZE]; + } params; +}; + +struct OtaProgressCmdUp { + Command c; + struct { + uint8_t id[ID_SIZE]; + uint8_t state; + int32_t state_data; + uint64_t time; + } params; +}; + +struct OtaUpdateCmdDown { + Command c; + struct { + uint8_t id[ID_SIZE]; + char url[URL_SIZE]; + uint8_t initialSha256[SHA256_SIZE]; + uint8_t finalSha256[SHA256_SIZE]; + } params; +}; + +struct TimezoneCommandUp { + Command c; +}; + +struct TimezoneCommandDown { + Command c; + struct { + int32_t offset; + uint32_t until; + } params; +}; + +union CommandDown { + struct Command c; + struct OtaUpdateCmdDown otaUpdateCmdDown; + struct ThingUpdateCmd thingUpdateCmd; + struct LastValuesUpdateCmd lastValuesUpdateCmd; + struct TimezoneCommandDown timezoneCommandDown; +}; From 3a0b4ceeb32cf0524910b3a1c9a672635b146d3c Mon Sep 17 00:00:00 2001 From: pennam Date: Thu, 29 Feb 2024 17:25:28 +0100 Subject: [PATCH 5/6] ArduinoIoTCloudTCP: switch to messages --- src/ArduinoIoTCloudTCP.cpp | 211 +++++++++++++++++-------------------- src/ArduinoIoTCloudTCP.h | 22 ++-- 2 files changed, 107 insertions(+), 126 deletions(-) diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index 0851700cc..6e38a896b 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -43,6 +43,7 @@ #include #include "cbor/CBOREncoder.h" #include "utility/watchdog/Watchdog.h" +#include /****************************************************************************** LOCAL MODULE FUNCTIONS @@ -62,7 +63,6 @@ ArduinoIoTCloudTCP::ArduinoIoTCloudTCP() , _connection_attempt(0,0) , _message_stream(std::bind(&ArduinoIoTCloudTCP::sendMessage, this, std::placeholders::_1)) , _thing(&_message_stream) -, _thing_id_property{nullptr} , _device(&_message_stream) , _mqtt_data_buf{0} , _mqtt_data_len{0} @@ -76,8 +76,8 @@ ArduinoIoTCloudTCP::ArduinoIoTCloudTCP() , _mqttClient{nullptr} , _deviceTopicOut("") , _deviceTopicIn("") -, _shadowTopicOut("") -, _shadowTopicIn("") +, _messageTopicOut("") +, _messageTopicIn("") , _dataTopicOut("") , _dataTopicIn("") #if OTA_ENABLED @@ -181,6 +181,7 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, _mqttClient.setUsernamePassword(getDeviceId(), _password); } #endif + _mqttClient.onMessage(ArduinoIoTCloudTCP::onMessage); _mqttClient.setKeepAliveInterval(30 * 1000); _mqttClient.setConnectionTimeout(1500); @@ -188,17 +189,14 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, _deviceTopicOut = getTopic_deviceout(); _deviceTopicIn = getTopic_devicein(); - - Property* p; - p = new CloudWrapperString(_lib_version); - addPropertyToContainer(_device.getPropertyContainer(), *p, "LIB_VERSION", Permission::Read, -1); - p = new CloudWrapperString(_thing_id); - _thing_id_property = &addPropertyToContainer(_device.getPropertyContainer(), *p, "thing_id", Permission::ReadWrite, -1).writeOnDemand(); + _messageTopicIn = getTopic_messagein(); + _messageTopicOut = getTopic_messageout(); _thing.begin(); _device.begin(); #if OTA_ENABLED + Property* p; p = new CloudWrapperBool(_ota_cap); addPropertyToContainer(_device.getPropertyContainer(), *p, "OTA_CAP", Permission::Read, -1); p = new CloudWrapperInt(_ota_error); @@ -322,9 +320,16 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_ConnectMqttBroker() { if (_mqttClient.connect(_brokerAddress.c_str(), _brokerPort)) { - DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s connected to %s:%d", __FUNCTION__, _brokerAddress.c_str(), _brokerPort); + /* Subscribe to message topic to receive commands */ + _mqttClient.subscribe(_messageTopicIn); + + /* Temoporarly subscribe to device topic to receive OTA properties */ + _mqttClient.subscribe(_deviceTopicIn); + /* Reconfigure timers for next state */ _connection_attempt.begin(AIOT_CONFIG_DEVICE_TOPIC_SUBSCRIBE_RETRY_DELAY_ms, AIOT_CONFIG_MAX_DEVICE_TOPIC_SUBSCRIBE_RETRY_DELAY_ms); + + DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s connected to %s:%d", __FUNCTION__, _brokerAddress.c_str(), _brokerPort); return State::Connected; } @@ -413,7 +418,6 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Disconnect() _thing.handleMessage(&message); _device.handleMessage(&message); - _thing_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; DEBUG_INFO("Disconnected from Arduino IoT Cloud"); execCloudEventCallback(ArduinoIoTCloudEvent::DISCONNECT); @@ -440,26 +444,6 @@ void ArduinoIoTCloudTCP::handleMessage(int length) /* Topic for OTA properties and device configuration */ if (_deviceTopicIn == topic) { CBORDecoder::decode(_device.getPropertyContainer(), (uint8_t*)bytes, length); - - /* Temporary check to avoid flooding device state machine with usless messages */ - if (_thing_id_property->isDifferentFromCloud()) { - _thing_id_property->fromCloudToLocal(); - - if (!_thing_id.length()) { - /* Send message to device state machine to inform we have received a null thing-id */ - _thing_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; - Message message; - message = { DeviceRegisteredCmdId }; - _device.handleMessage(&message); - } else { - if (_device.isAttached()) { - detachThing(); - } - if (!_device.isAttached()) { - attachThing(); - } - } - } } /* Topic for user input data */ @@ -467,41 +451,94 @@ 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()); + String new_thing_id = String(command.thingUpdateCmd.params.thing_id); + + if (!new_thing_id.length()) { + /* Send message to device state machine to inform we have received a null thing-id */ + _thing_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; + Message message; + message = { DeviceRegisteredCmdId }; + _device.handleMessage(&message); + } else { + if (_device.isAttached() && _thing_id != new_thing_id) { + detachThing(); + } + if (!_device.isAttached()) { + attachThing(new_thing_id); + } + } + } + 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); } } @@ -525,29 +562,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) { @@ -563,28 +577,9 @@ 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() +void ArduinoIoTCloudTCP::attachThing(String thingId) { + _thing_id = thingId; _dataTopicIn = getTopic_datain(); _dataTopicOut = getTopic_dataout(); @@ -595,15 +590,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."); - _thing_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; - return; - } - Message message; message = { DeviceAttachedCmdId }; _device.handleMessage(&message); @@ -620,11 +606,6 @@ void ArduinoIoTCloudTCP::detachThing() return; } - if (!_mqttClient.unsubscribe(_shadowTopicIn)) { - DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not unsubscribe from %s", __FUNCTION__, _shadowTopicIn.c_str()); - return; - } - Message message; message = { DeviceDetachedCmdId }; _device.handleMessage(&message); diff --git a/src/ArduinoIoTCloudTCP.h b/src/ArduinoIoTCloudTCP.h index 1c13592df..7f08c3ce2 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,18 +201,14 @@ 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 attachThing(String thingId); void detachThing(); int write(String const topic, byte const data[], int const length); #if OTA_ENABLED void sendDevicePropertyToCloud(String const name); #endif - }; /****************************************************************************** From ba2d5841e24029cc7e878cf68f5993b6a25b5802 Mon Sep 17 00:00:00 2001 From: pennam Date: Wed, 20 Mar 2024 10:43:43 +0100 Subject: [PATCH 6/6] 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 f6594e027..09151f4d2 100644 --- a/src/ArduinoIoTCloudDevice.cpp +++ b/src/ArduinoIoTCloudDevice.cpp @@ -104,12 +104,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();