Skip to content

Commit

Permalink
Merge pull request #226 from 107-systems/reg-storage
Browse files Browse the repository at this point in the history
Support permanent register value storage/retrieval.
  • Loading branch information
aentinger committed Apr 11, 2023
2 parents 80025d1 + d8660c8 commit 27bb144
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/107-Arduino-Cyphal.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@
#include "Subscription.hpp"
#include "ServiceClient.hpp"
#include "ServiceServer.hpp"
#include "util/storage/register_storage.hpp"

#endif /* _107_ARDUINO_CYPHAL_H_ */
2 changes: 1 addition & 1 deletion src/ServiceClient.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ template<typename T_REQ, typename T_RSP, typename OnResponseCb>
bool ServiceClient<T_REQ, T_RSP, OnResponseCb>::onTransferReceived(CanardRxTransfer const & transfer)
{
/* Deserialize the response message. */
T_RSP rsp{};
T_RSP rsp;
nunavut::support::const_bitspan rsp_bitspan(static_cast<uint8_t *>(transfer.payload), transfer.payload_size);
auto const rc = deserialize(rsp, rsp_bitspan);
if (!rc) return false;
Expand Down
2 changes: 1 addition & 1 deletion src/ServiceServer.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ template<typename T_REQ, typename T_RSP, typename OnRequestCb>
bool ServiceServer<T_REQ, T_RSP, OnRequestCb>::onTransferReceived(CanardRxTransfer const & transfer)
{
/* Deserialize the request message. */
T_REQ req{};
T_REQ req;
nunavut::support::const_bitspan req_buf_bitspan(static_cast<uint8_t *>(transfer.payload), transfer.payload_size);
auto const req_rc = deserialize(req, req_buf_bitspan);
if (!req_rc) return false;
Expand Down
2 changes: 1 addition & 1 deletion src/Subscription.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Subscription<T, OnReceiveCb>::~Subscription()
template<typename T, typename OnReceiveCb>
bool Subscription<T, OnReceiveCb>::onTransferReceived(CanardRxTransfer const & transfer)
{
T msg{};
T msg;
nunavut::support::const_bitspan msg_bitspan(static_cast<uint8_t *>(transfer.payload), transfer.payload_size);
auto const rc = deserialize(msg, msg_bitspan);
if (!rc) return false;
Expand Down
2 changes: 1 addition & 1 deletion src/util/registry/Registry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class Registry final : public registry::Registry

TAccessResponse onAccess_1_0_Request_Received(TAccessRequest const &req)
{
auto const req_name = std::string_view(reinterpret_cast<const char *>(req.name.name.cbegin()));
auto const req_name = std::string_view(reinterpret_cast<const char *>(req.name.name.cbegin()), req.name.name.size());

/* Try to set the registers value. Note, if this is a RO register
* this call will fail with SetError::Mutability.
Expand Down
63 changes: 63 additions & 0 deletions src/util/storage/KeyValueStorage.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* This software is distributed under the terms of the MIT License.
* Copyright (c) 2020-2023 LXRobotics.
* Author: Pavel Kirienko <pavel@opencyphal.org>
* Contributors: https://github.com/107-systems/107-Arduino-Cyphal/graphs/contributors.
*/

#pragma once

#if __GNUC__ >= 11

#include <string_view>
#include <optional>
#include <variant>

namespace cyphal::support::platform::storage
{

enum class Error : std::uint8_t
{
Existence, ///< Entry does not exist but should; or exists but shouldn't.
API, ///< Bad API invocation (e.g., null pointer).
Capacity, ///< No space left on the storage device.
IO, ///< Device input/output error.
Internal, ///< Internal failure in the filesystem (storage corruption or logic error).
};

namespace interface
{

/// Key-value storage provides a very simple API for storing and retrieving named blobs.
/// The underlying storage implementation is required to be power-loss tolerant and to
/// validate data integrity per key (e.g., using CRC and such).
/// This interface is fully blocking and should only be used during initialization and shutdown,
/// never during normal operation. Non-blocking adapters can be built on top of it.
class KeyValueStorage
{
public:
KeyValueStorage() = default;
KeyValueStorage(const KeyValueStorage&) = delete;
KeyValueStorage(KeyValueStorage&&) = delete;
auto operator=(const KeyValueStorage&) -> KeyValueStorage& = delete;
auto operator=(KeyValueStorage&&) -> KeyValueStorage& = delete;
virtual ~KeyValueStorage() = default;

/// The return value is the number of bytes read into the buffer or the error.
[[nodiscard]] virtual auto get(const std::string_view key, const std::size_t size, void* const data) const
-> std::variant<Error, std::size_t> = 0;

/// Existing data, if any, is replaced entirely. New file and its parent directories created implicitly.
/// Either all or none of the data bytes are written.
[[nodiscard]] virtual auto put(const std::string_view key, const std::size_t size, const void* const data)
-> std::optional<Error> = 0;

/// Remove key. If the key does not exist, the existence error is returned.
[[nodiscard]] virtual auto drop(const std::string_view key) -> std::optional<Error> = 0;
};

} /* interface */

} /* cyphal::support::platform::storage */

#endif /* __GNUC__ >= 11 */
138 changes: 138 additions & 0 deletions src/util/storage/register_storage.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/**
* This software is distributed under the terms of the MIT License.
* Copyright (c) 2020-2023 LXRobotics.
* Author: Pavel Kirienko <pavel@opencyphal.org>
* Contributors: https://github.com/107-systems/107-Arduino-Cyphal/graphs/contributors.
*/

#pragma once

#if __GNUC__ >= 11

#include "KeyValueStorage.hpp"

#include <functional>

#include "../registry/registry_base.hpp"

namespace cyphal::support
{

/// Scan all persistent registers in the registry and load their values from the storage if present.
/// Each register is loaded from a separate file, the file name equals the name of the register (no extension).
/// Stored registers that are not present in the registry will not be loaded.
/// The serialization format is simply the Cyphal DSDL.
/// In case of error, only part of the registers may be loaded and the registry will be left in an inconsistent state.
[[nodiscard]] inline std::optional<platform::storage::Error> load(const platform::storage::interface::KeyValueStorage& kv,
registry::IIntrospectableRegistry& rgy)
{
for (std::size_t index = 0; index < rgy.size(); index++)
{
// Find the next register in the registry.
const auto reg_name_storage = rgy.index(index); // This is a little suboptimal but we don't care.
const auto reg_name = std::string_view(reinterpret_cast<const char *>(reg_name_storage.name.cbegin()), reg_name_storage.name.size());
if (reg_name.empty())
{
break; // No more registers to load.
}
// If we get nothing, this means that the register has disappeared from the storage.
if (const auto reg_meta = rgy.get(reg_name); reg_meta && reg_meta.value().flags.persistent)
{
// We will attempt to restore the register even if it is not mutable,
// as it is not incompatible with the protocol.
std::array<std::uint8_t, uavcan::_register::Value_1_0::_traits_::SerializationBufferSizeBytes> serialized;
const auto kv_get_result = kv.get(reg_name, serialized.size(), serialized.data());
if (const auto* const err = std::get_if<platform::storage::Error>(&kv_get_result))
{
if (platform::storage::Error::Existence != *err)
{
return *err;
}
// The register is simply not present in the storage, which is OK.
}
else
{
registry::Value value;
// Invalid data in the storage will be ignored.
nunavut::support::const_bitspan serialized_bitspan(serialized.data(), serialized.size());
auto const rc = deserialize(value, serialized_bitspan);
if (rc)
{
// Assign the value to the register.
// Shall it fail, the error is likely to be corrected during the next save().
(void) rgy.set(reg_name, value);
}
}
}
}
return std::nullopt;
}

/// The register savior is the counterpart of load().
/// Saves all persistent mutable registers from the registry to the storage.
/// Registers that are not persistent OR not mutable will not be saved;
/// the reason immutable registers are not saved is that they are assumed to be constant or runtime-computed,
/// so there is no point wasting storage on them (which may be limited).
/// Eventually this logic should be decoupled from the network register presentation facade by introducing more
/// fine-grained register flags, such as "internally mutable" and "externally mutable".
///
/// Existing stored registers that are not found in the registry will not be altered.
/// In case of failure, one failure handling strategy is to clear or reformat the entire storage and try again.
///
/// The removal predicate, if provided, allows the caller to specify which registers need to be removed from the
/// storage instead of being saved. This is useful for implementing the "factory reset" feature.
template <typename ResetPredicate>
[[nodiscard]] std::optional<platform::storage::Error> save(platform::storage::interface::KeyValueStorage& kv,
const registry::IIntrospectableRegistry& rgy,
ResetPredicate const reset_predicate)
{
for (std::size_t index = 0; index < rgy.size(); index++)
{
const auto reg_name_storage = rgy.index(index); // This is a little suboptimal but we don't care.
const auto reg_name = std::string_view(reinterpret_cast<const char *>(reg_name_storage.name.cbegin()), reg_name_storage.name.size());
if (reg_name.empty())
{
break; // No more registers to load.
}
// Reset is handled before any other checks to enhance forward compatibility.
if (reset_predicate(reg_name))
{
if (const auto err = kv.drop(reg_name); err && (err != platform::storage::Error::Existence))
{
return err;
}
}
// If we get nothing, this means that the register has disappeared from the storage.
// We do not save immutable registers because they are assumed to be constant, so no need to waste storage.
else if (const auto reg_meta = rgy.get(reg_name);
reg_meta && reg_meta.value().flags.persistent && reg_meta.value().flags.mutable_)
{
// Now we have the register and we know that it is persistent, so we can save it.
std::array<std::uint8_t, uavcan::_register::Value_1_0::_traits_::SerializationBufferSizeBytes> serialized;
nunavut::support::bitspan serialized_bitspan{serialized};
auto const rc = serialize(reg_meta.value().value, serialized_bitspan);
if (!rc)
{
std::abort(); // This should never happen.
}
if (const auto err = kv.put(reg_name, *rc, serialized.data()); err)
{
return err;
}
}
else
{
(void) 0; // Nothing to do -- the register needs to be neither reset nor saved.
}
}
return std::nullopt;
}
[[nodiscard]] inline std::optional<platform::storage::Error> save(platform::storage::interface::KeyValueStorage& kv,
const registry::IIntrospectableRegistry& rgy)
{
return save(kv, rgy, [](std::string_view) { return false; });
}

} /* namespace cyphal::support */

#endif /* __GNUC__ >= 11 */

0 comments on commit 27bb144

Please sign in to comment.