Skip to content

Cbor messages #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Feb 26, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .codespellrc
Original file line number Diff line number Diff line change
@@ -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 =
2 changes: 2 additions & 0 deletions .github/workflows/compile-examples.yml
Original file line number Diff line number Diff line change
@@ -26,6 +26,8 @@ jobs:
- examples/crc32
- examples/crc16
- examples/sha256
- examples/customCborDecoder
- examples/customCborEncoder
- examples/timedBlink
SKETCHES_REPORTS_PATH: sketches-reports

79 changes: 79 additions & 0 deletions examples/customCborDecoder/customCborDecoder.ino
Original file line number Diff line number Diff line change
@@ -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 <Arduino_CBOR.h>

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() {}
77 changes: 77 additions & 0 deletions examples/customCborEncoder/customCborEncoder.ino
Original file line number Diff line number Diff line change
@@ -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 <Arduino_CBOR.h>

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() {}
15 changes: 15 additions & 0 deletions extras/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
)

85 changes: 85 additions & 0 deletions extras/test/src/cbor/test_cbor_decoder.cpp
Original file line number Diff line number Diff line change
@@ -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 <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include <cbor/MessageDecoder.h>

#include <stdio.h>

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));
}
}
}
79 changes: 79 additions & 0 deletions extras/test/src/cbor/test_cbor_encoder.cpp
Original file line number Diff line number Diff line change
@@ -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 <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers.hpp>
#include <catch2/matchers/catch_matchers_vector.hpp>
#include <cbor/MessageEncoder.h>

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<uint8_t> res(buffer, buffer+res_len);

REQUIRE_THAT(res, Catch::Matchers::Equals(std::vector<uint8_t>{
0xD9, 0x03, 0x21, 0x81, 0x66, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
}));
}
}
}
13 changes: 13 additions & 0 deletions src/Arduino_CBOR.h
Original file line number Diff line number Diff line change
@@ -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"
2 changes: 1 addition & 1 deletion src/Arduino_TinyCBOR.h
Original file line number Diff line number Diff line change
@@ -9,4 +9,4 @@
*/
#pragma once

#include "./tinycbor/cbor-lib.h"
#include "./cbor/tinycbor/cbor-lib.h"
24 changes: 24 additions & 0 deletions src/cbor/CBOR.h
Original file line number Diff line number Diff line change
@@ -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
};
}
}
94 changes: 94 additions & 0 deletions src/cbor/MessageDecoder.cpp
Original file line number Diff line number Diff line change
@@ -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);
}
133 changes: 133 additions & 0 deletions src/cbor/MessageDecoder.h
Original file line number Diff line number Diff line change
@@ -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 <vector>
#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<std::pair<CBORTag, CBORMessageDecoderInterface*>> 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);
}
};
80 changes: 80 additions & 0 deletions src/cbor/MessageEncoder.cpp
Original file line number Diff line number Diff line change
@@ -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 <algorithm>

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);
}
132 changes: 132 additions & 0 deletions src/cbor/MessageEncoder.h
Original file line number Diff line number Diff line change
@@ -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 <vector>
#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<std::pair<MessageId, CBORMessageEncoderInterface*>> 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);
}
};
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
41 changes: 41 additions & 0 deletions src/interfaces/MessageDecoder.h
Original file line number Diff line number Diff line change
@@ -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 <stddef.h>
#include <stdint.h>
#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;
};
42 changes: 42 additions & 0 deletions src/interfaces/MessageEncoder.h
Original file line number Diff line number Diff line change
@@ -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 <stddef.h>
#include <stdint.h>
#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;
};
34 changes: 34 additions & 0 deletions src/interfaces/message.h
Original file line number Diff line number Diff line change
@@ -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,
};