From 638de081150ef4f333accae5577057ae3d5cd682 Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Wed, 27 May 2015 14:52:41 +0300 Subject: [PATCH] CentralizedServer - storage implementation and test --- .../centralized/storage.hpp | 241 ++++++++++++++++++ .../centralized/storage.cpp | 115 +++++++++ 2 files changed, 356 insertions(+) create mode 100644 libuavcan/include/uavcan/protocol/dynamic_node_id_server/centralized/storage.hpp create mode 100644 libuavcan/test/protocol/dynamic_node_id_server/centralized/storage.cpp diff --git a/libuavcan/include/uavcan/protocol/dynamic_node_id_server/centralized/storage.hpp b/libuavcan/include/uavcan/protocol/dynamic_node_id_server/centralized/storage.hpp new file mode 100644 index 0000000000..280e077f38 --- /dev/null +++ b/libuavcan/include/uavcan/protocol/dynamic_node_id_server/centralized/storage.hpp @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2015 Pavel Kirienko + */ + +#ifndef UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_CENTRALIZED_STORAGE_HPP_INCLUDED +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_CENTRALIZED_STORAGE_HPP_INCLUDED + +#include +#include +#include +#include + +namespace uavcan +{ +namespace dynamic_node_id_server +{ +namespace centralized +{ +/** + * This class transparently replicates its state to the storage backend, keeping the most recent state in memory. + * Writes are slow, reads are instantaneous. + */ +class Storage +{ +public: + typedef uint8_t Size; + + enum { Capacity = NodeID::Max }; + + struct Entry + { + UniqueID unique_id; + NodeID node_id; + + bool operator==(const Entry& rhs) const + { + return unique_id == rhs.unique_id && + node_id == rhs.node_id; + } + }; + +private: + IStorageBackend& storage_; + Entry entries_[Capacity]; + Size size_; + + static IStorageBackend::String getSizeKey() { return "size"; } + + static IStorageBackend::String makeEntryKey(Size index, const char* postfix) + { + IStorageBackend::String str; + // "0_foobar" + str.appendFormatted("%d", int(index)); + str += "_"; + str += postfix; + return str; + } + + int readEntryFromStorage(Size index, Entry& out_entry) + { + const StorageMarshaller io(storage_); + + // Unique ID + if (io.get(makeEntryKey(index, "unique_id"), out_entry.unique_id) < 0) + { + return -ErrFailure; + } + + // Node ID + uint32_t node_id = 0; + if (io.get(makeEntryKey(index, "node_id"), node_id) < 0) + { + return -ErrFailure; + } + if (node_id > NodeID::Max || node_id == 0) + { + return -ErrFailure; + } + out_entry.node_id = NodeID(static_cast(node_id)); + + return 0; + } + + int writeEntryToStorage(Size index, const Entry& entry) + { + Entry temp = entry; + + StorageMarshaller io(storage_); + + // Unique ID + if (io.setAndGetBack(makeEntryKey(index, "unique_id"), temp.unique_id) < 0) + { + return -ErrFailure; + } + + // Node ID + uint32_t node_id = entry.node_id.get(); + if (io.setAndGetBack(makeEntryKey(index, "node_id"), node_id) < 0) + { + return -ErrFailure; + } + temp.node_id = NodeID(static_cast(node_id)); + + return (temp == entry) ? 0 : -ErrFailure; + } + +public: + Storage(IStorageBackend& storage) + : storage_(storage) + , size_(0) + { } + + /** + * This method reads all entries from the storage. + */ + int init() + { + StorageMarshaller io(storage_); + + // Reading size + size_ = 0; + { + uint32_t value = 0; + if (io.get(getSizeKey(), value) < 0) + { + if (storage_.get(getSizeKey()).empty()) + { + int res = io.setAndGetBack(getSizeKey(), value); + if (res < 0) + { + return res; + } + return (value == 0) ? 0 : -ErrFailure; + } + else + { + // There's some data in the storage, but it cannot be parsed - reporting an error + return -ErrFailure; + } + } + + if (value > Capacity) + { + return -ErrFailure; + } + + size_ = Size(value); + } + + // Restoring entries + for (Size index = 0; index < size_; index++) + { + const int result = readEntryFromStorage(index, entries_[index]); + if (result < 0) + { + return result; + } + } + + return 0; + } + + /** + * This method invokes storage IO. + * Returned value indicates whether the entry was successfully appended. + */ + int add(const NodeID node_id, const UniqueID& unique_id) + { + if (size_ == Capacity) + { + return -ErrLogic; + } + + Entry entry; + entry.node_id = node_id; + entry.unique_id = unique_id; + + // If next operations fail, we'll get a dangling entry, but it's absolutely OK. + int res = writeEntryToStorage(size_, entry); + if (res < 0) + { + return res; + } + + // Updating the size + StorageMarshaller io(storage_); + uint32_t new_size_index = size_ + 1U; + res = io.setAndGetBack(getSizeKey(), new_size_index); + if (res < 0) + { + return res; + } + if (new_size_index != size_ + 1U) + { + return -ErrFailure; + } + + entries_[size_] = entry; + size_++; + + return 0; + } + + /** + * Returns nullptr if there's no such entry. + */ + const Entry* findByNodeID(const NodeID node_id) const + { + for (Size i = 0; i < size_; i++) + { + if (entries_[i].node_id == node_id) + { + return &entries_[i]; + } + } + return NULL; + } + + /** + * Returns nullptr if there's no such entry. + */ + const Entry* findByUniqueID(const UniqueID& unique_id) const + { + for (Size i = 0; i < size_; i++) + { + if (entries_[i].unique_id == unique_id) + { + return &entries_[i]; + } + } + return NULL; + } + + Size getSize() const { return size_; } +}; + +} +} +} + +#endif // Include guard diff --git a/libuavcan/test/protocol/dynamic_node_id_server/centralized/storage.cpp b/libuavcan/test/protocol/dynamic_node_id_server/centralized/storage.cpp new file mode 100644 index 0000000000..b2d4e83819 --- /dev/null +++ b/libuavcan/test/protocol/dynamic_node_id_server/centralized/storage.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2015 Pavel Kirienko + */ + +#include +#include +#include "../../helpers.hpp" +#include "../memory_storage_backend.hpp" + + +TEST(dynamic_node_id_server_centralized_Storage, Basic) +{ + using namespace uavcan::dynamic_node_id_server::centralized; + + // No data in the storage - initializing empty + { + MemoryStorageBackend storage; + Storage stor(storage); + + ASSERT_EQ(0, storage.getNumKeys()); + ASSERT_LE(0, stor.init()); + + ASSERT_EQ(1, storage.getNumKeys()); + ASSERT_EQ(0, stor.getSize()); + + ASSERT_FALSE(stor.findByNodeID(1)); + ASSERT_FALSE(stor.findByNodeID(0)); + } + // Nonempty storage, one item + { + MemoryStorageBackend storage; + Storage stor(storage); + + storage.set("size", "1"); + ASSERT_EQ(-uavcan::ErrFailure, stor.init()); // Expected one entry, none found + ASSERT_EQ(1, storage.getNumKeys()); + + storage.set("0_unique_id", "00000000000000000000000000000000"); + storage.set("0_node_id", "0"); + ASSERT_EQ(-uavcan::ErrFailure, stor.init()); // Invalid entry - zero Node ID + + storage.set("0_node_id", "1"); + ASSERT_LE(0, stor.init()); // OK now + + ASSERT_EQ(3, storage.getNumKeys()); + ASSERT_EQ(1, stor.getSize()); + + ASSERT_TRUE(stor.findByNodeID(1)); + ASSERT_FALSE(stor.findByNodeID(0)); + } + // Nonempty storage, broken data + { + MemoryStorageBackend storage; + Storage stor(storage); + + storage.set("size", "foobar"); + ASSERT_EQ(-uavcan::ErrFailure, stor.init()); // Bad value + + storage.set("size", "128"); + ASSERT_EQ(-uavcan::ErrFailure, stor.init()); // Bad value + + storage.set("size", "1"); + ASSERT_EQ(-uavcan::ErrFailure, stor.init()); // No items + ASSERT_EQ(1, storage.getNumKeys()); + + storage.set("0_unique_id", "00000000000000000000000000000000"); + storage.set("0_node_id", "128"); // Bad value (127 max) + ASSERT_EQ(-uavcan::ErrFailure, stor.init()); // Failed + + storage.set("0_node_id", "127"); + ASSERT_LE(0, stor.init()); // OK now + ASSERT_EQ(1, stor.getSize()); + + ASSERT_TRUE(stor.findByNodeID(127)); + ASSERT_FALSE(stor.findByNodeID(0)); + + ASSERT_EQ(3, storage.getNumKeys()); + } + // Nonempty storage, many items + { + MemoryStorageBackend storage; + Storage stor(storage); + + storage.set("size", "2"); + storage.set("0_unique_id", "00000000000000000000000000000000"); + storage.set("0_node_id", "1"); + storage.set("1_unique_id", "0123456789abcdef0123456789abcdef"); + storage.set("1_node_id", "127"); + + ASSERT_LE(0, stor.init()); // OK now + ASSERT_EQ(5, storage.getNumKeys()); + ASSERT_EQ(2, stor.getSize()); + + ASSERT_TRUE(stor.findByNodeID(1)); + ASSERT_TRUE(stor.findByNodeID(127)); + ASSERT_FALSE(stor.findByNodeID(0)); + + uavcan::protocol::dynamic_node_id::server::Entry::FieldTypes::unique_id uid; + uid[0] = 0x01; + uid[1] = 0x23; + uid[2] = 0x45; + uid[3] = 0x67; + uid[4] = 0x89; + uid[5] = 0xab; + uid[6] = 0xcd; + uid[7] = 0xef; + uavcan::copy(uid.begin(), uid.begin() + 8, uid.begin() + 8); + + ASSERT_TRUE(stor.findByUniqueID(uid)); + ASSERT_EQ(127, stor.findByUniqueID(uid)->node_id.get()); + ASSERT_EQ(uid, stor.findByNodeID(127)->unique_id); + } +} + +