-
-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #226 from 107-systems/reg-storage
Support permanent register value storage/retrieval.
- Loading branch information
Showing
7 changed files
with
206 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 */ |