From 80169f9a1c87b02be2086e402f52a7913a50d2d7 Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Sat, 2 May 2015 16:59:22 +0300 Subject: [PATCH] Marshalling storage decorator --- .../dynamic_node_id_allocation_server.hpp | 16 +- .../uc_dynamic_node_id_allocation_server.cpp | 156 ++++++++++++++++++ .../dynamic_node_id_allocation_server.cpp | 109 +++++++++++- 3 files changed, 274 insertions(+), 7 deletions(-) create mode 100644 libuavcan/src/protocol/uc_dynamic_node_id_allocation_server.cpp diff --git a/libuavcan/include/uavcan/protocol/dynamic_node_id_allocation_server.hpp b/libuavcan/include/uavcan/protocol/dynamic_node_id_allocation_server.hpp index 803a6d4894..7e3f6abed9 100644 --- a/libuavcan/include/uavcan/protocol/dynamic_node_id_allocation_server.hpp +++ b/libuavcan/include/uavcan/protocol/dynamic_node_id_allocation_server.hpp @@ -30,6 +30,7 @@ namespace uavcan * The storage is represented as a key-value container, where keys and values are ASCII strings up to 32 * characters long, not including the termination byte. Fixed block size allows for absolutely straightforward * and efficient implementation of storage backends, e.g. based on text files. + * Keys and values may contain only alphanumeric characters and underscores. */ class IDynamicNodeIDStorageBackend { @@ -59,7 +60,7 @@ public: virtual String get(const String& key) const = 0; /** - * Create or update value for the given key. + * Create or update value for the given key. Empty value should be regarded as a request to delete the key. * This method should not block for more than 50 ms. * Failures will be ignored. */ @@ -85,6 +86,8 @@ class MarshallingStorageDecorator { IDynamicNodeIDStorageBackend& storage_; + static uint8_t convertLowerCaseHexCharToNibble(char ch); + public: MarshallingStorageDecorator(IDynamicNodeIDStorageBackend& storage) : storage_(storage) @@ -94,13 +97,10 @@ public: } /** - * Setters do the following: + * These methods set the value and then immediately read it back. * 1. Serialize the value. * 2. Update the value on the backend. - * 3. Read the value back from the backend; return false if read fails. - * 4. Deserealize the newly read value; return false if deserialization fails. - * 5. Update the argument with deserialized value. - * 6. Return true. + * 3. Call get() with the same value argument. * The caller then is supposed to check whether the argument has the desired value. */ bool setAndGetBack(const IDynamicNodeIDStorageBackend::String& key, uint32_t& inout_value); @@ -109,6 +109,10 @@ public: /** * Getters simply read and deserialize the value. + * 1. Read the value back from the backend; return false if read fails. + * 2. Deserealize the newly read value; return false if deserialization fails. + * 3. Update the argument with deserialized value. + * 4. Return true. */ bool get(const IDynamicNodeIDStorageBackend::String& key, uint32_t& out_value) const; bool get(const IDynamicNodeIDStorageBackend::String& key, diff --git a/libuavcan/src/protocol/uc_dynamic_node_id_allocation_server.cpp b/libuavcan/src/protocol/uc_dynamic_node_id_allocation_server.cpp new file mode 100644 index 0000000000..0dc68bbe06 --- /dev/null +++ b/libuavcan/src/protocol/uc_dynamic_node_id_allocation_server.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2015 Pavel Kirienko + */ + +#include +#include + +#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11 +# include +#endif + +#ifndef UAVCAN_CPP_VERSION +# error UAVCAN_CPP_VERSION +#endif + +namespace uavcan +{ +namespace dynamic_node_id_server_impl +{ +/* + * MarshallingStorageDecorator + */ +uint8_t MarshallingStorageDecorator::convertLowerCaseHexCharToNibble(char ch) +{ + const uint8_t ret = (ch > '9') ? static_cast(ch - 'a' + 10) : static_cast(ch - '0'); + UAVCAN_ASSERT(ret < 16); + return ret; +} + +bool MarshallingStorageDecorator::setAndGetBack(const IDynamicNodeIDStorageBackend::String& key, uint32_t& inout_value) +{ + IDynamicNodeIDStorageBackend::String serialized; + serialized.appendFormatted("%llu", static_cast(inout_value)); + + UAVCAN_TRACE("MarshallingStorageDecorator", "Set %s = %s", key.c_str(), serialized.c_str()); + storage_.set(key, serialized); + + return get(key, inout_value); +} + +bool MarshallingStorageDecorator::setAndGetBack(const IDynamicNodeIDStorageBackend::String& key, + protocol::dynamic_node_id::server::Entry::FieldTypes::unique_id& inout_value) +{ + IDynamicNodeIDStorageBackend::String serialized; + for (uint8_t i = 0; i < protocol::dynamic_node_id::server::Entry::FieldTypes::unique_id::MaxSize; i++) + { + serialized.appendFormatted("%02x", inout_value.at(i)); + } + UAVCAN_ASSERT(serialized.size() == protocol::dynamic_node_id::server::Entry::FieldTypes::unique_id::MaxSize * 2); + + UAVCAN_TRACE("MarshallingStorageDecorator", "Set %s = %s", key.c_str(), serialized.c_str()); + storage_.set(key, serialized); + + return get(key, inout_value); +} + +bool MarshallingStorageDecorator::get(const IDynamicNodeIDStorageBackend::String& key, uint32_t& out_value) const +{ + /* + * Reading the storage + */ + const IDynamicNodeIDStorageBackend::String val = storage_.get(key); + if (val.empty()) + { + return false; + } + + /* + * Per MISRA C++ recommendations, checking the inputs instead of relying solely on errno. + * The value must contain only numeric characters. + */ + for (IDynamicNodeIDStorageBackend::String::const_iterator it = val.begin(); it != val.end(); ++it) + { + if (static_cast(*it) < '0' || static_cast(*it) > '9') + { + return false; + } + } + + if (val.size() > 10) // len(str(0xFFFFFFFF)) + { + return false; + } + + /* + * Conversion is carried out here + */ +#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11 + errno = 0; +#endif + +#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11 + const unsigned long long x = std::strtoull(val.c_str(), NULL, 10); +#else + // There was no strtoull() before C++11, so we need to resort to strtoul() + StaticAssert<(sizeof(unsigned long) >= sizeof(uint32_t))>::check(); + const unsigned long x = std::strtoul(val.c_str(), NULL, 10); +#endif + +#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11 + if (errno != 0) + { + return false; + } +#endif + + out_value = static_cast(x); + return true; +} + +bool MarshallingStorageDecorator::get(const IDynamicNodeIDStorageBackend::String& key, + protocol::dynamic_node_id::server::Entry::FieldTypes::unique_id& out_value) const +{ + static const uint8_t NumBytes = protocol::dynamic_node_id::server::Entry::FieldTypes::unique_id::MaxSize; + + /* + * Reading the storage + */ + IDynamicNodeIDStorageBackend::String val = storage_.get(key); + if (val.size() != NumBytes * 2) + { + return false; + } + + /* + * Per MISRA C++ recommendations, checking the inputs instead of relying solely on errno. + * The value must contain only hexadecimal numbers. + */ + val.convertToLowerCaseASCII(); + for (IDynamicNodeIDStorageBackend::String::const_iterator it = val.begin(); it != val.end(); ++it) + { + if ((static_cast(*it) < '0' || static_cast(*it) > '9') && + (static_cast(*it) < 'a' || static_cast(*it) > 'f')) + { + return false; + } + } + + /* + * Conversion is carried out here + */ + IDynamicNodeIDStorageBackend::String::const_iterator it = val.begin(); + + for (uint8_t byte_index = 0; byte_index < NumBytes; byte_index++) + { + out_value[byte_index] = static_cast(convertLowerCaseHexCharToNibble(static_cast(*it++)) << 4); + out_value[byte_index] = static_cast(convertLowerCaseHexCharToNibble(static_cast(*it++)) | + out_value[byte_index]); + } + + return true; +} + +} // dynamic_node_id_server_impl + +} diff --git a/libuavcan/test/protocol/dynamic_node_id_allocation_server.cpp b/libuavcan/test/protocol/dynamic_node_id_allocation_server.cpp index ed9b785c8d..39af887c1a 100644 --- a/libuavcan/test/protocol/dynamic_node_id_allocation_server.cpp +++ b/libuavcan/test/protocol/dynamic_node_id_allocation_server.cpp @@ -3,10 +3,117 @@ */ #include +#include #include #include "helpers.hpp" -TEST(DynamicNodeIDAllocationClient, MarshallingStorageDecorator) +class StorageBackend : public uavcan::IDynamicNodeIDStorageBackend { +public: + typedef std::map Container; + Container container_; + virtual String get(const String& key) const + { + const Container::const_iterator it = container_.find(key); + if (it == container_.end()) + { + return String(); + } + return it->second; + } + + virtual void set(const String& key, const String& value) + { + container_[key] = value; + } + + void printContainer() const + { + for (Container::const_iterator it = container_.begin(); it != container_.end(); ++it) + { + std::cout << it->first.c_str() << "\t" << it->second.c_str() << std::endl; + } + } +}; + +TEST(DynamicNodeIDAllocationServer, MarshallingStorageDecorator) +{ + StorageBackend st; + + uavcan::dynamic_node_id_server_impl::MarshallingStorageDecorator marshaler(st); + + uavcan::IDynamicNodeIDStorageBackend::String key; + + /* + * uint32 + */ + uint32_t u32 = 0; + + key = "foo"; + u32 = 0; + ASSERT_TRUE(marshaler.setAndGetBack(key, u32)); + ASSERT_EQ(0, u32); + + key = "bar"; + u32 = 0xFFFFFFFF; + ASSERT_TRUE(marshaler.setAndGetBack(key, u32)); + ASSERT_EQ(0xFFFFFFFF, u32); + ASSERT_TRUE(marshaler.get(key, u32)); + ASSERT_EQ(0xFFFFFFFF, u32); + + key = "foo"; + ASSERT_TRUE(marshaler.get(key, u32)); + ASSERT_EQ(0, u32); + + key = "the_cake_is_a_lie"; + ASSERT_FALSE(marshaler.get(key, u32)); + ASSERT_EQ(0, u32); + + /* + * uint8[16] + */ + uavcan::protocol::dynamic_node_id::server::Entry::FieldTypes::unique_id array; + + key = "a"; + // Set zero + ASSERT_TRUE(marshaler.setAndGetBack(key, array)); + for (uint8_t i = 0; i < 16; i++) + { + ASSERT_EQ(0, array[i]); + } + + // Make sure this will not be interpreted as uint32 + ASSERT_FALSE(marshaler.get(key, u32)); + ASSERT_EQ(0, u32); + + // Set pattern + for (uint8_t i = 0; i < 16; i++) + { + array[i] = uint8_t(i + 1); + } + ASSERT_TRUE(marshaler.setAndGetBack(key, array)); + for (uint8_t i = 0; i < 16; i++) + { + ASSERT_EQ(i + 1, array[i]); + } + + // Set another pattern + for (uint8_t i = 0; i < 16; i++) + { + array[i] = uint8_t(i | (i << 4)); + } + ASSERT_TRUE(marshaler.setAndGetBack(key, array)); + for (uint8_t i = 0; i < 16; i++) + { + ASSERT_EQ(uint8_t(i | (i << 4)), array[i]); + } + + // Make sure uint32 cannot be interpreted as an array + key = "foo"; + ASSERT_FALSE(marshaler.get(key, array)); + + // Nonexistent key + key = "the_cake_is_a_lie"; + ASSERT_FALSE(marshaler.get(key, array)); }