CentralizedServer - storage implementation and test

This commit is contained in:
Pavel Kirienko 2015-05-27 14:52:41 +03:00
parent 82c24967e7
commit 638de08115
2 changed files with 356 additions and 0 deletions

View File

@ -0,0 +1,241 @@
/*
* Copyright (C) 2015 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_CENTRALIZED_STORAGE_HPP_INCLUDED
#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_CENTRALIZED_STORAGE_HPP_INCLUDED
#include <uavcan/build_config.hpp>
#include <uavcan/debug.hpp>
#include <uavcan/protocol/dynamic_node_id_server/storage_marshaller.hpp>
#include <uavcan/protocol/dynamic_node_id_server/event.hpp>
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<uint8_t>(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<uint8_t>(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

View File

@ -0,0 +1,115 @@
/*
* Copyright (C) 2015 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#include <gtest/gtest.h>
#include <uavcan/protocol/dynamic_node_id_server/centralized/storage.hpp>
#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);
}
}