Skip to content

Commit

Permalink
Added tinycbor based serialization method
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhjp01 committed Nov 26, 2024
1 parent 9f8f643 commit 7a98102
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 7 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ find_package(libzip REQUIRED)
find_package(Microsoft.GSL REQUIRED)
find_package(yaml-cpp REQUIRED)
find_package(XercesC MODULE REQUIRED)
find_package(tinycbor REQUIRED)
if(LIBCOSIM_WITH_PROXYFMU)
find_package(PROXYFMU CONFIG REQUIRED)
endif()
Expand Down
3 changes: 3 additions & 0 deletions conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def set_version(self):
def requirements(self):
self.tool_requires("cmake/[>=3.19]")
self.requires("fmilibrary/[~2.3]")
self.requires("tinycbor/[~0.6.0]")
self.requires("libzip/[>=1.7 <1.10]") # 1.10 deprecates some functions we use
self.requires("ms-gsl/[>=3 <5]", transitive_headers=True)
if self.options.proxyfmu:
Expand Down Expand Up @@ -66,6 +67,8 @@ def configure(self):
if self.options.shared:
self.options.rm_safe("fPIC")
self.options["*"].shared = self.options.shared
if self.settings.os == "Windows":
self.options["tinycbor/*"].shared = False

def generate(self):
# Copy dependencies to the folder where executables (tests, mainly)
Expand Down
5 changes: 3 additions & 2 deletions include/cosim/serialization.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ struct node_data_translator
}} // namespace cosim::serialization


std::ostream& operator<<(std::ostream& out, const cosim::serialization::node& data);

// Ordinarily, the following function would be in the `cosim::serialization`
// namespace and found by the compiler via ADL. This doesn't work here,
// because `cosim::serialization::node` is just an alias for a type in the
Expand All @@ -113,8 +115,7 @@ struct node_data_translator
* corresponding "read" function, nor is the output format designed to support
* round-trip information or type preservation.
*/
std::ostream& operator<<(std::ostream& out, const cosim::serialization::node& data);

void print_ptree(std::ostream& out, const cosim::serialization::node& data);

// Make node_translator the default translator for property trees whose data
// type is node_data.
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ target_link_libraries(cosim
libzip::zip
XercesC::XercesC
yaml-cpp
tinycbor::tinycbor
)

if(LIBCOSIM_WITH_PROXYFMU)
Expand Down
221 changes: 217 additions & 4 deletions src/cosim/serialization.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#include "cosim/serialization.hpp"

#include <algorithm>
#include <ios>
#include <iterator>
#include <utility>

#include <tinycbor/cbor.h>
#include <cosim/log/logger.hpp>

namespace
{
Expand Down Expand Up @@ -88,12 +88,225 @@ namespace
out << '}';
}
}
}

static constexpr size_t DEFAULT_CBOR_BLOCK_SIZE = 128;

class cbor_data
{
public:
std::shared_ptr<CborEncoder> encoder;
std::vector<uint8_t>& buf;

cbor_data(std::vector<uint8_t>& buf_ref)
: encoder(std::make_shared<CborEncoder>())
, buf(buf_ref)
, parent_(std::make_shared<CborEncoder>())
{
cbor_encoder_init(parent_.get(), buf.data(), buf.size(), 0);
wrap_cbor(cbor_encoder_create_map, parent_.get(), encoder.get(), CborIndefiniteLength);
root_ = parent_;
}

cbor_data(const cbor_data& data)
: encoder(data.encoder)
, buf(data.buf)
, root_(data.root_)
, parent_(data.parent_)
{
}

~cbor_data()
{
auto err = cbor_encoder_close_container(parent_.get(), encoder.get());
if (err != CborNoError) {
std::stringstream ss;
ss << "Encountered an issue while closing CBOR container: " << cbor_error_string(err);
BOOST_LOG_SEV(cosim::log::logger(), cosim::log::warning) << ss.str();
}

if (root_ == parent_) {
auto buf_size = cbor_encoder_get_buffer_size(parent_.get(), buf.data());
buf.resize(buf_size);
}
}

cbor_data new_child_map()
{
auto map_encoder = std::make_shared<CborEncoder>();
wrap_cbor(cbor_encoder_create_map, encoder.get(), map_encoder.get(), CborIndefiniteLength);
auto new_data = cbor_data{*this};
new_data.encoder = map_encoder;
new_data.parent_ = encoder;
return new_data;
}

cbor_data new_child_array()
{
auto array_encoder = std::make_shared<CborEncoder>();
wrap_cbor(cbor_encoder_create_array, encoder.get(), array_encoder.get(), CborIndefiniteLength);
auto new_data = cbor_data{*this};
new_data.encoder = array_encoder;
new_data.parent_ = encoder;
return new_data;
}

template<typename F, typename... Args>
CborError wrap_cbor(F&& f, Args&&... args)
{
total_bytes_ = cbor_encoder_get_buffer_size(encoder.get(), buf.data());
auto err = std::forward<F>(f)(std::forward<Args>(args)...);

if (err == CborErrorOutOfMemory) {
BOOST_LOG_SEV(cosim::log::logger(), cosim::log::debug) << "Out of memory while encoding CBOR, resizing a buffer.";
resize_buffer(DEFAULT_CBOR_BLOCK_SIZE);
err = std::forward<F>(f)(std::forward<Args>(args)...);
}

if (err != CborNoError) {
std::stringstream ss;
ss << "Error while encoding CBOR: " << cbor_error_string(err);
throw std::runtime_error(ss.str());
}
return err;
}

private:
void resize_buffer(size_t inc)
{
auto extra_bytes = cbor_encoder_get_extra_bytes_needed(encoder.get());
auto new_size = buf.size() + extra_bytes + inc;
buf.resize(new_size);

// Update the encoder's buffer pointer
encoder->data.ptr = buf.data() + total_bytes_;
encoder->end = buf.data() + new_size;
encoder->remaining = CborIndefiniteLength + 1;
}

std::shared_ptr<CborEncoder> root_, parent_;
size_t total_bytes_{};
};

