diff --git a/.codespellrc b/.codespellrc index 4cdd0de..025901d 100644 --- a/.codespellrc +++ b/.codespellrc @@ -3,7 +3,7 @@ [codespell] # In the event of a false positive, add the problematic word, in all lowercase, to a comma-separated list here: ignore-words-list = , -skip = ./.git,./.licenses,__pycache__,node_modules,./go.mod,./go.sum,./package-lock.json,./poetry.lock,./yarn.lock,./src/tinycbor +skip = ./.git,./.licenses,__pycache__,node_modules,./go.mod,./go.sum,./package-lock.json,./poetry.lock,./yarn.lock,./src/cbor/tinycbor builtin = clear,informal,en-GB_to_en-US check-filenames = check-hidden = diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 1de2167..85bb2af 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -26,6 +26,8 @@ jobs: - examples/crc32 - examples/crc16 - examples/sha256 + - examples/customCborDecoder + - examples/customCborEncoder - examples/timedBlink SKETCHES_REPORTS_PATH: sketches-reports diff --git a/examples/customCborDecoder/customCborDecoder.ino b/examples/customCborDecoder/customCborDecoder.ino new file mode 100644 index 0000000..7d0931b --- /dev/null +++ b/examples/customCborDecoder/customCborDecoder.ino @@ -0,0 +1,79 @@ +/* + This file is part of the Arduino_CloudUtils 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 + +enum : MessageId { + CBORTestMessageId = 0x0123, +}; + +enum : CBORTag { + CBORTestMessageTag = 0x0321, +}; + + +struct CBORTestMessage { + Message m; + char parameter[20]; +}; + +class CustomMessageDecoder: public CBORMessageDecoderInterface { +public: + CustomMessageDecoder() + : CBORMessageDecoderInterface(CBORTestMessageTag, CBORTestMessageId) {} + +protected: + MessageDecoder::Status decode(CborValue* iter, Message *msg) override { + CBORTestMessage* test = (CBORTestMessage*) msg; + size_t dest_size = 20; + + if(!cbor_value_is_text_string(iter)) { + return MessageDecoder::Status::Error; + } + + // 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(iter, test->parameter, &dest_size, NULL) != CborNoError) { + return MessageDecoder::Status::Error; + } + + return MessageDecoder::Status::Complete; + } +} customMessageDecoder; + +void setup() { + Serial.begin(9600); + while(!Serial); + + CBORMessageDecoder decoder; + + CBORTestMessage expected_result { + CBORTestMessageId, + "abcdef", + }; + + uint8_t buffer[] { + 0xD9, 0x03, 0x21, 0x81, 0x66, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + }; + size_t buffer_len = sizeof(buffer); + + CBORTestMessage cmd_res; + MessageDecoder::Status res = decoder.decode((Message*)&cmd_res, buffer, buffer_len); + + if(res == MessageDecoder::Status::Complete && + cmd_res.m.id == expected_result.m.id && + strcmp(cmd_res.parameter, expected_result.parameter) == 0) { + + Serial.println("Decode operation completed with success"); + } else { + Serial.println("Decode operation failed"); + } +} + +void loop() {} diff --git a/examples/customCborEncoder/customCborEncoder.ino b/examples/customCborEncoder/customCborEncoder.ino new file mode 100644 index 0000000..0ab9fc8 --- /dev/null +++ b/examples/customCborEncoder/customCborEncoder.ino @@ -0,0 +1,77 @@ +/* + This file is part of the Arduino_CloudUtils 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 + +enum : MessageId { + CBORTestMessageId = 0x0123, +}; + +enum : CBORTag { + CBORTestMessageTag = 0x0321, +}; + + +struct CBORTestMessage { + Message m; + char parameter[20]; +}; + +class CustomMessageEncoder: public CBORMessageEncoderInterface { +public: + CustomMessageEncoder() + : CBORMessageEncoderInterface(CBORTestMessageTag, CBORTestMessageId) {} + +protected: + MessageEncoder::Status encode(CborEncoder* encoder, Message *msg) override { + CBORTestMessage * testMessage = (CBORTestMessage *) msg; + CborEncoder array_encoder; + + if(cbor_encoder_create_array(encoder, &array_encoder, 1) != CborNoError) { + return MessageEncoder::Status::Error; + } + + if(cbor_encode_text_stringz(&array_encoder, testMessage->parameter) != CborNoError) { + return MessageEncoder::Status::Error; + } + + if(cbor_encoder_close_container(encoder, &array_encoder) != CborNoError) { + return MessageEncoder::Status::Error; + } + return MessageEncoder::Status::Complete; + } +} customMessageEncoder; + +void setup() { + Serial.begin(9600); + while(!Serial); + + CBORMessageEncoder encoder; + uint8_t buffer[100]; // shared buffer for encoding + uint8_t expected[] = {0xD9, 0x03, 0x21, 0x81, 0x66, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66}; + const size_t buf_len = sizeof(buffer); + + CBORTestMessage cmd { + CBORTestMessageId, + "abcdef", + }; + size_t res_len=buf_len; + MessageEncoder::Status res = encoder.encode((Message*)&cmd, buffer, res_len); + + if(res == MessageEncoder::Status::Complete && + memcmp(buffer, expected, res_len) == 0) { + + Serial.println("Encode operation completed with success"); + } else { + Serial.println("Encode operation failed"); + } +} + +void loop() {} diff --git a/extras/test/CMakeLists.txt b/extras/test/CMakeLists.txt index 7877717..b604c53 100644 --- a/extras/test/CMakeLists.txt +++ b/extras/test/CMakeLists.txt @@ -28,6 +28,8 @@ set(TEST_SRCS src/crc16/test_crc16.cpp src/sha256/test_sha256.cpp src/hex/test_hex.cpp + src/cbor/test_cbor_encoder.cpp + src/cbor/test_cbor_decoder.cpp src/time/test_TimedAttempt.cpp ) @@ -36,6 +38,19 @@ set(TEST_DUT_SRCS ../../src/crc/crc16.cpp ../../src/sha256/sha2.c ../../src/hex/chex.h + ../../src/cbor/MessageDecoder.cpp + ../../src/cbor/MessageEncoder.cpp + ../../src/cbor/tinycbor + ../../src/cbor/tinycbor/src/cborencoder.c + ../../src/cbor/tinycbor/src/cborencoder_close_container_checked.c + ../../src/cbor/tinycbor/src/cborerrorstrings.c + ../../src/cbor/tinycbor/src/cborparser.c + ../../src/cbor/tinycbor/src/cborparser_dup_string.c + ../../src/cbor/tinycbor/src/cborpretty.c + ../../src/cbor/tinycbor/src/cborpretty_stdio.c + ../../src/cbor/tinycbor/src/cbortojson.c + ../../src/cbor/tinycbor/src/cborvalidation.c + ../../src/cbor/tinycbor/src/open_memstream.c ../../src/time/TimedAttempt.cpp ) diff --git a/extras/test/src/cbor/test_cbor_decoder.cpp b/extras/test/src/cbor/test_cbor_decoder.cpp new file mode 100644 index 0000000..09e24f4 --- /dev/null +++ b/extras/test/src/cbor/test_cbor_decoder.cpp @@ -0,0 +1,85 @@ +/* + This file is part of the Arduino_CloudUtils 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 +#include +#include + +#include + +enum : MessageId { + CBORTestMessageId = 0x0123, +}; + +enum : CBORTag { + CBORTestMessageTag = 0x0321, +}; + +struct CBORTestMessage { + Message m; + char parameter[20]; +}; + +class CustomMessageDecoder: public CBORMessageDecoderInterface { +public: + CustomMessageDecoder() + : CBORMessageDecoderInterface(CBORTestMessageTag, CBORTestMessageId) {} + +protected: + MessageDecoder::Status decode(CborValue* iter, Message *msg) override { + CBORTestMessage* test = (CBORTestMessage*) msg; + size_t dest_size = 20; + + if(!cbor_value_is_text_string(iter)) { + return MessageDecoder::Status::Error; + } + + // 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(iter, test->parameter, &dest_size, NULL) != CborNoError) { + return MessageDecoder::Status::Error; + } + + return MessageDecoder::Status::Complete; + } +} customMessageDecoder; + +SCENARIO( "A custom decoder is defined", "[cbor][decode]" ) { + CBORMessageDecoder decoder; + GIVEN( "A buffer containing a cbor encoded message" ) { + CBORTestMessage expected_result { + CBORTestMessageId, + "abcdef", + }; + + uint8_t buffer[] { + 0xD9, 0x03, 0x21, 0x81, 0x66, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + }; + size_t buffer_len = sizeof(buffer); + + CBORTestMessage cmd_res; + + MessageDecoder::Status res = decoder.decode((Message*)&cmd_res, buffer, buffer_len); + + THEN( "Message decode result is Complete" ) { + REQUIRE(res == MessageDecoder::Status::Complete); + } + + THEN( "the decode result matches the expectations" ) { + REQUIRE(buffer_len == sizeof(buffer)); + + REQUIRE(expected_result.m.id == cmd_res.m.id); + + std::string parameter_expected(expected_result.parameter); + std::string parameter_result(cmd_res.parameter); + + REQUIRE_THAT(parameter_result, Catch::Matchers::Equals(parameter_expected)); + } + } +} diff --git a/extras/test/src/cbor/test_cbor_encoder.cpp b/extras/test/src/cbor/test_cbor_encoder.cpp new file mode 100644 index 0000000..8c2d899 --- /dev/null +++ b/extras/test/src/cbor/test_cbor_encoder.cpp @@ -0,0 +1,79 @@ +/* + This file is part of the Arduino_CloudUtils 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 +#include +#include + +enum : MessageId { + CBORTestMessageId = 0x0123, +}; + +enum : CBORTag { + CBORTestMessageTag = 0x0321, +}; + + +struct CBORTestMessage { + Message m; + char parameter[20]; +}; + +class CustomMessageEncoder: public CBORMessageEncoderInterface { +public: + CustomMessageEncoder() + : CBORMessageEncoderInterface(CBORTestMessageTag, CBORTestMessageId) {} + +protected: + MessageEncoder::Status encode(CborEncoder* encoder, Message *msg) override { + CBORTestMessage * testMessage = (CBORTestMessage *) msg; + CborEncoder array_encoder; + + if(cbor_encoder_create_array(encoder, &array_encoder, 1) != CborNoError) { + return MessageEncoder::Status::Error; + } + + if(cbor_encode_text_stringz(&array_encoder, testMessage->parameter) != CborNoError) { + return MessageEncoder::Status::Error; + } + + if(cbor_encoder_close_container(encoder, &array_encoder) != CborNoError) { + return MessageEncoder::Status::Error; + } + return MessageEncoder::Status::Complete; + } +} customMessageEncoder; + +SCENARIO( "A custom encoder is defined", "[cbor][encode]" ) { + CBORMessageEncoder encoder; + uint8_t buffer[100]; // shared buffer for encoding + const size_t buf_len = sizeof(buffer); + + GIVEN( "A Message with an id that the global encoder is able to encode" ) { + CBORTestMessage cmd { + CBORTestMessageId, + "abcdef", + }; + size_t res_len=buf_len; + MessageEncoder::Status res = encoder.encode((Message*)&cmd, buffer, res_len); + + THEN( "Message encode result is Complete" ) { + REQUIRE(res == MessageEncoder::Status::Complete); + } + + THEN( "the encode result matches the expectations" ) { + std::vector res(buffer, buffer+res_len); + + REQUIRE_THAT(res, Catch::Matchers::Equals(std::vector{ + 0xD9, 0x03, 0x21, 0x81, 0x66, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + })); + } + } +} diff --git a/src/Arduino_CBOR.h b/src/Arduino_CBOR.h new file mode 100644 index 0000000..d1584eb --- /dev/null +++ b/src/Arduino_CBOR.h @@ -0,0 +1,13 @@ +/* + This file is part of the Arduino_CloudUtils 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 "./cbor/MessageEncoder.h" +#include "./cbor/MessageDecoder.h" diff --git a/src/Arduino_TinyCBOR.h b/src/Arduino_TinyCBOR.h index 0ac7ca1..c16e55d 100644 --- a/src/Arduino_TinyCBOR.h +++ b/src/Arduino_TinyCBOR.h @@ -9,4 +9,4 @@ */ #pragma once -#include "./tinycbor/cbor-lib.h" +#include "./cbor/tinycbor/cbor-lib.h" diff --git a/src/cbor/CBOR.h b/src/cbor/CBOR.h new file mode 100644 index 0000000..45e4033 --- /dev/null +++ b/src/cbor/CBOR.h @@ -0,0 +1,24 @@ +/* + This file is part of the Arduino_CloudUtils 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 + +typedef uint64_t CBORTag; + +namespace cbor { + namespace tag { + enum : CBORTag { + // 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 + }; + } +} diff --git a/src/cbor/MessageDecoder.cpp b/src/cbor/MessageDecoder.cpp new file mode 100644 index 0000000..ae630cd --- /dev/null +++ b/src/cbor/MessageDecoder.cpp @@ -0,0 +1,94 @@ +/* + This file is part of the Arduino_CloudUtils 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 "MessageDecoder.h" + +MessageDecoder::Status CBORMessageDecoderSingleton::decode(Message* msg, const uint8_t* const buf, size_t &len) { + // prepare cbor structure + CborValue iter; + CborTag tag; + CborParser parser; + + if (cbor_parser_init(buf, len, 0, &parser, &iter) != CborNoError) { + return MessageDecoder::Status::Error; + } + + if (iter.type != CborTagType) { + return MessageDecoder::Status::Error; + } + + if (cbor_value_get_tag(&iter, &tag) != CborNoError) { + return MessageDecoder::Status::Error; + } + + if (cbor_value_advance(&iter) != CborNoError) { + return MessageDecoder::Status::Error; + } + + auto decoder_it = decoders.begin(); + + for(; decoder_it != decoders.end(); decoder_it++) { + if(decoder_it->first == tag) { + break; + } + } + + // check if message.id exists on the decoders list or return error + if(decoder_it == decoders.end()) { + return MessageDecoder::Status::Error; + } + + // encode the message + if(decoder_it->second->_decode(&iter, msg) == MessageDecoder::Status::Error) { + return MessageDecoder::Status::Error; + } + + return MessageDecoder::Status::Complete; +} + +CBORMessageDecoderSingleton& CBORMessageDecoderSingleton::getInstance() { + static CBORMessageDecoderSingleton singleton; + + return singleton; +} + +void CBORMessageDecoderSingleton::append(CBORTag tag, CBORMessageDecoderInterface* decoder) { + auto decoder_it = decoders.begin(); + + for(; decoder_it != decoders.end(); decoder_it++) { + if(decoder_it->first == tag) { + return; + } + } + + decoders.push_back( + std::make_pair(tag, decoder) + ); +} + +CBORMessageDecoderInterface::CBORMessageDecoderInterface(const CBORTag tag, const MessageId id) +: tag(tag), id(id) { + // call singleton/global variable and insert this decoder + CBORMessageDecoderSingleton::getInstance().append(tag, this); +} + +MessageDecoder::Status CBORMessageDecoderInterface::_decode(CborValue* iter, Message *msg) { + CborValue array_iter; + msg->id = this->id; + + if (cbor_value_get_type(iter) != CborArrayType) { + return MessageDecoder::Status::Error; + } + + if (cbor_value_enter_container(iter, &array_iter) != CborNoError) { + return MessageDecoder::Status::Error; + } + + return decode(&array_iter, msg); +} diff --git a/src/cbor/MessageDecoder.h b/src/cbor/MessageDecoder.h new file mode 100644 index 0000000..1a9bf58 --- /dev/null +++ b/src/cbor/MessageDecoder.h @@ -0,0 +1,133 @@ +/* + This file is part of the Arduino_CloudUtils 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 "../interfaces/MessageDecoder.h" +#include "CBOR.h" +#include "../interfaces/message.h" +#include "./tinycbor/cbor-lib.h" + +// forward declaration +class CBORMessageDecoderSingleton; + +/* + * This library collects the interfaces used to decode Messages. Messages are C structs + * that are identified by a uint32 id after which they can contain anything. + * The objective of this library is to be modular and extensible in other libraries. + * + * In order to do so one have to extend the class `CBORMessageDecoderInterface`, + * provide the associated CBORTag and MessageId and provide a way to decode the message + * specific data. MessageId must be univocal across all DecoderInterfaces instantiated. + * The class implemented must be instantiated somewhere (using extern may be helpful) + * and that is enough to have it included in the decode process. + * + * In order to decode a message one can instantiate `CBORMessageDecoder` and + * call the decode function. + */ + +/** + * CBORMessageDecoderInterface class is an abstract class one has to implement + * in order to provide the instructions needed to decode a buffer contasining + * a message specific data. + */ +class CBORMessageDecoderInterface { +public: + + /** + * Constructor that initializes the CBORMessageDecoderInterface by providing the associated + * CBORTag and MessageId. The constructor also appends the object into CBORMessageDecoderSingleton + * allowing it to be used in CBORMessageDecoderSingleton::decode + * + * @param tag the cbor tag the message is associated with + * @param id the message id the message is associated with + */ + CBORMessageDecoderInterface(const CBORTag tag, const MessageId id); + virtual ~CBORMessageDecoderInterface() {} + +protected: + + /** + * Abstract decode function one must implement to decode the Message meaningful + * information present into the provided buffer + * + * @param iter tinycbor iterator to the buffer provided to CBORMessageDecoderSingleton::decode + * @param msg The message to which data must be applied to. Casting may be needed + */ + virtual MessageDecoder::Status decode(CborValue* iter, Message *msg) = 0; + +private: + const CBORTag tag; + const MessageId id; + + friend CBORMessageDecoderSingleton; + + /** + * Decode wrapper function that opens the cbor array and calls the abstract decode function + * + * @param iter tinycbor iterator to the buffer provided to CBORMessageDecoderSingleton::decode + * @param msg The message to which data must be applied to. Casting may be needed + */ + MessageDecoder::Status _decode(CborValue* iter, Message *msg); +}; + +/** + * This class is a singleton. It collects CBORMessageDecoderInterfaces implementations to decode + * all possible CBOR encoded messages + */ +class CBORMessageDecoderSingleton: public MessageDecoder { +public: + /** + * Get the singleton instance + */ + static CBORMessageDecoderSingleton& getInstance(); + + /** + * Add a new decoder to the singleton instance associating it to the provided cbor tag + * + * @param tag the tag to which we associate the decoder + * @param decoder the instance of decoder to append + */ + void append(CBORTag tag, CBORMessageDecoderInterface* decoder); + + /** + * Decode a buffer and put the contents into a message. The message should be big enough + * to accommodate the content of the buffer. + * + * @param[out] msg A struct that embeds struct Message onto which the content of buf is copied + * @param[in] buf The input buffer containing a cbor encoded message + * @param[in out] len The length of the buffer + */ + MessageDecoder::Status decode(Message* msg, const uint8_t* const buf, size_t &len); +private: + CBORMessageDecoderSingleton() {} + + std::vector> decoders; +}; + +/** + * In order to decode a message one can instantiate `CBORMessageDecoder` and + * call the decode function. In the future this class will contain decode session related + * information, in order to allow streaming decoding + */ +class CBORMessageDecoder: public MessageDecoder { +public: + /** + * Decode a buffer and put the contents into a message. The message should be big enough + * to accommodate the content of the buffer. + * + * @param[out] msg A struct that embeds struct Message onto which the content of buf is copied + * @param[in] buf The input buffer containing a cbor encoded message + * @param[in out] len The length of the buffer + */ + inline MessageDecoder::Status decode(Message* msg, const uint8_t* const buf, size_t &len) { + return CBORMessageDecoderSingleton::getInstance().decode(msg, buf, len); + } +}; diff --git a/src/cbor/MessageEncoder.cpp b/src/cbor/MessageEncoder.cpp new file mode 100644 index 0000000..0dc0924 --- /dev/null +++ b/src/cbor/MessageEncoder.cpp @@ -0,0 +1,80 @@ +/* + This file is part of the Arduino_CloudUtils 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 "MessageEncoder.h" +#include + +MessageEncoder::Status CBORMessageEncoderSingleton::encode(Message* message, uint8_t * data, size_t& len) { + // prepare cbor structure + CborEncoder encoder; + + cbor_encoder_init(&encoder, data, len, 0); + + auto encoder_it = encoders.begin(); + + for(; encoder_it != encoders.end(); encoder_it++) { + if(encoder_it->first == message->id) { + break; + } + } + + // check if message.id exists on the encoders list or return error + if(encoder_it == encoders.end()) { + return MessageEncoder::Status::Error; + } + + // encode the message + if(encoder_it->second->_encode(&encoder, message) == MessageEncoder::Status::Error) { + return MessageEncoder::Status::Error; + } + + len = cbor_encoder_get_buffer_size(&encoder, data); + + return MessageEncoder::Status::Complete; +} + +CBORMessageEncoderSingleton& CBORMessageEncoderSingleton::getInstance() { + static CBORMessageEncoderSingleton singleton; + + return singleton; +} + +void CBORMessageEncoderSingleton::append(MessageId id, CBORMessageEncoderInterface* encoder) { + auto encoder_it = encoders.begin(); + + for(; encoder_it != encoders.end(); encoder_it++) { + if(encoder_it->first == id) { + return; + } + } + + encoders.push_back( + std::make_pair(id, encoder) + ); +} + +CBORMessageEncoderInterface::CBORMessageEncoderInterface(const CBORTag tag, const MessageId id) +: tag(tag), id(id) { + // call singleton/global variable and insert this encoder + CBORMessageEncoderSingleton::getInstance().append(id, this); +} + +MessageEncoder::Status CBORMessageEncoderInterface::_encode(CborEncoder* encoder, Message *msg) { + // this must always be true, it could mean that there are issues in the map of encoders + assert(msg->id == id); + + if (tag == cbor::tag::CBORUnknownCmdTag16b || + tag == cbor::tag::CBORUnknownCmdTag32b || + tag == cbor::tag::CBORUnknownCmdTag64b || + cbor_encode_tag(encoder, tag) != CborNoError) { + return MessageEncoder::Status::Error; + } + + return this->encode(encoder, msg); +} diff --git a/src/cbor/MessageEncoder.h b/src/cbor/MessageEncoder.h new file mode 100644 index 0000000..2a8003b --- /dev/null +++ b/src/cbor/MessageEncoder.h @@ -0,0 +1,132 @@ +/* + This file is part of the Arduino_CloudUtils 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 "../interfaces/MessageEncoder.h" +#include "CBOR.h" +#include "../interfaces/message.h" +#include "./tinycbor/cbor-lib.h" + +// forward declaration +class CBORMessageEncoderSingleton; + +/* + * This library collects the interfaces used to encode Messages. Messages are C structs + * that are identified by a uint32 id after which they can contain anything. + * The objective of this library is to be modular and extensible in other libraries. + * + * In order to do so one have to extend the class `CBORMessageEncoderInterface`, + * provide the associated CBORTag and MessageId and provide a way to encode the message + * specific data. MessageId must be univocal across all EncoderInterfaces instantiated. + * The class implemented must be instantiated somewhere (using extern may be helpful) + * and that is enough to have it included in the encode process. + * + * In order to encode a message one can instantiate `CBORMessageEncoder` and + * call the encode function. + */ + +/** + * CBORMessageEncoderInterface class is an abstract class one has to implement + * in order to provide the instructions needed to encode a buffer contasining + * a message specific data. + */ +class CBORMessageEncoderInterface { +public: + + /** + * Constructor that initializes the CBORMessageEncoderInterface by providing the associated + * CBORTag and MessageId. The constructor also appends the object into CBORMessageEncoderSingleton + * allowing it to be used in CBORMessageEncoderSingleton::encode + * + * @param tag the cbor tag the message is associated with + * @param id the message id the message is associated with + */ + CBORMessageEncoderInterface(const CBORTag tag, const MessageId id); + virtual ~CBORMessageEncoderInterface() {} + +protected: + + /** + * Abstract encode function one must implement to encode a Message into a provided buffer + * + * @param encoder tinycbor encoder struct + * @param msg The message from which data must be extracted + */ + virtual MessageEncoder::Status encode(CborEncoder* encoder, Message *msg) = 0; + +private: + const CBORTag tag; + const MessageId id; + + friend CBORMessageEncoderSingleton; + + /** + * Encode wrapper function that encodes the cbor tag and then calls the encode virtual function + * + * @param encoder tinycbor encoder struct + * @param msg The message from which data must be extracted + */ + MessageEncoder::Status _encode(CborEncoder* encoder, Message *msg); +}; + +/** + * This class is a singleton. It collects CBORMessageEncoderInterfaces implementations to encode + * all possible CBOR encoded messages + */ +class CBORMessageEncoderSingleton: public MessageEncoder { +public: + /** + * Get the singleton instance + */ + static CBORMessageEncoderSingleton& getInstance(); + + /** + * Add a new encoder to the singleton instance associating it to the provided cbor tag + * + * @param tag the tag to which we associate the encoder + * @param encoder the instance of encoder to append + */ + void append(MessageId id, CBORMessageEncoderInterface* encoder); + + /** + * Encode a message and put the contents into a byte buffer. The message should be big enough + * to accommodate the content of the buffer. + * + * @param[in] msg A struct that embeds struct Message + * @param[in out] buf The output buffer onto which the message will be encoded + * @param[in out] len The length of the buffer + */ + MessageEncoder::Status encode(Message* message, uint8_t * data, size_t& len); +private: + CBORMessageEncoderSingleton() {} + + std::vector> encoders; +}; + +/** + * In order to encode a message one can instantiate `CBORMessageEncoder` and + * call the encode function. In the future this class will contain encode session related + * information, in order to allow streaming decoding + */ +class CBORMessageEncoder: public MessageEncoder { +public: + /** + * Encode a message and put the contents into a byte buffer. The message should be big enough + * to accommodate the content of the buffer. + * + * @param[in] msg A struct that embeds struct Message + * @param[in out] buf The output buffer onto which the message will be encoded + * @param[in out] len The length of the buffer + */ + inline MessageEncoder::Status encode(Message* msg, uint8_t* buf, size_t &len) { + return CBORMessageEncoderSingleton::getInstance().encode(msg, buf, len); + } +}; diff --git a/src/tinycbor/README.md b/src/cbor/tinycbor/README.md similarity index 100% rename from src/tinycbor/README.md rename to src/cbor/tinycbor/README.md diff --git a/src/tinycbor/cbor-lib.h b/src/cbor/tinycbor/cbor-lib.h similarity index 100% rename from src/tinycbor/cbor-lib.h rename to src/cbor/tinycbor/cbor-lib.h diff --git a/src/tinycbor/patch/patch.diff b/src/cbor/tinycbor/patch/patch.diff similarity index 100% rename from src/tinycbor/patch/patch.diff rename to src/cbor/tinycbor/patch/patch.diff diff --git a/src/tinycbor/src/cbor.h b/src/cbor/tinycbor/src/cbor.h similarity index 100% rename from src/tinycbor/src/cbor.h rename to src/cbor/tinycbor/src/cbor.h diff --git a/src/tinycbor/src/cborencoder.c b/src/cbor/tinycbor/src/cborencoder.c similarity index 100% rename from src/tinycbor/src/cborencoder.c rename to src/cbor/tinycbor/src/cborencoder.c diff --git a/src/tinycbor/src/cborencoder_close_container_checked.c b/src/cbor/tinycbor/src/cborencoder_close_container_checked.c similarity index 100% rename from src/tinycbor/src/cborencoder_close_container_checked.c rename to src/cbor/tinycbor/src/cborencoder_close_container_checked.c diff --git a/src/tinycbor/src/cborerrorstrings.c b/src/cbor/tinycbor/src/cborerrorstrings.c similarity index 100% rename from src/tinycbor/src/cborerrorstrings.c rename to src/cbor/tinycbor/src/cborerrorstrings.c diff --git a/src/tinycbor/src/cborinternal_p.h b/src/cbor/tinycbor/src/cborinternal_p.h similarity index 100% rename from src/tinycbor/src/cborinternal_p.h rename to src/cbor/tinycbor/src/cborinternal_p.h diff --git a/src/tinycbor/src/cborjson.h b/src/cbor/tinycbor/src/cborjson.h similarity index 100% rename from src/tinycbor/src/cborjson.h rename to src/cbor/tinycbor/src/cborjson.h diff --git a/src/tinycbor/src/cborparser.c b/src/cbor/tinycbor/src/cborparser.c similarity index 100% rename from src/tinycbor/src/cborparser.c rename to src/cbor/tinycbor/src/cborparser.c diff --git a/src/tinycbor/src/cborparser_dup_string.c b/src/cbor/tinycbor/src/cborparser_dup_string.c similarity index 100% rename from src/tinycbor/src/cborparser_dup_string.c rename to src/cbor/tinycbor/src/cborparser_dup_string.c diff --git a/src/tinycbor/src/cborpretty.c b/src/cbor/tinycbor/src/cborpretty.c similarity index 100% rename from src/tinycbor/src/cborpretty.c rename to src/cbor/tinycbor/src/cborpretty.c diff --git a/src/tinycbor/src/cborpretty_stdio.c b/src/cbor/tinycbor/src/cborpretty_stdio.c similarity index 100% rename from src/tinycbor/src/cborpretty_stdio.c rename to src/cbor/tinycbor/src/cborpretty_stdio.c diff --git a/src/tinycbor/src/cbortojson.c b/src/cbor/tinycbor/src/cbortojson.c similarity index 100% rename from src/tinycbor/src/cbortojson.c rename to src/cbor/tinycbor/src/cbortojson.c diff --git a/src/tinycbor/src/cborvalidation.c b/src/cbor/tinycbor/src/cborvalidation.c similarity index 100% rename from src/tinycbor/src/cborvalidation.c rename to src/cbor/tinycbor/src/cborvalidation.c diff --git a/src/tinycbor/src/compilersupport_p.h b/src/cbor/tinycbor/src/compilersupport_p.h similarity index 100% rename from src/tinycbor/src/compilersupport_p.h rename to src/cbor/tinycbor/src/compilersupport_p.h diff --git a/src/tinycbor/src/open_memstream.c b/src/cbor/tinycbor/src/open_memstream.c similarity index 100% rename from src/tinycbor/src/open_memstream.c rename to src/cbor/tinycbor/src/open_memstream.c diff --git a/src/tinycbor/src/tinycbor-version.h b/src/cbor/tinycbor/src/tinycbor-version.h similarity index 100% rename from src/tinycbor/src/tinycbor-version.h rename to src/cbor/tinycbor/src/tinycbor-version.h diff --git a/src/tinycbor/src/utf8_p.h b/src/cbor/tinycbor/src/utf8_p.h similarity index 100% rename from src/tinycbor/src/utf8_p.h rename to src/cbor/tinycbor/src/utf8_p.h diff --git a/src/interfaces/MessageDecoder.h b/src/interfaces/MessageDecoder.h new file mode 100644 index 0000000..f4d888e --- /dev/null +++ b/src/interfaces/MessageDecoder.h @@ -0,0 +1,41 @@ +/* + This file is part of the Arduino_CloudUtils 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 +#include "message.h" + +/****************************************************************************** + * CLASS DECLARATION + ******************************************************************************/ + +class MessageDecoder { +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/MessageEncoder.h b/src/interfaces/MessageEncoder.h new file mode 100644 index 0000000..d2fc453 --- /dev/null +++ b/src/interfaces/MessageEncoder.h @@ -0,0 +1,42 @@ +/* + This file is part of the Arduino_CloudUtils 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 +#include "message.h" + +/****************************************************************************** + * CLASS DECLARATION + ******************************************************************************/ + +class MessageEncoder { +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; +}; diff --git a/src/interfaces/message.h b/src/interfaces/message.h new file mode 100644 index 0000000..ba09c87 --- /dev/null +++ b/src/interfaces/message.h @@ -0,0 +1,34 @@ +/* + This file is part of the Arduino_CloudUtils 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 + +typedef uint32_t MessageId; + +/** + * The following struct can be used as a template to create ArduinoCloud compatible + * Messages that can be handled by encoders/decoders and other Cloud related message exchange + * These kind of messages are required to be identifiable by a starting uint32_t uinique identification number. + * + * In order to use this structure you need to embed this into another structure and + * add additional parameters + */ +struct Message { + MessageId id; +}; + +/** + * The following enum is defined to assign Arduino MessageIds starting values + * and boundaries and avoid value clashing + */ +enum : MessageId { + ArduinoIOTCloudStartMessageId = 0x100, + ArduinoProvisioningStartMessageId = 0x200, +};