CBOR (Concise Binary Object Representation) is an Internet standard encoding for structured data defined in RFC7049. It defines how various types of data used in programming can be encoded to a stream of bytes to be exchanged over an IPC or communication medium.
“The Concise Binary Object Representation (CBOR) is a data format whose design goals include the possibility of extremely small code size, fairly small message size, and extensibility without the need for version negotiation.”
For example the following data structure in JSON notation can be serialized to binary and deserialized from binary by utilizing CBOR encoding provided by this library.
{
"t": 72.0,
"v": 3,
"sn": "123-456"
}
More CBOR details are available at https://cbor.io.
MicroCbor is a C++ implementation of CBOR implemented as a single header file plus some associated unit tests.
The data structure referenced previously would be encoded with C++ using the following MicroCbor code:
uint8_t buf[200]; // Serialization output buffer
MicroCbor cbor(buf, sizeof(buf));
cbor.startMap();
cbor.add("t", 72.0f);
cbor.add("v", 3);
cbor.add("sn", "123-456");
cbor.endMap();
The types to serialized are implied from the arguments provided. It is also permissible to explicitly inform the type to be used:
cbor.add<int32_t>("parm", value);
Add the following to CMakeLists.txt to pull from git and include in your project. CMake 3.14 is needed as it includes FetchContent and the FetchContent_MakeAvailable function.
cmake_minimum_required(VERSION 3.14.0)
#==============================================================================
# Load MicroCbor from git
include(FetchContent)
FetchContent_Declare(
microcbor
GIT_REPOSITORY https://github.com/glenne/microcbor.git
GIT_TAG main
)
FetchContent_MakeAvailable(microcbor)
#==============================================================================
target_link_libraries( YourLibraryName
INTERFACE microcbor
)
In your source file:
#include "microcbor/MicroCbor.hpp"
To serialize:
- Allocate some memory
- Call startMap() to start a key/value pair dictionary
- Call add(key,value) for each item to serialize
- Call endMap() to end the dictionary
In most cases, serializing will be inline code stuffing bytes into the output buffer and will not require any function calls.
- Initialize with a cbor encoded buffer.
- Call
get<T>(key, defaultValue)
to retrieve a value.
Getters are assumed to never fail and either return the default value provided or the value contained in the serialized stream. This allows code to be written without a sea of if/else clauses and provides a vaccine for version-itis. In other words, newer code can ask for a property it expects and proceed normally using a default even if the property was not provided by the sender.
Arrays are serialized by copying into the output buffer. On reading, arrays retrieve a pointer to the array data contained in the serialized stream. The pointer can used as-is for the lifetime of the serialized stream or it can be used to copy the array to some other location.
By default, arrays are aligned in the output serialization buffer on natural boundaries. That is, an array of int32_t values would insure the output buffer has the start of the array on an 4 byte boundary. This should reduce the need for copies by allowing the array to be used 'in place'.
The following code illustrates obtaining an array from a serialized stream. The return value from getPointer is actually a structure with a pointer and length:
auto array = cbor.getPointer<int32_t>("pts", nullptr); // default is nullptr
if (array.p == nullptr)
{
LOG_ERROR("Array not provided\n");
}
else
{
cout << "The array is " << array.length << " elements long" << std::endl;
}
This example from the unit tests illustrates some of the forms. See the unit test for more examples.
TEST(microcbor, ints)
{
uint8_t buf[200];
MicroCbor cbor(buf, sizeof(buf));
cbor.startMap();
cbor.add("true", true);
cbor.add("false", false);
cbor.add<int8_t>("i8", -80);
cbor.add<int16_t>("i16", -16000);
cbor.add<int32_t>("i32", -32000000);
cbor.add<uint8_t>("ui8", 80);
cbor.add<uint16_t>("ui16", 16000);
cbor.add<uint32_t>("ui32", 32000000);
cbor.endMap();
cbor.restart();
ASSERT_EQ(true, cbor.get<bool>("true", false));
ASSERT_EQ(false, cbor.get<bool>("false", true));
ASSERT_EQ(-80, cbor.get<int8_t>("i8", 0));
ASSERT_EQ(-16000, cbor.get<int16_t>("i16", 0));
ASSERT_EQ(-32000000, cbor.get<int32_t>("i32", 0));
ASSERT_EQ(-80, cbor.get("i8", int8_t(0)));
ASSERT_EQ(-16000, cbor.get("i16", int16_t(0)));
ASSERT_EQ(-32000000, cbor.get("i32", int32_t(0)));
ASSERT_EQ(80, cbor.get<uint8_t>("ui8", 0));
ASSERT_EQ(16000, cbor.get<uint16_t>("ui16", 0));
ASSERT_EQ(32000000, cbor.get<uint32_t>("ui32", 0));
ASSERT_EQ(80, cbor.get("ui8", uint8_t(0)));
ASSERT_EQ(16000, cbor.get("ui16", uint16_t(0)));
ASSERT_EQ(32000000, cbor.get("ui32", uint32_t(0)));
}
For a nice structured data abstraction capabile of serializing to and from CBOR, see the KArgMap project which provides a fantastic C++ interface for structured data and utilizes this project for CBOR support.
-
From test/, create a build directory and navigate into it
mkdir build && cd build
-
Run cmake
cmake ..
-
Run make
make
-
Execute the Test
microcbortest