struct cbor_write_visitor
{
cbor_write_visitor(cbor_data& data)
: data_(data)
{ }

void operator()(std::nullptr_t v)
{
data_.wrap_cbor(cbor_encode_null, data_.encoder.get());
}

void operator()(std::byte v)
{
data_.wrap_cbor(cbor_encode_simple_value, data_.encoder.get(), static_cast<uint8_t>(v));
}

void operator()(const std::vector<std::byte>& blob)
{
auto child = data_.new_child_array();
child.wrap_cbor(cbor_encode_byte_string, child.encoder.get(), reinterpret_cast<const uint8_t*>(blob.data()), blob.size());
}

void operator()(bool v)
{
data_.wrap_cbor(cbor_encode_boolean, data_.encoder.get(), v);
}

void operator()(uint8_t v)
{
data_.wrap_cbor(cbor_encode_simple_value, data_.encoder.get(), v);
}

void operator()(int8_t v)
{
data_.wrap_cbor(cbor_encode_simple_value, data_.encoder.get(), v);
}

void operator()(uint16_t v)
{
data_.wrap_cbor(cbor_encode_uint, data_.encoder.get(), v);
}

void operator()(int16_t v)
{
data_.wrap_cbor(cbor_encode_int, data_.encoder.get(), v);
}

void operator()(uint32_t v)
{
data_.wrap_cbor(cbor_encode_uint, data_.encoder.get(), v);
}

void operator()(int32_t v)
{
data_.wrap_cbor(cbor_encode_int, data_.encoder.get(), v);
}

void operator()(uint64_t v)
{
data_.wrap_cbor(cbor_encode_uint, data_.encoder.get(), v);
}

void operator()(int64_t v)
{
data_.wrap_cbor(cbor_encode_int, data_.encoder.get(), v);
}

void operator()(float v)
{
data_.wrap_cbor(cbor_encode_float, data_.encoder.get(), v);
}

void operator()(double v)
{
data_.wrap_cbor(cbor_encode_double, data_.encoder.get(), v);
}

void operator()(char v)
{
data_.wrap_cbor(cbor_encode_text_string, data_.encoder.get(), &v, 1);
}

void operator()(std::string v)
{
data_.wrap_cbor(cbor_encode_text_stringz, data_.encoder.get(), v.data());
}

private:
cbor_write_visitor() = delete;

cbor_data& data_;
};
void serialize_cbor(cbor_data& data, const cosim::serialization::node& tree)
{
for (auto child = tree.begin(); child != tree.end(); ++child) {
data.wrap_cbor(cbor_encode_text_stringz, data.encoder.get(), child->first.c_str());
if (child->second.begin() != child->second.end()) {
auto new_data = data.new_child_map();
serialize_cbor(new_data, child->second);
} else {
std::visit(cbor_write_visitor(data), child->second.data());
}
}
}
}

std::ostream& operator<<(std::ostream& out, const cosim::serialization::node& data)
{
std::vector<uint8_t> vec(DEFAULT_CBOR_BLOCK_SIZE);
{
auto cbor_array = cbor_data(vec);
serialize_cbor(cbor_array, data);
} // To finalize vec by calling cbor_data's destructor

out.write(reinterpret_cast<const char*>(vec.data()), vec.size());
return out;
}

void print_ptree(std::ostream& out, const cosim::serialization::node& data)
{
constexpr int indentStep = 2;
print_tree(out, data, 0, indentStep);
return out;
}
2 changes: 1 addition & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ foreach(test IN LISTS tests)
"cpp_${test}"
FOLDER "C++ tests"
SOURCES "${test}.cpp"
DEPENDENCIES cosim
DEPENDENCIES "cosim;tinycbor::tinycbor"
DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/data"
)
endforeach()
Expand Down

0 comments on commit 7a98102

Please sign in to comment.