From 78db2c2f0fb47110ebced201a74ea92542684f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20H=C3=BC=C3=9Fler?= Date: Sat, 29 Apr 2023 16:15:19 +0200 Subject: [PATCH 01/12] sys/auto_init: expose auto_init_module() ... and define a general _AUT_INIT() macro taking an XFA as parameter to allow for a second level auto-init XFA --- sys/auto_init/auto_init.c | 18 ++++++----------- sys/include/auto_init_utils.h | 37 ++++++++++++++++++++++++++++------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c index 8f2212c3360e..56e985ca34b0 100644 --- a/sys/auto_init/auto_init.c +++ b/sys/auto_init/auto_init.c @@ -27,19 +27,8 @@ #include "auto_init_priorities.h" #include "kernel_defines.h" -#define ENABLE_DEBUG CONFIG_AUTO_INIT_ENABLE_DEBUG -#include "debug.h" - XFA_INIT_CONST(auto_init_module_t, auto_init_xfa); -static inline void _auto_init_module(const volatile auto_init_module_t *module) -{ -#if IS_ACTIVE(CONFIG_AUTO_INIT_ENABLE_DEBUG) - DEBUG("auto_init: %s (%u)\n", module->name, module->prio); -#endif - module->init(); -} - #if IS_USED(MODULE_AUTO_INIT_ZTIMER) extern void ztimer_init(void); AUTO_INIT(ztimer_init, @@ -174,6 +163,11 @@ extern void auto_init_vfs(void); AUTO_INIT(auto_init_vfs, AUTO_INIT_PRIO_MOD_VFS); #endif +#if IS_USED(MODULE_CONFIGURATION) +extern void auto_init_configuration(void); +AUTO_INIT(auto_init_configuration, + AUTO_INIT_PRIO_MOD_CONFIGURATION); +#endif #if IS_USED(MODULE_AUTO_INIT_GNRC_IPV6_NIB) extern void gnrc_ipv6_nib_init(void); AUTO_INIT(gnrc_ipv6_nib_init, @@ -348,6 +342,6 @@ AUTO_INIT(auto_init_gnrc_ipv6_static_addr, void auto_init(void) { for (unsigned i = 0; i < XFA_LEN(auto_init_module_t, auto_init_xfa); i++) { - _auto_init_module(&auto_init_xfa[i]); + auto_init_module(&auto_init_xfa[i]); } } diff --git a/sys/include/auto_init_utils.h b/sys/include/auto_init_utils.h index 4aefda05a644..2703ac34a1c8 100644 --- a/sys/include/auto_init_utils.h +++ b/sys/include/auto_init_utils.h @@ -27,6 +27,7 @@ #ifndef AUTO_INIT_UTILS_H #define AUTO_INIT_UTILS_H +#include #include #include "xfa.h" #include "macros/xtstr.h" @@ -69,30 +70,52 @@ typedef struct { #if IS_ACTIVE(CONFIG_AUTO_INIT_ENABLE_DEBUG) || defined(DOXYGEN) /** - * @brief Add a module to the auto-initialization array + * @brief Add a module to an auto-initialization array @p xfa * + * @param xfa An XFA * @param function Function to be called on initialization @ref auto_init_fn_t * @param priority Priority level @ref auto_init_prio_t */ -#define AUTO_INIT(function, priority) \ - XFA_CONST(auto_init_module_t, auto_init_xfa, priority) \ - auto_init_xfa_ ## function \ +#define _AUTO_INIT(xfa, function, priority) \ + XFA_CONST(auto_init_module_t, xfa, priority) \ + xfa ## _ ## function \ = { .init = (auto_init_fn_t)function, \ .prio = priority, \ .name = XTSTR(function) } #else -#define AUTO_INIT(function, priority) \ - XFA_CONST(auto_init_module_t, auto_init_xfa, priority) \ - auto_init_xfa_ ## function \ +#define _AUTO_INIT(xfa, function, priority) \ + XFA_CONST(auto_init_module_t, xfa, priority) \ + xfa ## _ ## function \ = { .init = (auto_init_fn_t)function } #endif +/** + * @brief Add a module to the auto-initialization array + * + * @param function Function to be called on initialization @ref auto_init_fn_t + * @param priority Priority level @ref auto_init_prio_t + */ +#define AUTO_INIT(function, priority) _AUTO_INIT(auto_init_xfa, function, priority) + /** * @brief Construct a priority value equal to @p priority + 1, * to be used with @ref AUTO_INIT */ #define AUTO_INIT_PRIORITY_AFTER(priority) RIOT_PP_SUCCESSOR(priority) +/** + * @brief Call the auto-init function of module @p module + * + * @param[in] module Module to be initialized + */ +static inline void auto_init_module(const volatile auto_init_module_t *module) +{ +#if IS_ACTIVE(CONFIG_AUTO_INIT_ENABLE_DEBUG) + printf("auto_init: %s (%u)\n", module->name, module->prio); +#endif + module->init(); +} + #ifdef __cplusplus } #endif From 65d68457ba7a22db9c248c735b7691d3ef63bb34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20H=C3=BC=C3=9Fler?= Date: Sat, 29 Apr 2023 20:18:18 +0200 Subject: [PATCH 02/12] sys: add persistent storage runtime configuration module --- makefiles/pseudomodules.inc.mk | 1 + sys/Kconfig | 4 + sys/Makefile | 3 + sys/Makefile.dep | 4 + sys/Makefile.include | 4 + sys/auto_init/Makefile | 4 + sys/auto_init/configuration/Makefile | 3 + .../configuration/auto_init_configuration.c | 61 ++ sys/auto_init/include/auto_init_priorities.h | 6 + sys/configuration/Kconfig | 18 + sys/configuration/Makefile | 11 + sys/configuration/Makefile.dep | 24 + sys/configuration/Makefile.include | 14 + sys/configuration/configuration.c | 957 ++++++++++++++++++ sys/configuration/default_handlers.c | 876 ++++++++++++++++ .../include/configuration_iterator.h | 79 ++ sys/include/configuration.h | 956 +++++++++++++++++ 17 files changed, 3025 insertions(+) create mode 100644 sys/auto_init/configuration/Makefile create mode 100644 sys/auto_init/configuration/auto_init_configuration.c create mode 100644 sys/configuration/Kconfig create mode 100644 sys/configuration/Makefile create mode 100644 sys/configuration/Makefile.dep create mode 100644 sys/configuration/Makefile.include create mode 100644 sys/configuration/configuration.c create mode 100644 sys/configuration/default_handlers.c create mode 100644 sys/configuration/include/configuration_iterator.h create mode 100644 sys/include/configuration.h diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 253086ed4d6e..f842800e83a0 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -609,6 +609,7 @@ PSEUDOMODULES += test_utils_main_exit_cb # All auto_init modules are pseudomodules PSEUDOMODULES += auto_init_% NO_PSEUDOMODULES += auto_init_can +NO_PSEUDOMODULES += auto_init_configuration NO_PSEUDOMODULES += auto_init_loramac NO_PSEUDOMODULES += auto_init_multimedia NO_PSEUDOMODULES += auto_init_security diff --git a/sys/Kconfig b/sys/Kconfig index 4573def28c14..65a11ce4a3be 100644 --- a/sys/Kconfig +++ b/sys/Kconfig @@ -8,6 +8,10 @@ menu "System" rsource "auto_init/Kconfig" rsource "chunked_ringbuffer/Kconfig" +rsource "clif/Kconfig" +rsource "color/Kconfig" +rsource "configuration/Kconfig" +rsource "crypto/Kconfig" rsource "congure/Kconfig" rsource "debug_irq_disable/Kconfig" rsource "entropy_source/Kconfig" diff --git a/sys/Makefile b/sys/Makefile index 835383e8b9a8..f4ce60317876 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -17,6 +17,9 @@ endif ifneq (,$(filter telnet,$(USEMODULE))) DIRS += net/application_layer/telnet endif +ifneq (,$(filter configuration,$(USEMODULE))) + DIRS += configuration +endif ifneq (,$(filter constfs,$(USEMODULE))) DIRS += fs/constfs endif diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 00afb08a25ee..d890e9f97418 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -705,4 +705,8 @@ ifneq (,$(filter wifi_scan_list,$(USEMODULE))) USEMODULE += l2scan_list endif +ifneq (,$(filter configuration configuration_%,$(USEMODULE))) + include $(RIOTBASE)/sys/configuration/Makefile.dep +endif + include $(RIOTBASE)/sys/test_utils/Makefile.dep diff --git a/sys/Makefile.include b/sys/Makefile.include index 617f62fce931..3dffd0b48e0f 100644 --- a/sys/Makefile.include +++ b/sys/Makefile.include @@ -199,3 +199,7 @@ endif ifneq (,$(filter nanocoap,$(USEMODULE))) include $(RIOTBASE)/sys/net/application_layer/nanocoap/Makefile.include endif + +ifneq (,$(filter configuration configuration_%,$(USEMODULE))) + include $(RIOTBASE)/sys/configuration/Makefile.include +endif diff --git a/sys/auto_init/Makefile b/sys/auto_init/Makefile index 7f5b3e3b6f23..9ecbff75ea15 100644 --- a/sys/auto_init/Makefile +++ b/sys/auto_init/Makefile @@ -10,6 +10,10 @@ ifneq (,$(filter auto_init_can,$(USEMODULE))) DIRS += can endif +ifneq (,$(filter auto_init_configuration,$(USEMODULE))) + DIRS += configuration +endif + ifneq (,$(filter auto_init_loramac,$(USEMODULE))) DIRS += loramac endif diff --git a/sys/auto_init/configuration/Makefile b/sys/auto_init/configuration/Makefile new file mode 100644 index 000000000000..b96f6d021c2e --- /dev/null +++ b/sys/auto_init/configuration/Makefile @@ -0,0 +1,3 @@ +MODULE = auto_init_configuration + +include $(RIOTBASE)/Makefile.base diff --git a/sys/auto_init/configuration/auto_init_configuration.c b/sys/auto_init/configuration/auto_init_configuration.c new file mode 100644 index 000000000000..84d306f20e0b --- /dev/null +++ b/sys/auto_init/configuration/auto_init_configuration.c @@ -0,0 +1,61 @@ +/* + * Copyright 2023 ML!PA Consulting GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + * + */ + +/** + * @ingroup sys_auto_init_configuration + * @{ + * + * @file + * @brief Auto initialization for configuration subsystems + * + * @author Fabian Hüßler + * + * @} + */ + +#ifdef MODULE_CONFIGURATION + +#include "auto_init_utils.h" +#include "configuration.h" +#if IS_USED(MODULE_RIOTCONF) +#include "configuration_backend_riotconf.h" +#endif + +XFA_INIT_CONST(auto_init_module_t, auto_init_configuration_xfa); + +#ifndef CONFIG_CONFIGURATION_ROOT_BACKEND_OPS +#if IS_USED(MODULE_RIOTCONF) +#define CONFIG_CONFIGURATION_ROOT_BACKEND_OPS &conf_backend_riotconf_ops +#else +#define CONFIG_CONFIGURATION_ROOT_BACKEND_OPS NULL +#endif +#endif + +static const CONF_BACKEND(_root_backend, + CONFIG_CONFIGURATION_ROOT_BACKEND_OPS, + CONF_FMT_CBOR); + +__attribute__((weak)) +void auto_init_configuration_root_backend_init(void) +{ + *configuration_get_src_backend(configuration_get_root()) = &_root_backend; + *configuration_get_dst_backend(configuration_get_root()) = &_root_backend; +} + +void auto_init_configuration(void) +{ + for (unsigned i = 0; i < XFA_LEN(auto_init_module_t, auto_init_configuration_xfa); i++) { + auto_init_module(&auto_init_configuration_xfa[i]); + } + auto_init_configuration_root_backend_init(); +} + +#else +typedef int dont_be_pedantic; +#endif /* MODULE_CONFIGURATION */ diff --git a/sys/auto_init/include/auto_init_priorities.h b/sys/auto_init/include/auto_init_priorities.h index d87d43cdcb45..dcf77df8cb77 100644 --- a/sys/auto_init/include/auto_init_priorities.h +++ b/sys/auto_init/include/auto_init_priorities.h @@ -197,6 +197,12 @@ extern "C" { */ #define AUTO_INIT_PRIO_MOD_VFS 1260 #endif +#ifndef AUTO_INIT_PRIO_MOD_CONFIGURATION +/** + * @brief Configuration priority + */ +#define AUTO_INIT_PRIO_MOD_CONFIGURATION 1265 +#endif #ifndef AUTO_INIT_PRIO_MOD_GNRC_IPV6_NIB /** * @brief GNRC IPv6 NIB priority diff --git a/sys/configuration/Kconfig b/sys/configuration/Kconfig new file mode 100644 index 000000000000..ca43a9b4b316 --- /dev/null +++ b/sys/configuration/Kconfig @@ -0,0 +1,18 @@ +# Copyright (c) 2023 ML!PA Consulting GmbH +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. +# + +menuconfig MODULE_CONFIGURATION + bool "Runtime Configuration Module" + help + Backend agnostic runtime configuration module. + +if MODULE_CONFIGURATION + +config CONFIGURATION_DEPTH_MAX + int "Maximum number of nested configuration items" + +endif # MODULE_CONFIGURATION diff --git a/sys/configuration/Makefile b/sys/configuration/Makefile new file mode 100644 index 000000000000..56db6a1a5ed3 --- /dev/null +++ b/sys/configuration/Makefile @@ -0,0 +1,11 @@ +MODULE := configuration + +SUBMODULES := 1 + +SRC := configuration.c default_handlers.c + +ifneq (,$(filter configuration_backend_%,$(USEMODULE))) + DIRS += backend +endif + +include $(RIOTBASE)/Makefile.base diff --git a/sys/configuration/Makefile.dep b/sys/configuration/Makefile.dep new file mode 100644 index 000000000000..1cf4f3442b4b --- /dev/null +++ b/sys/configuration/Makefile.dep @@ -0,0 +1,24 @@ +DEFAULT_MODULE += auto_init_configuration + +USEMODULE += fmt + +ifneq (,$(filter configuration_backend_%,$(USEMODULE))) + USEMODULE += configuration_backend +endif + +ifneq (,$(filter configuration_backend_riotconf,$(USEMODULE))) + USEMODULE += riotconf +endif + +ifneq (,$(filter configuration_backend_flashdb%,$(USEMODULE))) + # this backend needs strings + USEMODULE += configuration_strings + USEMODULE += configuration_backend_flashdb + USEMODULE += flashdb_kvdb + ifneq (,$(filter configuration_backend_flashdb_vfs,$(USEMODULE))) + USEMODULE += flashdb_vfs + USEMODULE += vfs_default + else ifneq (,$(filter configuration_backend_flashdb_mtd,$(USEMODULE))) + USEMODULE += flashdb_mtd + endif +endif diff --git a/sys/configuration/Makefile.include b/sys/configuration/Makefile.include new file mode 100644 index 000000000000..b20fe1175a11 --- /dev/null +++ b/sys/configuration/Makefile.include @@ -0,0 +1,14 @@ +USEMODULE_INCLUDES_configuration := $(LAST_MAKEFILEDIR)/include +USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_configuration) + +# any backend pseudo module +PSEUDOMODULES += configuration_backend_% + +# use if you want to use custom handlers for import, export, delete, encode, decode +PSEUDOMODULES += configuration_custom_operations + +# use if source backend is unequal to destination backend +PSEUDOMODULES += configuration_destination_backend + +# use if you want to construct a string path for a configuration item +PSEUDOMODULES += configuration_strings diff --git a/sys/configuration/configuration.c b/sys/configuration/configuration.c new file mode 100644 index 000000000000..5db465a8291f --- /dev/null +++ b/sys/configuration/configuration.c @@ -0,0 +1,957 @@ +/* + * Copyright (C) 2023 ML!PA Consulting Gmbh + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup sys_configuration + * @{ + * + * @file + * @brief Implementation of a runtime configuration module + * + * @author Fabian Hüßler + * + * @} + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fmt.h" +#include "modules.h" +#include "container.h" +#include "configuration.h" +#include "configuration_iterator.h" +#include "mutex.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#define _HANDLER(n) container_of((n), conf_handler_t, node) +#define _ARRAY_HANDLER(n) container_of((n), conf_array_handler_t, handler) + +#if IS_USED(MODULE_CONFIGURATION_STRINGS) +#define _KEY_BUF(k) ((k)->buf) +#define _KEY_BUF_LEN(k) ((k)->buf_len) +#else +#define _KEY_BUF(k) NULL +#define _KEY_BUF_LEN(k) 0 +#endif + +int configuration_set_handler_default(const conf_handler_t *handler, + conf_key_buf_t *key, const void *val, + size_t *size); + +int configuration_get_handler_default(const conf_handler_t *handler, + conf_key_buf_t *key, void *val, + size_t *size); + +int configuration_import_handler_default(const conf_handler_t *handler, + conf_key_buf_t *key); + +int configuration_export_handler_default(const conf_handler_t *handler, + conf_key_buf_t *key); + +int configuration_delete_handler_default(const conf_handler_t *handler, + conf_key_buf_t *key); + +int configuration_encode_handler_default(const conf_handler_t *handler, + conf_key_buf_t *key, const conf_sid_t *sid_start, + conf_fmt_t fmt, void **enc_data, size_t *enc_size); + +int configuration_decode_handler_default(const conf_handler_t *handler, + conf_key_buf_t *key, conf_sid_t *sid_start, + conf_fmt_t fmt, const void **dec_data, size_t *dec_size); + +#if IS_USED(MODULE_CONFIGURATION_CUSTOM_OPERATIONS) +const conf_handler_backend_ops_t configuration_default_backend_ops = { + .import = configuration_import_handler_default, + .export = configuration_export_handler_default, + .delete = configuration_delete_handler_default, +}; + +const conf_handler_backend_data_ops_t configuration_default_backend_data_ops = { + .encode = configuration_encode_handler_default, + .decode = configuration_decode_handler_default, +}; +#endif + +static CONF_HANDLER_NODE_ID(_conf_root_handler_node_id, 0, UINT64_MAX, ""); + +static CONF_HANDLER(_conf_root_handler, + &_conf_root_handler_node_id, + NULL, + &configuration_default_backend_ops, + &configuration_default_backend_data_ops, + 0, NULL); + +static conf_iterator_restore_t _configuration_iterator_init(conf_iterator_t *iter, + const conf_handler_t *handler, + const conf_key_buf_t *key, + char *key_buf) +{ + (void)key_buf; + assert(iter); + assert(handler); + iter->root = handler; + iter->sp = 0; + iter->stack[iter->sp++] = (conf_iterator_item_t) { + handler + }; + return (conf_iterator_restore_t) { .sid = key->sid +#if IS_USED(MODULE_CONFIGURATION_STRINGS) + , .key_len = key_buf ? strlen(key_buf) : 0 +#endif + }; +} + +static conf_iterator_restore_t _configuration_path_iterator_init(conf_path_iterator_t *iter, + const conf_handler_t *handler, + const conf_key_buf_t *key, + char *key_buf) +{ + (void)key_buf; + assert(iter); + assert(handler); + iter->root = handler; + iter->sp = 0; + iter->stack[iter->sp++] = (conf_path_iterator_item_t) { + handler, + key->sid > handler->node_id->sid_lower + ? _sid_array_index(_ARRAY_HANDLER(handler), key->sid) : 0 + }; + return (conf_iterator_restore_t) { .sid = key->sid +#if IS_USED(MODULE_CONFIGURATION_STRINGS) + , .key_len = key_buf ? strlen(key_buf) : 0 +#endif + }; +} + +static bool _sid_in_node_range(const conf_handler_t *handler, conf_sid_t sid) +{ + return handler->node_id->sid_lower <= sid && sid <= handler->node_id->sid_upper; +} + +static bool _sid_in_array_range(const conf_array_handler_t *array, conf_sid_t sid) +{ + return array->handler.array_id->sid_lower <= sid && sid <= array->handler.array_id->sid_upper; +} + +static bool _sid_in_range(const conf_handler_t *handler, conf_sid_t sid) +{ + if (handler->conf_flags.handles_array) { + return _sid_in_array_range(_ARRAY_HANDLER(handler), sid); + } + else if (!handler->conf_flags.handles_primitive) { + return _sid_in_node_range(handler, sid); + } + return handler->handler_id->sid_lower == sid; +} + +static bool _sid_in_array_bounds(const conf_array_handler_t *array, conf_sid_t sid) +{ + assert(sid > array->handler.array_id->sid_lower); + return _sid_array_index(array, sid) < array->array_size; +} + +static int _configuration_append_segment(const conf_handler_t *next, conf_key_buf_t *key, + char *key_buf, size_t key_buf_len) +{ + (void)next; (void)key; (void)key_buf; (void)key_buf_len; +#if IS_USED(MODULE_CONFIGURATION_STRINGS) + if (key_buf) { + char *buf = key_buf; + size_t size = key_buf_len; + if (*next->node_id->subtree) { + for (int l = 0; l < (int)next->level - 1; l++) { + if (*buf++ != '/') { + return -EINVAL; + } + char *slash; + if ((slash = strchr(buf, '/'))) { + if (isdigit((int)slash[1])) { + if ((slash = strchr(++slash, '/'))) { + buf = slash; + } + } + else { + buf = slash; + } + } + } + if (*buf == '/') { + *buf = '\0'; + } + assert(size > strlen(key_buf) + 1 + strlen(next->node_id->subtree) + 1); + strcat(buf, "/"); + strcat(buf, next->node_id->subtree); + } + } +#endif + return 0; +} + +static void _debug_print(const conf_key_buf_t *key) +{ + (void)key; +#if ENABLE_DEBUG + char ssid[17]; + ssid[fmt_u64_hex(ssid, key->sid)] = '\0'; + DEBUG("configuration: %16s %10u %s\n", ssid, key->offset, + configuration_key_buf((conf_key_t *)key)); +#endif +} + +static int _configuration_append_index(uint32_t index, conf_key_buf_t *key, + char *key_buf, size_t key_buf_len) +{ + (void)index; (void)key; (void)key_buf; (void)key_buf_len; +#if IS_USED(MODULE_CONFIGURATION_STRINGS) + if (key_buf) { + char *buf = key_buf; + size_t size = key_buf_len; + char sindex[11]; + sindex[fmt_u32_dec(sindex, index)] = '\0'; + assert(size > strlen(buf) + 1 + strlen(sindex) + 1); + strcat(buf, "/"); + strcat(buf, sindex); + } +#endif + return 0; +} + +static int _configuration_find_handler_sid(const conf_handler_t **next_handler, + conf_key_buf_t *key, char *key_buf, size_t key_buf_len) +{ + assert(next_handler); + assert(key); + + const conf_handler_t *handler = *next_handler; + + if (!handler || !_sid_in_range(handler, key->sid_normal)) { + return -ENOENT; + } + while (handler) { + if (key->sid_normal > handler->node_id->sid_lower) { + if (handler->conf_flags.handles_array) { + if (!_sid_in_array_bounds(_ARRAY_HANDLER(handler), key->sid_normal)) { + return -ERANGE; + } + uint32_t index = _sid_array_index(_ARRAY_HANDLER(handler), key->sid_normal); + /* Set data offset */ + key->offset += index * handler->size; + /* map to first array element (normalize) */ + key->sid_normal -= (index * handler->array_id->sid_stride); + + if (_configuration_append_index(index, key, key_buf, key_buf_len)) { + return -ENOBUFS; + } + } + } + for (handler = handler->subnodes; handler; handler = _HANDLER(handler->node.next)) { + if (!_sid_in_range(handler, key->sid_normal)) { + continue; + } + if (_configuration_append_segment(handler, key, key_buf, key_buf_len)) { + return -ENOBUFS; + } + *next_handler = handler; + break; + } + } + if (key->sid_normal != (*next_handler)->node_id->sid_lower) { + if ((*next_handler)->conf_flags.handles_array) { + if (!_sid_in_array_bounds(_ARRAY_HANDLER(*next_handler), key->sid_normal)) { + return -ERANGE; + } + } + else { + return -ENOENT; + } + } + _debug_print(key); + return 0; +} + + +static int _configuration_prepare_sid(const conf_handler_t **next_handler, conf_key_buf_t *key, + char *key_buf, size_t key_buf_len) +{ + key->offset = 0; + key->sid_normal = key->sid; +#if IS_USED(MODULE_CONFIGURATION_STRINGS) + if (key_buf) { + memset(key_buf, 0, key_buf_len); + } +#endif + int ret = _configuration_find_handler_sid(next_handler, key, key_buf, key_buf_len); + if (ret < 0) { + (void)ret; + DEBUG("configuration: no handler found %d, SID %llu\n", ret, key->sid); + } + return ret; +} + +static conf_handler_t *_configuration_handler_sid_iterator_next(conf_iterator_t *iter, conf_key_buf_t *key) +{ + if (iter->sp > 0) { + conf_iterator_item_t next = iter->stack[--iter->sp]; + if (next.node != iter->root) { + if (_configuration_append_segment(next.node, key, _KEY_BUF(key), _KEY_BUF_LEN(key))) { + return NULL; + } + key->sid += next.node->node_id->sid_lower - key->sid_normal; + key->sid_normal = next.node->node_id->sid_lower; + if (next.node->node.next) { + assert(iter->sp < ARRAY_SIZE(iter->stack)); + iter->stack[iter->sp++] = (conf_iterator_item_t) { _HANDLER(next.node->node.next) }; + } + } + if (next.node->subnodes) { + assert(iter->sp < ARRAY_SIZE(iter->stack)); + iter->stack[iter->sp++] = (conf_iterator_item_t) { next.node->subnodes }; + } + _debug_print(key); + return (conf_handler_t *)next.node; + } + return NULL; +} + +static conf_handler_t *_configuration_handler_encode_iterator_next(conf_path_iterator_t *iter, + conf_key_buf_t *key, + const conf_sid_t *sid_start) +{ + if (iter->sp > 0) { + conf_path_iterator_item_t next = iter->stack[--iter->sp]; + if (next.node->node_id->sid_lower > key->sid) { + key->sid = next.node->node_id->sid_lower; + key->sid_normal = next.node->node_id->sid_lower; + key->offset = 0; + } + else if (next.node->node_id->sid_lower > key->sid_normal) { + key->sid += next.node->node_id->sid_lower - key->sid_normal; + key->sid_normal = next.node->node_id->sid_lower; + } + bool push_subnodes = false; + if (next.node->conf_flags.handles_array && next.index < _ARRAY_HANDLER(next.node)->array_size) { + assert(iter->sp < ARRAY_SIZE(iter->stack)); + iter->stack[iter->sp++] = (conf_path_iterator_item_t) { next.node, next.index + 1 }; + if (key->sid != *sid_start && key->sid_normal != next.node->node_id->sid_lower) { + if (next.node->node_id->sid_lower + 1 + next.index * next.node->array_id->sid_stride > key->sid) { + key->sid = next.node->node_id->sid_lower + 1 + next.index * next.node->array_id->sid_stride; + key->offset = next.index * next.node->size; + } + else { + key->sid += (next.node->array_id->sid_stride - (key->sid_normal - (next.node->array_id->sid_lower + 1))); + key->offset += next.node->size; + } + key->sid_normal = next.node->node_id->sid_lower + 1; + } + push_subnodes = true; + } + else if (!next.node->conf_flags.handles_primitive && next.index < 1) { + assert(iter->sp < ARRAY_SIZE(iter->stack)); + iter->stack[iter->sp++] = (conf_path_iterator_item_t) { next.node, 1 }; + if (key->sid != *sid_start) { + key->sid += next.node->node_id->sid_lower - key->sid_normal; + key->sid_normal = next.node->node_id->sid_lower; + } + push_subnodes = true; + } + if (!push_subnodes && key->sid != *sid_start) { + if (next.node->node.next) { + assert(iter->sp < ARRAY_SIZE(iter->stack)); + iter->stack[iter->sp++] = (conf_path_iterator_item_t) { _HANDLER(next.node->node.next), 0 }; + } + } + else if (push_subnodes) { + if (next.node->subnodes) { + /* Stop encoding when the backend changes, because subnode is exported to another backend */ + const conf_backend_t *be = *configuration_get_dst_backend(iter->root); + const conf_backend_t *be_h = *configuration_get_dst_backend(next.node); + if (!be_h || be == be_h) { + assert(iter->sp < ARRAY_SIZE(iter->stack)); + iter->stack[iter->sp++] = (conf_path_iterator_item_t) { next.node->subnodes, 0 }; + } + } + } + return (conf_handler_t *)next.node; + } + return NULL; +} + +static conf_handler_t *_configuration_handler_decode_iterator_next(conf_path_iterator_t *iter, + conf_key_buf_t *key, + const conf_handler_t *handler, + const conf_sid_t *sid_start) +{ + if (iter->sp > 0) { + conf_path_iterator_item_t next = iter->stack[--iter->sp]; + if (handler) { + /* got a new key: go deeper by finding the next handler */ + if (!next.node->conf_flags.handles_primitive) { + assert(iter->sp < ARRAY_SIZE(iter->stack)); + iter->stack[iter->sp++] = (conf_path_iterator_item_t) { next.node, next.index}; + } + if (next.node != handler && !handler->conf_flags.handles_primitive) { + uint32_t index = 0; + if (handler->conf_flags.handles_array && key->sid > handler->node_id->sid_lower) { + index = _sid_array_index(_ARRAY_HANDLER(handler), key->sid); + } + assert(iter->sp < ARRAY_SIZE(iter->stack)); + iter->stack[iter->sp++] = (conf_path_iterator_item_t) { handler, index + 1}; + } + } + else { + handler = next.node; + if (handler == iter->root && key->sid == *sid_start) { + /* push back root if nothing changed */ + assert(iter->sp < ARRAY_SIZE(iter->stack)); + iter->stack[iter->sp++] = (conf_path_iterator_item_t) { handler, next.index + 1}; + } + else { + /* no new key: go upper by finishing an element */ + if (handler->conf_flags.handles_array && next.index < _ARRAY_HANDLER(handler)->array_size) { + if (handler->array_id->sid_lower + 1 + next.index * handler->array_id->sid_stride > key->sid) { + key->sid = handler->array_id->sid_lower + 1 + next.index * handler->array_id->sid_stride; + key->offset = next.index * handler->size; + } + else { + key->sid += (handler->array_id->sid_stride - (key->sid_normal - (handler->array_id->sid_lower + 1))); + key->offset += handler->size; + } + key->sid_normal = handler->node_id->sid_lower + 1; + assert(iter->sp < ARRAY_SIZE(iter->stack)); + iter->stack[iter->sp++] = (conf_path_iterator_item_t) { handler, next.index + 1 }; + } + else { + key->sid = handler->node_id->sid_upper; + key->sid_normal = key->sid; + } + } + } + return (conf_handler_t *)handler; + } + return NULL; +} + +static conf_handler_t *_configuration_path_sid_iterator_next(conf_path_iterator_t *iter, + conf_key_buf_t *key, + const conf_sid_t *sid_start) +{ + if (iter->sp > 0) { + conf_path_iterator_item_t next = iter->stack[--iter->sp]; + if (next.node->node_id->sid_lower > key->sid) { + key->sid = next.node->node_id->sid_lower; + key->sid_normal = next.node->node_id->sid_lower; + key->offset = 0; + } + else if (next.node->node_id->sid_lower > key->sid_normal) { + key->sid += next.node->node_id->sid_lower - key->sid_normal; + key->sid_normal = next.node->node_id->sid_lower; + } + if (key->sid != *sid_start && _configuration_append_segment(next.node, key, _KEY_BUF(key), _KEY_BUF_LEN(key))) { + return NULL; + } + if (next.node->conf_flags.handles_array) { + bool skip = key->sid == *sid_start && next.node->node_id->sid_lower < key->sid_normal; + if (next.index == 0) { + if (key->sid_normal == next.node->node_id->sid_lower) { + key->sid++; + (key->sid_normal)++; + } + } + else if (key->sid != *sid_start) { + if (next.node->node_id->sid_lower + 1 + next.index * next.node->array_id->sid_stride > key->sid) { + key->sid = next.node->node_id->sid_lower + 1 + next.index * next.node->array_id->sid_stride; + key->offset = next.index * next.node->size; + } + else { + key->sid += (next.node->array_id->sid_stride - (key->sid_normal - (next.node->array_id->sid_lower + 1))); + key->offset += next.node->size; + } + key->sid_normal = next.node->node_id->sid_lower + 1; + } + if (key->sid != *sid_start && _configuration_append_index(next.index, key, _KEY_BUF(key), _KEY_BUF_LEN(key))) { + return NULL; + } + if (!skip) { + if (++next.index < _ARRAY_HANDLER(next.node)->array_size) { + assert(iter->sp < ARRAY_SIZE(iter->stack)); + iter->stack[iter->sp++] = next; + } + else if (next.node != iter->root && next.node->node.next) { + assert(iter->sp < ARRAY_SIZE(iter->stack)); + iter->stack[iter->sp++] = (conf_path_iterator_item_t) { _HANDLER(next.node->node.next), 0 }; + } + } + } + else { + key->sid_normal = next.node->node_id->sid_lower; + if (next.node != iter->root && next.node->node.next) { + assert(iter->sp < ARRAY_SIZE(iter->stack)); + iter->stack[iter->sp++] = (conf_path_iterator_item_t) { _HANDLER(next.node->node.next), 0 }; + } + } + if (next.node->subnodes) { + assert(iter->sp < ARRAY_SIZE(iter->stack)); + iter->stack[iter->sp++] = (conf_path_iterator_item_t) { next.node->subnodes, 0 }; + } + _debug_print(key); + return (conf_handler_t *)next.node; + } + return NULL; +} + +static void _configuration_key_restore(conf_key_buf_t *key, const conf_iterator_restore_t *restore, + char *key_buf) +{ + (void)key_buf; +#if IS_USED(MODULE_CONFIGURATION_STRINGS) + if (key_buf) { + key_buf[restore->key_len] = '\0'; + } +#endif + key->sid = restore->sid; +} + +static int _configuration_handler_set_internal(const conf_handler_t *root, + conf_key_buf_t *key, const void *value, + size_t *size) +{ + assert(root); + assert(key); + assert((value && size && *size) || (!value && !size)); + + if (_configuration_prepare_sid(&root, key, _KEY_BUF(key), _KEY_BUF_LEN(key)) < 0) { + return -ENOENT; + } + conf_handler_t *handler = (conf_handler_t *)root; + if (!handler->size || !handler->data) { + return -ENOTSUP; + } + return configuration_set_handler_default(handler, key, value, size); +} + +static int _configuration_handler_get_internal(const conf_handler_t *root, + conf_key_buf_t *key, void *value, size_t *size) +{ + assert(root); + assert(key); + assert(value); + assert(size); + + if (_configuration_prepare_sid(&root, key, _KEY_BUF(key), _KEY_BUF_LEN(key)) < 0) { + return -ENOENT; + } + conf_handler_t *handler = (conf_handler_t *)root; + if (!handler->size || !handler->data) { + return -ENOTSUP; + } + return configuration_get_handler_default(handler, key, value, size); +} + +static int _configuration_handler_import_internal(const conf_handler_t *root, + conf_key_buf_t *key) +{ + assert(root); + assert(key); + + if (_configuration_prepare_sid(&root, key, _KEY_BUF(key), _KEY_BUF_LEN(key)) < 0) { + return -ENOENT; + } + conf_path_iterator_t iter; + conf_iterator_restore_t restore = _configuration_path_iterator_init(&iter, root, key, _KEY_BUF(key)); + conf_handler_t *handler; + while ((handler = _configuration_path_sid_iterator_next(&iter, key, &restore.sid))) { + const conf_backend_t *be = *configuration_get_src_backend(handler); + if (!be || !be->ops || !be->ops->be_load) { + continue; + } + if (handler != root && be == *configuration_get_src_backend(root)) { + continue; + } +#if IS_USED(MODULE_CONFIGURATION_CUSTOM_OPERATIONS) + if (!handler->ops_be || !handler->ops_be->import) { + continue; + } + handler->ops_be->import(handler, key); +#else + configuration_import_handler_default(handler, key); +#endif + } + _configuration_key_restore(key, &restore, _KEY_BUF(key)); + return 0; +} + +static int _configuration_handler_export_internal(const conf_handler_t *root, + conf_key_buf_t *key) +{ + assert(root); + assert(key); + + if (_configuration_prepare_sid(&root, key, _KEY_BUF(key), _KEY_BUF_LEN(key)) < 0) { + return -ENOENT; + } + int ret = 0; + conf_path_iterator_t iter; + conf_iterator_restore_t restore = _configuration_path_iterator_init(&iter, root, key, _KEY_BUF(key)); + conf_handler_t *handler; + while ((handler = _configuration_path_sid_iterator_next(&iter, key, &restore.sid))) { + const conf_backend_t *be = *configuration_get_dst_backend(handler); + if (!be || !be->ops || !be->ops->be_store) { + continue; + } + if (handler != root && be == *configuration_get_dst_backend(root)) { + continue; + } + if (handler->ops_dat && handler->ops_dat->verify) { + if (handler->ops_dat->verify(handler, key)) { + continue; + } + } +#if IS_USED(MODULE_CONFIGURATION_CUSTOM_OPERATIONS) + if (!handler->ops_be || !handler->ops_be->export) { + continue; + } + ret = handler->ops_be->export(handler, key); +#else + ret = configuration_export_handler_default(handler, key); +#endif + if (ret < 0) { + break; + } + } + _configuration_key_restore(key, &restore, _KEY_BUF(key)); + return ret; +} + +static int _configuration_handler_delete_internal(const conf_handler_t *root, + conf_key_buf_t *key) +{ + assert(root); + assert(key); + + if (_configuration_prepare_sid(&root, key, _KEY_BUF(key), _KEY_BUF_LEN(key)) < 0) { + return -ENOENT; + } + conf_path_iterator_t iter; + conf_iterator_restore_t restore = _configuration_path_iterator_init(&iter, root, key, _KEY_BUF(key)); + conf_handler_t *handler; + while ((handler = _configuration_path_sid_iterator_next(&iter, key, &restore.sid))) { + const conf_backend_t *be = *configuration_get_src_backend(handler); + if (!be || !be->ops || !be->ops->be_delete) { + continue; + } + if (handler != root && be == *configuration_get_src_backend(root)) { + continue; + } +#if IS_USED(MODULE_CONFIGURATION_CUSTOM_OPERATIONS) + if (!handler->ops_be || !handler->ops_be->delete) { + continue; + } + handler->ops_be->delete(handler, key); +#else + configuration_delete_handler_default(handler, key); +#endif + } + _configuration_key_restore(key, &restore, _KEY_BUF(key)); + return 0; +} + +static int _configuration_handler_apply_internal(const conf_handler_t *root, + conf_key_buf_t *key) +{ + assert(root); + assert(key); + + if (_configuration_prepare_sid(&root, key, _KEY_BUF(key), _KEY_BUF_LEN(key)) < 0) { + return -ENOENT; + } + conf_iterator_t iter; + conf_iterator_restore_t restore = _configuration_iterator_init(&iter, root, key, _KEY_BUF(key)); + conf_handler_t *handler; + while ((handler = _configuration_handler_sid_iterator_next(&iter, key))) { + if (!handler->ops_dat || !handler->ops_dat->apply) { + continue; + } + /* If this fails, there would be an inconsistency between verify() and applied values, or + an API misuse where verify() was not called before. */ + handler->ops_dat->apply(handler, key); + } + _configuration_key_restore(key, &restore, _KEY_BUF(key)); + return 0; +} + +static int _configuration_handler_lock(const conf_handler_t *root, conf_key_buf_t *key) +{ + assert(root); + assert(key); + + if (_configuration_prepare_sid(&root, key, _KEY_BUF(key), _KEY_BUF_LEN(key)) < 0) { + return -ENOENT; + } + conf_iterator_t iter; + conf_iterator_restore_t restore = _configuration_iterator_init(&iter, root, key, _KEY_BUF(key)); + conf_handler_t *handler; + while ((handler = _configuration_handler_sid_iterator_next(&iter, key))) { + mutex_lock(&handler->mutex); + } + _configuration_key_restore(key, &restore, _KEY_BUF(key)); + return 0; +} + +static int _configuration_handler_unlock(const conf_handler_t *root, conf_key_buf_t *key) +{ + assert(root); + assert(key); + + if (_configuration_prepare_sid(&root, key, _KEY_BUF(key), _KEY_BUF_LEN(key)) < 0) { + return -ENOENT; + } + conf_iterator_t iter; + conf_iterator_restore_t restore = _configuration_iterator_init(&iter, root, key, _KEY_BUF(key)); + conf_handler_t *handler; + while ((handler = _configuration_handler_sid_iterator_next(&iter, key))) { + mutex_unlock(&handler->mutex); + } + _configuration_key_restore(key, &restore, _KEY_BUF(key)); + return 0; +} + +static int _configuration_handler_verify_internal(const conf_handler_t *root, conf_key_buf_t *key, + bool try_reimport) +{ + assert(root); + assert(key); + + if (_configuration_prepare_sid(&root, key, _KEY_BUF(key), _KEY_BUF_LEN(key)) < 0) { + return -ENOENT; + } + int ret = 0; + conf_iterator_t iter; + conf_iterator_restore_t restore = _configuration_iterator_init(&iter, root, key, _KEY_BUF(key)); + conf_handler_t *handler; + while ((handler = _configuration_handler_sid_iterator_next(&iter, key))) { + if (!handler->ops_dat || !handler->ops_dat->verify) { + continue; + } + if (handler->ops_dat->verify(handler, key)) { + if (!try_reimport) { + ret = -ECANCELED; + break; + } + else if ((ret = configuration_import(key))) { + break; + } + if (handler->ops_dat->verify(handler, key)) { + ret = -ECANCELED; + break; + } + } + } + _configuration_key_restore(key, &restore, _KEY_BUF(key)); + return ret; +} + +conf_handler_t *configuration_get_root(void) +{ + return &_conf_root_handler; +} + +void configuration_register(conf_handler_t *parent, conf_handler_t *node) +{ + assert(parent); + assert(node); + assert(!node->node.next); /* must be registered from the root to the leafs */ + conf_handler_t **end = &parent->subnodes; + while (*end) { + end = (conf_handler_t **)&(*end)->node.next; + } + node->level = parent->level + 1; + *end = node; +} + +int configuration_lock(conf_key_t *key) +{ + return _configuration_handler_lock(configuration_get_root(), key); +} + +int configuration_unlock(conf_key_t *key) +{ + return _configuration_handler_unlock(configuration_get_root(), key); +} + +int configuration_set(conf_key_t *key, const void *value, size_t *size) +{ + return _configuration_handler_set_internal(configuration_get_root(), key, value, size); +} + +int configuration_verify(conf_key_t *key, bool try_reimport) +{ + return _configuration_handler_verify_internal(configuration_get_root(), key, try_reimport); +} + +int configuration_get(conf_key_t *key, void *value, size_t *size) +{ + return _configuration_handler_get_internal(configuration_get_root(), key, value, size); +} + +int configuration_import(conf_key_t *key) +{ + return _configuration_handler_import_internal(configuration_get_root(), key); +} + +int configuration_export(conf_key_t *key) +{ + return _configuration_handler_export_internal(configuration_get_root(), key); +} + +int configuration_delete(conf_key_t *key) +{ + return _configuration_handler_delete_internal(configuration_get_root(), key); +} + +int configuration_apply(conf_key_t *key) +{ + return _configuration_handler_apply_internal(configuration_get_root(), key); +} + +int configuration_encode_internal(conf_path_iterator_t *iter, conf_iterator_restore_t *restore, + const conf_handler_t **root, conf_key_buf_t *key, + void *buf, size_t *size) +{ + assert(iter); + assert(restore); + assert(root); + assert(key); + assert(buf); + assert(size); + + int ret = 0; + if (!iter->root) { + *restore = _configuration_path_iterator_init(iter, *root, key, NULL); + } + else { + /* continue where encoding failed previously */ + assert(*root); + const conf_backend_t *be = *configuration_get_dst_backend(iter->root); +#if IS_USED(MODULE_CONFIGURATION_CUSTOM_OPERATIONS) + assert((*root)->ops_be_dat); + assert((*root)->ops_be_dat->encode); + ret = (*root)->ops_be_dat->encode(*root, key, &restore->sid, be->fmt, &buf, size); +#else + ret = configuration_encode_handler_default(*root, key, &restore->sid, be->fmt, &buf, size); +#endif + if (ret) { + return -ECANCELED; + } + } + conf_handler_t *handler; + while ((handler = _configuration_handler_encode_iterator_next(iter, key, &restore->sid))) { + const conf_backend_t *be = *configuration_get_dst_backend(iter->root); + /* do not export a subnode which has an own backend set + because this would export the same data to two backends */ + const conf_backend_t *be_h = *configuration_get_dst_backend(handler); +#if IS_USED(MODULE_CONFIGURATION_CUSTOM_OPERATIONS) + if (!handler->ops_be_dat || !handler->ops_be_dat->encode) { + continue; + } + if (be_h && be_h != be && be_h->ops && be_h->ops->be_store) { + continue; + } + ret = handler->ops_be_dat->encode(handler, key, &restore->sid, be->fmt, &buf, size); +#else + if (be_h && be_h != be && be_h->ops && be_h->ops->be_store) { + continue; + } + ret = configuration_encode_handler_default(handler, key, &restore->sid, be->fmt, &buf, size); +#endif + if (ret) { + /* save node to continue with, after encoding failure */ + *root = handler; + break; + } + } + return ret; +} + +int configuration_decode_internal(conf_path_iterator_t *iter, conf_iterator_restore_t *restore, + const conf_handler_t **root, conf_key_buf_t *key, + const void *buf, size_t *size) +{ + assert(iter); + assert(restore); + assert(root); + assert(key); + assert(buf); + assert(size); + + int ret = 0; + const conf_handler_t *handler = NULL; + if (!iter->root) { + *restore = _configuration_path_iterator_init(iter, *root, key, NULL); + *root = NULL; + } + do { + size_t size_cpy = *size; + /* decode new SID */ + const conf_handler_t *next = configuration_get_root(); + const conf_backend_t *be = *configuration_get_src_backend(iter->root); +#if IS_USED(MODULE_CONFIGURATION_CUSTOM_OPERATIONS) + ret = iter->root->ops_be_dat->decode(iter->root, NULL, &key->sid, be->fmt, &buf, &size_cpy); +#else + ret = configuration_decode_handler_default(iter->root, NULL, &key->sid, be->fmt, &buf, &size_cpy); +#endif + bool skip = false; /* skip structure is parsed SID is not known */ + if (!ret) { + key->offset = 0; + key->sid_normal = key->sid; + /* for a new SID we must start from the root to get the full key->offset */ + if ((ret = _configuration_prepare_sid(&next, key, NULL, 0)) < 0) { + if (ret == -ENOENT) { + skip = true; + } + else { + return ret; + } + } + } + else { + if (ret == -ENOBUFS) { + break; + } + next = NULL; + } + /* get handler of SID */ + if ((skip && (handler = iter->root)) || + (handler = *root) || + (handler = *root = _configuration_handler_decode_iterator_next(iter, key, next, &restore->sid))) { +#if IS_USED(MODULE_CONFIGURATION_CUSTOM_OPERATIONS) + if (!handler->ops_be_dat || !handler->ops_be_dat->decode) { + continue; + } + ret = handler->ops_be_dat->decode(skip ? NULL : handler, key, &restore->sid, be->fmt, &buf, &size_cpy); +#else + ret = configuration_decode_handler_default(skip ? NULL : handler, key, &restore->sid, be->fmt, &buf, &size_cpy); +#endif + if (ret) { + break; + } + *root = NULL; + } + *size = size_cpy; + } while (handler && *size); + + return ret; +} diff --git a/sys/configuration/default_handlers.c b/sys/configuration/default_handlers.c new file mode 100644 index 000000000000..e44f19ca159a --- /dev/null +++ b/sys/configuration/default_handlers.c @@ -0,0 +1,876 @@ +/* + * Copyright (C) 2023 ML!PA Consulting Gmbh + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup sys_configuration + * @{ + * + * @file + * @brief Implementation of runtime configuration default handlers + * + * These handler implementations can be used if no dynamic key part is used. + * This is the case if every path segment in the key has a node in the configuration tree. + * + * @author Fabian Hüßler + * + * @} + */ + +#include +#include +#include +#include +#include + +#include "macros/utils.h" +#include "modules.h" +#include "container.h" +#include "configuration.h" +#include "configuration_iterator.h" + +#if IS_USED(MODULE_NANOCBOR) +#include +#endif + +#ifndef PRIu64 +#define PRIu64 "llu" +#endif + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#define _ARRAY_HANDLER(h) container_of((h), conf_array_handler_t, handler) + +static mutex_t _enc_mutex = MUTEX_INIT; /* encode buffer needs concurrency protection */ +static mutex_t _dec_mutex = MUTEX_INIT; /* decode buffer needs concurrency protection */ +static uint8_t _enc_buffer[CONFIGURATION_ENCODING_BUF_SIZE]; +static uint8_t _dec_buffer[CONFIGURATION_DECODING_BUF_SIZE]; + +int configuration_encode_internal(conf_path_iterator_t *iter, conf_iterator_restore_t *restore, + const conf_handler_t **root, conf_key_buf_t *key, + void *buf, size_t *size); + +int configuration_decode_internal(conf_path_iterator_t *iter, conf_iterator_restore_t *restore, + const conf_handler_t **root, conf_key_buf_t *key, + const void *buf, size_t *size); + +int configuration_set_handler_default(const conf_handler_t *handler, + conf_key_buf_t *key, const void *val, + size_t *size) +{ + assert(handler); + assert(key); + assert(handler->size); + assert(handler->data); + assert((val && size && *size) || (!val && !size)); + + size_t sz = size ? *size : 0; + if (handler->conf_flags.handles_array && handler->array_id->sid_lower == key->sid_normal) { + if (val && sz < handler->size * _ARRAY_HANDLER(handler)->array_size) { + return -ENOBUFS; + } + sz = handler->size * _ARRAY_HANDLER(handler)->array_size; + } + else { + if (val && sz < handler->size) { + return -ENOBUFS; + } + sz = handler->size; + } + uint8_t *data = (uint8_t *)handler->data + key->offset; + if (val) { + memcpy(data, val, sz); + *size -= sz; + } + else { + memset(data, 0, sz); + } + return 0; +} + +int configuration_get_handler_default(const conf_handler_t *handler, + conf_key_buf_t *key, void *val, + size_t *size) +{ + assert(handler); + assert(val); + assert(key); + assert(size); + assert(handler->size); + assert(handler->data); + + size_t sz = handler->size; + if (handler->conf_flags.handles_array && handler->array_id->sid_lower == key->sid_normal) { + if (*size < (sz = handler->size * _ARRAY_HANDLER(handler)->array_size)) { + return -ENOBUFS; + } + } + const uint8_t *data = (const uint8_t *)handler->data + key->offset; + *size -= sz; + memcpy(val, data, sz); + return 0; +} + +int configuration_import_handler_default(const conf_handler_t *handler, + conf_key_buf_t *key) +{ + assert(handler); + assert(key); + assert(*configuration_get_src_backend(handler)); + assert((*configuration_get_src_backend(handler))->ops); + assert((*configuration_get_src_backend(handler))->ops->be_load); + + conf_sid_t sid_restore = key->sid; + size_t sz = handler->size; + const conf_backend_t *be = *configuration_get_src_backend(handler); + if (handler->conf_flags.handles_array && handler->array_id->sid_lower == key->sid_normal) { + sz = handler->size * _ARRAY_HANDLER(handler)->array_size; + } + int err = 0; (void)err; + if (be->fmt != CONF_FMT_RAW) { + size_t total = 0; + conf_path_iterator_t iter = { .root = NULL }; + conf_iterator_restore_t restore; + const conf_handler_t *dec_handler = handler; + conf_key_buf_t dec_key = *key; + conf_backend_flags_t flg = 0; + size_t full_dec_size = sizeof(_dec_buffer); + mutex_lock(&_dec_mutex); + do { + /* if decoding failed with ENOBUFS before it is not allowed to fail again */ + bool abort_on_failure = (err < 0); + if (total + sizeof(_dec_buffer) >= full_dec_size) { + flg |= CONF_BACKEND_FLAG_FINISH; + } + void *dec_data = _dec_buffer; + size_t dec_size = MIN(sizeof(_dec_buffer), full_dec_size - total); + if ((err = be->ops->be_load(be, key, dec_data, &dec_size, total, &flg))) { + DEBUG("configuration: backend importing key %s failed (%d)\n", + configuration_key_buf(key), err); + goto restore_key; + } + if (!total) { + /* first read returns the full size */ + full_dec_size = dec_size; + } + if (flg & CONF_BACKEND_FLAG_MORE) { + dec_size = sizeof(_dec_buffer); + flg &= ~CONF_BACKEND_FLAG_MORE; + } + sz = dec_size; + if (!(err = configuration_decode_internal(&iter, &restore, &dec_handler, &dec_key, dec_data, &dec_size)) || + (err == -ENOBUFS && !abort_on_failure)) { + total += sz - dec_size; + } + else { + DEBUG("configuration: decoding for key %s failed (%d)\n", + configuration_key_buf(key), err); + goto restore_key; + } + } while (total < full_dec_size); + } + else if (handler->data) { + uint8_t *data = (uint8_t *)handler->data + key->offset; + conf_backend_flags_t flg = CONF_BACKEND_FLAG_FINISH; + if ((err = be->ops->be_load(be, key, data, &sz, 0, &flg))) { + DEBUG("configuration: backend importing key %s failed (%d)\n", + configuration_key_buf(key), err); + } + } + +restore_key: + mutex_unlock(&_dec_mutex); + key->sid = sid_restore; + return 0; +} + +int configuration_export_handler_default(const conf_handler_t *handler, + conf_key_buf_t *key) +{ + assert(handler); + assert(key); + assert(*configuration_get_dst_backend(handler)); + assert((*configuration_get_dst_backend(handler))->ops); + assert((*configuration_get_dst_backend(handler))->ops->be_store); + + conf_sid_t sid_restore = key->sid; + size_t sz = handler->size; + const conf_backend_t *be = *configuration_get_dst_backend(handler); + const uint8_t *data = handler->data ? (const uint8_t *)handler->data + key->offset : NULL; + if (handler->conf_flags.handles_array && handler->array_id->sid_lower == key->sid_normal) { + sz = handler->size * _ARRAY_HANDLER(handler)->array_size; + } + int err; (void)err; + size_t total = 0; + conf_key_buf_t enc_key = *key; + conf_backend_flags_t flg = 0; + if (be->fmt != CONF_FMT_RAW) { + conf_path_iterator_t iter = { .root = NULL }; + conf_iterator_restore_t restore; + const conf_handler_t *enc_handler = handler; + void *enc_data = _enc_buffer; + size_t enc_size = sizeof(_enc_buffer); + mutex_lock(&_enc_mutex); + while ((err = configuration_encode_internal(&iter, &restore, &enc_handler, &enc_key, enc_data, &enc_size)) != 0) { + if (err == -ENOBUFS) { + /* need to flush */ + sz = sizeof(_enc_buffer) - enc_size; + if ((err = be->ops->be_store(be, key, enc_data, &sz, total, &flg))) { + DEBUG("configuration: backend exporting key %s failed (%d)\n", + configuration_key_buf(key), err); + } + total += sz; + enc_data = _enc_buffer; + enc_size = sizeof(_enc_buffer); + } + else { + DEBUG("configuration: encoding for key %s failed (%d)\n", + configuration_key_buf(key), err); + goto restore_key; + } + } + data = enc_data; + sz = sizeof(_enc_buffer) - enc_size; + } + if (data) { + flg |= CONF_BACKEND_FLAG_FINISH; + if ((err = be->ops->be_store(be, key, data, &sz, total, &flg))) { + DEBUG("configuration: backend exporting key %s failed (%d)\n", + configuration_key_buf(key), err); + } + } + +restore_key: + mutex_unlock(&_enc_mutex); + key->sid = sid_restore; + return 0; +} + +int configuration_delete_handler_default(const conf_handler_t *handler, + conf_key_buf_t *key) +{ + assert(handler); + assert(key); + assert(*configuration_get_src_backend(handler)); + assert((*configuration_get_src_backend(handler))->ops); + + const conf_backend_t *be = *configuration_get_src_backend(handler); + if (!be->ops->be_delete) { + return -ENOTSUP; + } + int err; (void)err; + if ((err = be->ops->be_delete(be, key))) { + DEBUG("configuration: backend deleting key %s failed (%d)\n", + configuration_key_buf(key), err); + } + + return 0; +} + +static int _encode_node_handler_cbor(const conf_handler_t *handler, + conf_key_buf_t *key, const conf_sid_t *sid_start, + void **enc_data, size_t *enc_size) +{ + (void)handler; (void)key; (void)sid_start; (void)enc_data; (void)enc_size; +#if IS_USED(MODULE_NANOCBOR) + nanocbor_encoder_t enc; + nanocbor_encoder_init(&enc, *enc_data, *enc_size); + if (key->sid_normal== handler->node_id->sid_lower) { + if (*sid_start != key->sid) { + if (nanocbor_fmt_uint(&enc, handler->node_id->sid_lower) < 0) { + DEBUG("configuration: failed to encode node SID %"PRIu64" handler %p\n", + key->sid, (const void *)handler); + return -ENOBUFS; + } + } + if (nanocbor_fmt_map_indefinite(&enc) < 0) { + DEBUG("configuration: failed to encode map for SID %"PRIu64" handler %p\n", + key->sid, (const void *)handler); + return -ENOBUFS; + } + } + else { + if (nanocbor_fmt_end_indefinite(&enc) < 0) { + DEBUG("configuration: failed to encode map end for SID %"PRIu64" handler %p\n", + key->sid, (const void *)handler); + return -ENOBUFS; + } + } + *enc_data = enc.cur; + *enc_size -= nanocbor_encoded_len(&enc); + return 0; +#endif + return -ENOTSUP; +} + +static int _decode_node_handler_cbor(const conf_handler_t *handler, + conf_key_buf_t *key, conf_sid_t *sid_start, + const void **dec_data, size_t *dec_size) +{ + (void)handler; (void)key; (void)sid_start; (void)dec_data; (void)dec_size; +#if IS_USED(MODULE_NANOCBOR) + nanocbor_value_t dec; + int ret; + nanocbor_decoder_init(&dec, *dec_data, *dec_size); + if (!key) { + uint64_t sid; + if ((ret = nanocbor_get_uint64(&dec, &sid)) < 0) { + DEBUG("configuration: failed to decode SID handler %p\n", + (const void *)handler); + return ret == NANOCBOR_ERR_END ? -ENOBUFS : -EINVAL; + } + *sid_start = sid; + } + else { + if (key->sid_normal == handler->node_id->sid_lower) { + nanocbor_value_t map; + if ((ret = nanocbor_enter_map(&dec, &map)) < 0) { + DEBUG("configuration: failed to decode map for SID %"PRIu64" handler %p\n", + key->sid, (const void *)handler ); + return ret == NANOCBOR_ERR_END ? -ENOBUFS : -EINVAL; + } + dec = map; + } + else { + /* disobey nanoCBOR API to use the same map object */ + nanocbor_value_t map = dec; + if (dec.end > dec.cur && *dec.cur == 0xFF) { + map.flags = NANOCBOR_DECODER_FLAG_CONTAINER | NANOCBOR_DECODER_FLAG_INDEFINITE; + nanocbor_leave_container(&dec, &map); + } + } + } + *dec_size -= dec.cur - (const uint8_t *)*dec_data; + *dec_data = dec.cur; + return 0; +#endif + return -ENOTSUP; +} + +static int _encode_array_handler_cbor(const conf_handler_t *handler, + conf_key_buf_t *key, const conf_sid_t *sid_start, + void **enc_data, size_t *enc_size) +{ + (void)handler; (void)key; (void)sid_start; (void)enc_data; (void)enc_size; +#if IS_USED(MODULE_NANOCBOR) + const conf_array_handler_t *arr_handler = _ARRAY_HANDLER(handler); + nanocbor_encoder_t enc; + nanocbor_encoder_init(&enc, *enc_data, *enc_size); + if (key->sid_normal == arr_handler->handler.array_id->sid_lower) { + if (*sid_start != key->sid) { + if (nanocbor_fmt_uint(&enc, arr_handler->handler.array_id->sid_lower) < 0) { + DEBUG("configuration: failed to encode array SID %"PRIu64" handler %p\n", + key->sid, (const void *)arr_handler); + return -ENOBUFS; + } + } + if (nanocbor_fmt_array(&enc, arr_handler->array_size) < 0) { + DEBUG("failed to encode array for SID %"PRIu64" handler %p\n", + key->sid, (const void *)arr_handler); + return -ENOBUFS; + } + if (nanocbor_fmt_map_indefinite(&enc) < 0) { + DEBUG("configuration: failed to encode array [0] for SID %"PRIu64" handler %p\n", + key->sid, (const void *)arr_handler); + return -ENOBUFS; + } + } + else if (!_sid_array_remainder(arr_handler, key->sid)) { + if (key->sid != *sid_start) { + /* finish map of previous call if the key SID is not the start SID */ + if (nanocbor_fmt_end_indefinite(&enc) < 0) { + DEBUG("configuration: failed to encode array map end for SID %"PRIu64" handler %p\n", + key->sid, (const void *)arr_handler); + return -ENOBUFS; + } + } + if (nanocbor_fmt_map_indefinite(&enc) < 0) { + DEBUG("configuration: failed to encode array [%"PRIu32"] for SID %"PRIu64" handler %p\n", + _sid_array_index(arr_handler, key->sid), key->sid, (const void *)arr_handler); + return -ENOBUFS; + } + } + else { + /* close last map */ + if (nanocbor_fmt_end_indefinite(&enc) < 0) { + DEBUG("configuration: failed to encode array map end for SID %"PRIu64" handler %p\n", + key->sid, (const void *)arr_handler); + return -ENOBUFS; + } + } + *enc_data = enc.cur; + *enc_size -= nanocbor_encoded_len(&enc); + return 0; +#endif + return -ENOTSUP; +} + +static int _decode_array_handler_cbor(const conf_handler_t *handler, + conf_key_buf_t *key, conf_sid_t *sid_start, + const void **dec_data, size_t *dec_size) +{ + (void)handler; (void)key; (void)sid_start; (void)dec_data; (void)dec_size; +#if IS_USED(MODULE_NANOCBOR) + const conf_array_handler_t *arr_handler = _ARRAY_HANDLER(handler); + nanocbor_value_t dec; + int ret; + nanocbor_decoder_init(&dec, *dec_data, *dec_size); + if (!key) { + uint64_t sid; + if ((ret = nanocbor_get_uint64(&dec, &sid)) < 0) { + DEBUG("configuration: failed to decode SID handler %p\n", + (const void *)handler); + return ret == NANOCBOR_ERR_END ? -ENOBUFS : -EINVAL; + } + *sid_start = sid; + } + else { + if (key->sid_normal == arr_handler->handler.array_id->sid_lower) { + nanocbor_value_t array; + nanocbor_value_t map; + if ((ret = nanocbor_enter_array(&dec, &array)) < 0) { + DEBUG("configuration: failed to decode array for SID %"PRIu64" handler %p\n", + key->sid, (const void *)arr_handler); + return ret == NANOCBOR_ERR_END ? -ENOBUFS : -EINVAL; + } + if ((ret = nanocbor_enter_map(&array, &map)) < 0) { + DEBUG("configuration: failed to decode array [0] for SID %"PRIu64" handler %p\n", + key->sid, (const void *)arr_handler); + return ret == NANOCBOR_ERR_END ? -ENOBUFS : -EINVAL; + } + dec = map; + } + else if (!_sid_array_remainder(arr_handler, key->sid)) { + nanocbor_value_t map = dec; + if (key->sid != *sid_start) { + /* close last opened map */ + if (key->sid != *sid_start) { + if (dec.end > dec.cur && *dec.cur == 0xFF) { + map.flags = NANOCBOR_DECODER_FLAG_CONTAINER | NANOCBOR_DECODER_FLAG_INDEFINITE; + nanocbor_leave_container(&dec, &map); + } + } + } + if ((ret = nanocbor_enter_map(&dec, &map)) < 0) { + DEBUG("configuration: failed to decode array [%"PRIu32"] for SID %"PRIu64" handler %p\n", + _sid_array_index(arr_handler, key->sid), key->sid, (const void *)arr_handler); + return ret == NANOCBOR_ERR_END ? -ENOBUFS : -EINVAL; + } + dec = map; + } + else { + /* close last opened map */ + nanocbor_value_t map = dec; + if (dec.end > dec.cur && *dec.cur == 0xFF) { + map.flags = NANOCBOR_DECODER_FLAG_CONTAINER | NANOCBOR_DECODER_FLAG_INDEFINITE; + nanocbor_leave_container(&dec, &map); + } + } + } + *dec_size -= (dec.cur - (const uint8_t *)*dec_data); + *dec_data = dec.cur; + return 0; +#endif + return -ENOTSUP; +} + +static int _encode_uint_cbor(const conf_handler_t *handler, + conf_key_buf_t *key, const conf_sid_t *sid_start, + void **enc_data, size_t *enc_size) +{ + (void)handler; (void)key; (void)sid_start; (void)enc_data; (void)enc_size; +#if IS_USED(MODULE_NANOCBOR) + const void *data = ((const uint8_t *)handler->data) + key->offset; + nanocbor_encoder_t enc; + nanocbor_encoder_init(&enc, *enc_data, *enc_size); + if (nanocbor_fmt_uint(&enc, key->sid) < 0) { + DEBUG("configuration: failed to encode SID %"PRIu64" for CBOR uint handler %p\n", + key->sid, (const void *)handler); + return -ENOBUFS; + } + uint64_t uint_val = 0; + switch (handler->size) { + case 1: + uint_val = *((const uint8_t *)data); + break; + case 2: + uint_val = *((const uint16_t *)data); + break; + case 4: + uint_val = *((const uint32_t *)data); + break; + case 8: + uint_val = *((const uint64_t *)data); + break; + default: + assert(false); + break; + } + if (nanocbor_fmt_uint(&enc, uint_val) < 0) { + DEBUG("configuration: failed to encode CBOR uint for SID %"PRIu64" handler %p\n", + key->sid, (const void *)handler); + return -ENOBUFS; + } + *enc_data = enc.cur; + *enc_size -= nanocbor_encoded_len(&enc); + return 0; +#endif + return -ENOTSUP; +} + +static int _decode_uint_cbor(const conf_handler_t *handler, + conf_key_buf_t *key, conf_sid_t *sid_start, + const void **dec_data, size_t *dec_size) +{ + (void)handler; (void)key; (void)sid_start; (void)dec_data; (void)dec_size; +#if IS_USED(MODULE_NANOCBOR) + nanocbor_value_t dec; + int ret = NANOCBOR_NOT_FOUND; + nanocbor_decoder_init(&dec, *dec_data, *dec_size); + union { + uint8_t uint8_val; uint16_t uint16_val; uint32_t uint32_val; uint64_t uint64_val; + } uint_val = { 0 }; + unsigned uint_size; + switch ((uint_size = handler->size)) { + case 1: + ret = nanocbor_get_uint8(&dec, &uint_val.uint8_val); + break; + case 2: + ret = nanocbor_get_uint16(&dec, &uint_val.uint16_val); + break; + case 4: + ret = nanocbor_get_uint32(&dec, &uint_val.uint32_val); + break; + case 8: + ret = nanocbor_get_uint64(&dec, &uint_val.uint64_val); + break; + default: + assert(false); + uint_size = 0; + break; + } + if (ret < 0) { + DEBUG("configuration: failed to decode uint for SID %"PRIu64" handler %p\n", + key->sid, (const void *)handler); + return ret == NANOCBOR_ERR_END ? -ENOBUFS : -EINVAL; + } + else { + void *data = ((uint8_t *)handler->data) + key->offset; + memcpy(data, &uint_val, uint_size); + } + *dec_size -= dec.cur - (const uint8_t *)*dec_data; + *dec_data = dec.cur; + return 0; +#endif + return -ENOTSUP; +} + +static int _encode_int_cbor(const conf_handler_t *handler, + conf_key_buf_t *key, const conf_sid_t *sid_start, + void **enc_data, size_t *enc_size) +{ + (void)handler; (void)key; (void)sid_start; (void)enc_data; (void)enc_size; +#if IS_USED(MODULE_NANOCBOR) + const void *data = ((const uint8_t *)handler->data) + key->offset; + nanocbor_encoder_t enc; + nanocbor_encoder_init(&enc, *enc_data, *enc_size); + if (nanocbor_fmt_uint(&enc, key->sid) < 0) { + DEBUG("configuration: failed to encode SID %"PRIu64" for CBOR int handler %p\n", + key->sid, (const void *)handler); + return -ENOBUFS; + } + int64_t int_val = 0; + switch (handler->size) { + case 1: + int_val = *((const int8_t *)data); + break; + case 2: + int_val = *((const int16_t *)data); + break; + case 4: + int_val = *((const int32_t *)data); + break; + case 8: + int_val = *((const int64_t *)data); + break; + default: + assert(false); + } + if (nanocbor_fmt_int(&enc, int_val) != NANOCBOR_OK) { + DEBUG("configuration: failed to encode CBOR int for SID %"PRIu64" handler %p\n", + key->sid, (const void *)handler); + return -ENOBUFS; + } + *enc_data = enc.cur; + *enc_size -= nanocbor_encoded_len(&enc); + return 0; +#endif + return -ENOTSUP; +} + + +static int _decode_int_cbor(const conf_handler_t *handler, + conf_key_buf_t *key, conf_sid_t *sid_start, + const void **dec_data, size_t *dec_size) +{ + (void)handler; (void)key; (void)sid_start; (void)dec_data; (void)dec_size; +#if IS_USED(MODULE_NANOCBOR) + nanocbor_value_t dec; + int ret = NANOCBOR_NOT_FOUND; + nanocbor_decoder_init(&dec, *dec_data, *dec_size); + union { + int8_t int8_val; int16_t int16_val; int32_t int32_val; int64_t int64_val; + } int_val = { 0 }; + unsigned int_size; + switch ((int_size = handler->size)) { + case 1: + ret = nanocbor_get_int8(&dec, &int_val.int8_val); + break; + case 2: + ret = nanocbor_get_int16(&dec, &int_val.int16_val); + break; + case 4: + ret = nanocbor_get_int32(&dec, &int_val.int32_val); + break; + case 8: + ret = nanocbor_get_int64(&dec, &int_val.int64_val); + break; + default: + assert(false); + int_size = 0; + break; + } + if (ret < 0) { + DEBUG("configuration: failed to decode int for SID %"PRIu64" handler %p\n", + key->sid, (const void *)handler); + return ret == NANOCBOR_ERR_END ? -ENOBUFS : -EINVAL; + } + else { + void *data = ((uint8_t *)handler->data) + key->offset; + memcpy(data, &int_val, int_size); + } + *dec_size -= dec.cur - (const uint8_t *)*dec_data; + *dec_data = dec.cur; + return 0; +#endif + return -ENOTSUP; +} + +static int _encode_byte_string_cbor(const conf_handler_t *handler, + conf_key_buf_t *key, const conf_sid_t *sid_start, + void **enc_data, size_t *enc_size) +{ + (void)handler; (void)key; (void)sid_start; (void)enc_data; (void)enc_size; +#if IS_USED(MODULE_NANOCBOR) + const uint8_t *data = ((const uint8_t *)handler->data) + key->offset; + nanocbor_encoder_t enc; + nanocbor_encoder_init(&enc, *enc_data, *enc_size); + if (nanocbor_fmt_uint(&enc, key->sid) < 0) { + DEBUG("configuration: failed to encode SID %"PRIu64" for CBOR byte string handler %p\n", + key->sid, (const void *)handler); + return -ENOBUFS; + } + if (nanocbor_put_bstr(&enc, data, handler->size) != NANOCBOR_OK) { + DEBUG("configuration: failed to encode CBOR byte string for SID %"PRIu64" handler %p\n", + key->sid, (const void *)handler); + return -ENOBUFS; + } + *enc_data = enc.cur; + *enc_size -= nanocbor_encoded_len(&enc); + return 0; +#endif + return -ENOTSUP; +} + +static int _decode_byte_string_cbor(const conf_handler_t *handler, + conf_key_buf_t *key, conf_sid_t *sid_start, + const void **dec_data, size_t *dec_size) +{ + (void)handler; (void)key; (void)sid_start; (void)dec_data; (void)dec_size; +#if IS_USED(MODULE_NANOCBOR) + nanocbor_value_t dec; + int ret; + nanocbor_decoder_init(&dec, *dec_data, *dec_size); + const uint8_t *bstr; + size_t bstr_len; + if ((ret = nanocbor_get_bstr(&dec, &bstr, &bstr_len)) < 0) { + DEBUG("configuration: failed to decode byte string for SID %"PRIu64" handler %p\n", + key->sid, (const void *)handler); + return ret == NANOCBOR_ERR_END ? -ENOBUFS : -EINVAL; + } + if (handler->size < bstr_len) { + DEBUG("configuration: byte string too large for SID %"PRIu64" handler %p\n", + key->sid, (const void *)handler); + } + else { + void *data = ((uint8_t *)handler->data) + key->offset; + memcpy(data, bstr, bstr_len); + } + *dec_size -= dec.cur - (const uint8_t *)*dec_data; + *dec_data = dec.cur; + return 0; +#endif + return -ENOTSUP; +} + +static int _encode_text_string_cbor(const conf_handler_t *handler, + conf_key_buf_t *key, const conf_sid_t *sid_start, + void **enc_data, size_t *enc_size) +{ + (void)handler; (void)key; (void)sid_start; (void)enc_data; (void)enc_size; +#if IS_USED(MODULE_NANOCBOR) + const char *data = ((const char *)handler->data) + key->offset; + nanocbor_encoder_t enc; + nanocbor_encoder_init(&enc, *enc_data, *enc_size); + if (nanocbor_fmt_uint(&enc, key->sid) < 0) { + DEBUG("configuration: failed to encode SID %"PRIu64" for CBOR test string handler %p\n", + key->sid, (const void *)handler); + return -ENOBUFS; + } + if (nanocbor_put_tstr(&enc, data) != NANOCBOR_OK) { + DEBUG("configuration: failed to encode CBOR text string for SID %"PRIu64" handler %p\n", + key->sid, (const void *)handler); + return -ENOBUFS; + } + *enc_data = enc.cur; + *enc_size -= nanocbor_encoded_len(&enc); + return 0; +#endif + return -ENOTSUP; +} + +static int _decode_text_string_cbor(const conf_handler_t *handler, + conf_key_buf_t *key, conf_sid_t *sid_start, + const void **dec_data, size_t *dec_size) +{ + (void)handler; (void)key; (void)sid_start; (void)dec_data; (void)dec_size; +#if IS_USED(MODULE_NANOCBOR) + nanocbor_value_t dec; + int ret; + nanocbor_decoder_init(&dec, *dec_data, *dec_size); + const uint8_t *tstr; + size_t tstr_len; + if ((ret = nanocbor_get_tstr(&dec, &tstr, &tstr_len)) < 0) { + DEBUG("configuration: failed to decode text string for SID %"PRIu64" handler %p\n", + key->sid, (const void *)handler); + return ret == NANOCBOR_ERR_END ? -ENOBUFS : -EINVAL; + } + if (handler->size < tstr_len + 1) { + DEBUG("configuration: text string too large for SID %"PRIu64" handler %p\n", + key->sid, (const void *)handler); + } + else { + void *data = ((uint8_t *)handler->data) + key->offset; + strncpy(data, (const char *)tstr, tstr_len); + } + *dec_size -= dec.cur - (const uint8_t *)*dec_data; + *dec_data = dec.cur; + return 0; +#endif + return -ENOTSUP; +} + +static int _decode_skip_cbor(const conf_handler_t *handler, + conf_key_buf_t *key, conf_sid_t *sid_start, + const void **dec_data, size_t *dec_size) +{ + (void)handler; (void)key; (void)sid_start; (void)dec_data; (void)dec_size; +#if IS_USED(MODULE_NANOCBOR) + nanocbor_value_t dec; + int ret; + nanocbor_decoder_init(&dec, *dec_data, *dec_size); + if ((ret = nanocbor_skip(&dec)) < 0) { + DEBUG("configuration: failed to skip CBOR data for SID %"PRIu64" handler %p\n", + key->sid, (const void *)handler); + return ret == NANOCBOR_ERR_END ? -ENOBUFS : -EINVAL; + } + *dec_size -= dec.cur - (const uint8_t *)*dec_data; + *dec_data = dec.cur; + return 0; +#endif + return -ENOTSUP; +} + +int configuration_encode_handler_default(const conf_handler_t *handler, + conf_key_buf_t *key, const conf_sid_t *sid_start, + conf_fmt_t fmt, void **enc_data, size_t *enc_size) +{ + assert(handler); + assert(key); + assert(sid_start); + assert(enc_data); + assert(enc_size); + (void)handler; (void)key; (void)sid_start; (void)enc_data; (void)enc_size; + + if (fmt == CONF_FMT_CBOR) { + if (IS_USED(MODULE_NANOCBOR)) { + if (handler->conf_flags.handles_array) { + return _encode_array_handler_cbor(handler, key, sid_start, enc_data, enc_size); + } + else if (handler->conf_flags.handles_primitive) { + if (handler->conf_flags.primitive_type == CONF_PRIM_TYPE_UINT) { + return _encode_uint_cbor(handler, key, sid_start, enc_data, enc_size); + } + else if (handler->conf_flags.primitive_type == CONF_PRIM_TYPE_INT) { + return _encode_int_cbor(handler, key, sid_start, enc_data, enc_size); + } + else if (handler->conf_flags.primitive_type == CONF_PRIM_TYPE_BSTR) { + return _encode_byte_string_cbor(handler, key, sid_start, enc_data, enc_size); + } + else if (handler->conf_flags.primitive_type == CONF_PRIM_TYPE_TSTR) { + return _encode_text_string_cbor(handler, key, sid_start, enc_data, enc_size); + } + else { + assert(false); + } + } + return _encode_node_handler_cbor(handler, key, sid_start, enc_data, enc_size); + } + } + return -ENOTSUP; +} + +int configuration_decode_handler_default(const conf_handler_t *handler, + conf_key_buf_t *key, conf_sid_t *sid_start, + conf_fmt_t fmt, const void **dec_data, size_t *dec_size) +{ + assert(sid_start); + assert(dec_data); + assert(dec_size); + (void)key; (void)sid_start; (void)dec_data; (void)dec_size; + + if (fmt == CONF_FMT_CBOR) { + if (IS_USED(MODULE_NANOCBOR)) { + if (!handler) { + return _decode_skip_cbor(handler, key, sid_start, dec_data, dec_size); + } + else if (handler->conf_flags.handles_array) { + return _decode_array_handler_cbor(handler, key, sid_start, dec_data, dec_size); + } + else if (handler->conf_flags.handles_primitive) { + if (handler->conf_flags.primitive_type == CONF_PRIM_TYPE_UINT) { + return _decode_uint_cbor(handler, key, sid_start, dec_data, dec_size); + } + else if (handler->conf_flags.primitive_type == CONF_PRIM_TYPE_INT) { + return _decode_int_cbor(handler, key, sid_start, dec_data, dec_size); + } + else if (handler->conf_flags.primitive_type == CONF_PRIM_TYPE_BSTR) { + return _decode_byte_string_cbor(handler, key, sid_start, dec_data, dec_size); + } + else if (handler->conf_flags.primitive_type == CONF_PRIM_TYPE_TSTR) { + return _decode_text_string_cbor(handler, key, sid_start, dec_data, dec_size); + } + else { + assert(false); + } + } + return _decode_node_handler_cbor(handler, key, sid_start, dec_data, dec_size); + } + } + return -ENOTSUP; +} diff --git a/sys/configuration/include/configuration_iterator.h b/sys/configuration/include/configuration_iterator.h new file mode 100644 index 000000000000..bf687fbcea3e --- /dev/null +++ b/sys/configuration/include/configuration_iterator.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2023 ML!PA Consulting Gmbh + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup sys_configuration + * @{ + * + * @file + * @brief Configuration iterator types + * + * @author Fabian Hüßler + */ + +#ifndef CONFIGURATION_ITERATOR_H +#define CONFIGURATION_ITERATOR_H + +#include +#include + +#include "configuration.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Iterator item type to retrieve when iterating over all nodes in the configuration tree + */ +typedef struct conf_iterator_item { + const conf_handler_t *node; /**< Configuration node to retrieve */ +} conf_iterator_item_t; + +/** + * @brief Iterator type to iterate over every node in the configuration tree non-recursively + */ +typedef struct configuration_iterator { + const conf_handler_t *root; /**< Root node to start from */ + unsigned short sp; /**< Stack pointer */ + conf_iterator_item_t stack[CONFIGURATION_DEPTH_MAX + 1]; /**< Stack of handlers */ +} conf_iterator_t; + +/** + * @brief Iterator item type to retrieve when iterating over all configuration path items + * in the configuration tree, including arrays + */ +typedef struct conf_path_iterator_item { + const conf_handler_t *node; /**< Configuration node to retrieve */ + uint32_t index; /**< Index when the node is an array */ +} conf_path_iterator_item_t; + +/** + * @brief Iterator type to iterate over all configuration SIDs non-recursively + */ +typedef struct configuration_path_iterator { + const conf_handler_t *root; /**< Root node to start from */ + unsigned short sp; /**< Stack pointer */ + conf_path_iterator_item_t stack[CONFIGURATION_DEPTH_MAX + 1]; /**< Stack of handlers */ +} conf_path_iterator_t; + +/** + * @brief Type to restore a key after an iterator has modified it + */ +typedef struct conf_iterator_restore { + conf_sid_t sid; /**< Initial SID */ +#if IS_USED(MODULE_CONFIGURATION_STRINGS) + int key_len; /**< Initial key length */ +#endif +} conf_iterator_restore_t; + +#ifdef __cplusplus +} +#endif +#endif /* CONFIGURATION_ITERATOR_H */ +/* @} */ diff --git a/sys/include/configuration.h b/sys/include/configuration.h new file mode 100644 index 000000000000..11447e9d5f82 --- /dev/null +++ b/sys/include/configuration.h @@ -0,0 +1,956 @@ +/* + * Copyright (C) 2023 ML!PA Consulting Gmbh + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup sys_configuration Runtime configuration module + * @ingroup sys + * @brief Provides an interface for runtime configuration of modules + * which must keep persistent configuration parameters + * + * TODO: Write some documentation + * @{ + * + * @file + * @brief Runtime configuration API + * + * @author Fabian Hüßler + */ + +#ifndef CONFIGURATION_H +#define CONFIGURATION_H + +#include +#include +#include + +#include "list.h" +#include "mutex.h" +#include "auto_init_utils.h" +#include "modules.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if IS_USED(MODULE_AUTO_INIT_CONFIGURATION) || defined(DOXYGEN) +/** + * @brief __attribute__((weak)) function to be overwritten by the application + * to set the backend of the configuration root. + * + * This function sets the riotconf backend by default + */ +void auto_init_configuration_root_backend_init(void); +/** + * @brief Add a configuration module to the auto-initialization array + * + * @param function Function to be called on initialization @ref auto_init_fn_t + * @param priority Priority level @ref auto_init_prio_t + */ +#define AUTO_INIT_CONFIGURATION(function, priority) \ + _AUTO_INIT(auto_init_configuration_xfa, function, priority) +#else +#define AUTO_INIT_CONFIGURATION(...) +#endif + +#if !defined(CONFIG_CONFIGURATION_DEPTH_MAX) || defined(DOXYGEN) +/** + * @brief A path to a configuration item must not have more than this number of segments + */ +#define CONFIG_CONFIGURATION_DEPTH_MAX 8 +#endif +#if !defined(CONFIG_CONFIGURATION_ENCODING_BUF_SIZE) || defined(DOXYGEN) +/** + * @brief The size of the encoding buffer for the default export handler + */ +#define CONFIG_CONFIGURATION_ENCODING_BUF_SIZE 512 +#endif +#if !defined(CONFIG_CONFIGURATION_DECODING_BUF_SIZE) || defined(DOXYGEN) +/** + * @brief The size of the decoding buffer for the default import handler + */ +#define CONFIG_CONFIGURATION_DECODING_BUF_SIZE 512 +#endif + +/** + * @brief @ref CONFIG_CONFIGURATION_DEPTH_MAX + */ +#define CONFIGURATION_DEPTH_MAX CONFIG_CONFIGURATION_DEPTH_MAX +/** + * @brief @ref CONFIG_CONFIGURATION_ENCODING_BUF_SIZE + */ +#define CONFIGURATION_ENCODING_BUF_SIZE CONFIG_CONFIGURATION_ENCODING_BUF_SIZE +/** + * @brief @ref CONFIG_CONFIGURATION_DECODING_BUF_SIZE + */ +#define CONFIGURATION_DECODING_BUF_SIZE CONFIG_CONFIGURATION_DECODING_BUF_SIZE + +#if IS_USED(MODULE_CONFIGURATION_STRINGS) || defined(DOXYGEN) +/** + * @brief When configuration_strings is used as a module, + * a member to store a configuration path segment is added to a node + * @internal + */ +#define _CONF_SUBTREE const char *subtree; +/** + * @brief When module configuration_strings is used as a module, + * a member to store the configuration path is added to a key buffer + * @internal + */ +#define _CONF_KEY_BUF(len) char buf[len]; +/** + * @brief When configuration_strings is used as a module, + * a member to store the length of the configuration key buffer is added + * @internal + */ +#define _CONF_KEY_BUF_LEN unsigned char buf_len; +/** + * @brief Key buffer initializer + * @internal + */ +#define _CONF_KEY_INITIALIZER(_sid, _buf_len) \ +{ \ + .sid = _sid, \ + .buf_len = _buf_len, \ + .buf = "", \ +} +/** + * @brief Internal initialization macro for a configuration handler node + * @internal + */ +#define _CONF_HANDLER_NODE_ID_INITIALIZER(_sid_lower, _sid_upper, _subtree) \ +{ \ + .subtree = _subtree, \ + .sid_lower = _sid_lower, \ + .sid_upper = _sid_upper, \ +} +/** + * @brief Internal initialization macro for a configuration handler + * @internal + */ +#define _CONF_HANDLER_ID_INITIALIZER(_sid, _subtree) \ +{ \ + .subtree = _subtree, \ + .sid_lower = _sid, \ +} +/** + * @brief Internal initialization macro for a configuration handler array + * @internal + */ +#define _CONF_HANDLER_ARRAY_ID_INITIALIZER(_sid_lower, _sid_upper, _sid_stride, _subtree) \ +{ \ + .subtree = _subtree, \ + .sid_lower = _sid_lower, \ + .sid_upper = _sid_upper, \ + .sid_stride = _sid_stride, \ +} +/** + * @brief Key buffer initializer with a static maximum key length + * + * @param name Name of the key buffer variable + * @param sid SID of the configuration item + * @param len Buffer length to store a key + */ +#define CONF_KEY(name, sid, len) \ + CONF_KEY_T(len) name = _CONF_KEY_INITIALIZER(sid, len) +/** + * @brief Macro to instantiate a configuration handler node identification + * + * @param name Name of the configuration handler node identifier variable + * @param sid_lower Lower SID of the configuration handler node + * @param sid_upper Upper SID of the configuration handler node (inclusive) + * @param subtree Subtree string + */ +#define CONF_HANDLER_NODE_ID(name, sid_lower, sid_upper, subtree) \ + const conf_handler_node_id_t name = \ + _CONF_HANDLER_NODE_ID_INITIALIZER(sid_lower, sid_upper, subtree) +/** + * @brief Macro to instantiate a configuration handler identification + * + * @param name Name of the configuration handler variable + * @param sid SID of the configuration handler + * @param subtree Subtree string + */ +#define CONF_HANDLER_ID(name, sid, subtree) \ + const conf_handler_id_t name = _CONF_HANDLER_ID_INITIALIZER(sid, subtree) +/** + * @brief Macro to instantiate a configuration handler array identification + * + * @param name Name of the configuration array handler identifier variable + * @param sid_lower Lower SID of the configuration handler array + * @param sid_upper Upper SID of the configuration handler array (inclusive) + * @param sid_stride SID Stride from one to the next array entry + * @param subtree Subtree string + */ +#define CONF_HANDLER_ARRAY_ID(name, sid_lower, sid_upper, sid_stride, subtree) \ + const conf_handler_array_id_t name = \ + _CONF_HANDLER_ARRAY_ID_INITIALIZER(sid_lower, sid_upper, sid_stride, subtree) +#else +#define _CONF_SUBTREE +#define _CONF_KEY_BUF(len) +#define _CONF_KEY_BUF_LEN +#define _CONF_KEY_INITIALIZER(_sid, ...) \ +{ \ + .sid = _sid, \ +} +#define _CONF_HANDLER_NODE_ID_INITIALIZER(_sid_lower, _sid_upper) \ +{ \ + .sid_lower = _sid_lower, \ + .sid_upper = _sid_upper, \ +} +#define _CONF_HANDLER_ID_INITIALIZER(_sid) \ +{ \ + .sid_lower = _sid, \ +} +#define _CONF_HANDLER_ARRAY_ID_INITIALIZER(_sid_lower, _sid_upper, _sid_stride) \ +{ \ + .sid_lower = _sid_lower, \ + .sid_upper = _sid_upper, \ + .sid_stride = _sid_stride, \ +} +#define CONF_KEY(name ,sid, ...) \ + CONF_KEY_T(0) name = _CONF_KEY_INITIALIZER(sid) +#define CONF_HANDLER_NODE_ID(name, sid_lower, sid_upper, ...) \ + const conf_handler_node_id_t name = \ + _CONF_HANDLER_NODE_ID_INITIALIZER(sid_lower, sid_upper) +#define CONF_HANDLER_ID(name, sid, ...) \ + const conf_handler_id_t name = _CONF_HANDLER_ID_INITIALIZER(sid) +#define CONF_HANDLER_ARRAY_ID(name, sid_lower, sid_upper, sid_stride, ...) \ + const conf_handler_array_id_t name = \ + _CONF_HANDLER_ARRAY_ID_INITIALIZER(sid_lower, sid_upper, sid_stride) +#endif + +#if IS_USED(MODULE_CONFIGURATION_CUSTOM_OPERATIONS) || defined(DOXYGEN) +/** + * @brief Static initializer for an intermediate handler node + * @internal + */ +#define _CONF_HANDLER_INITIALIZER(_id, _data_ops, _be_ops, _be_data_ops, _flags, _item_size, _loc) \ +{ \ + .node_id = _id, \ + .ops_dat = _data_ops, \ + .ops_be = _be_ops, \ + .ops_be_dat = _be_data_ops, \ + .mutex = MUTEX_INIT, \ + .conf_flags = _flags, \ + .data = _loc, \ + .size = _item_size, \ +} +#else +#define _CONF_HANDLER_INITIALIZER(_id, _data_ops, _be_ops, _be_data_ops, _flags, _item_size, _loc) \ +{ \ + .node_id = _id, \ + .ops_dat = _data_ops, \ + .mutex = MUTEX_INIT, \ + .conf_flags = _flags, \ + .data = _loc, \ + .size = _item_size, \ +} +#endif +/** + * @brief Macro to instantiate a configuration handler node for a compound configuration item + * + * @param name Name of the configuration handler variable + * @param id Pointer to configuration handler ID @ref CONF_HANDLER_NODE_ID + * @param data_ops Pointer to handler data operations (verify, apply) + * @param be_ops Pointer to handler backend operations (import, export, delete) + * @param be_data_ops Pointer to handler backend data operations (encode, decode) + * @param item_size Size of the configuration item + * @param loc Location of the configuration data + */ +#define CONF_HANDLER(name, id, data_ops, be_ops, be_data_ops, item_size, loc) \ + conf_handler_t name = \ + _CONF_HANDLER_INITIALIZER((const conf_handler_node_id_t *)id, data_ops, be_ops, be_data_ops, \ + { 0 }, item_size, loc) + +/** + * @brief Macro to instantiate a configuration handler for a primitive type + * + * @param name Name of the configuration handler variable + * @param id Pointer to configuration handler ID @ref CONF_HANDLER_ID + * @param data_ops Pointer to handler data operations (verify, apply) + * @param be_ops Pointer to handler backend operations (import, export, delete) + * @param be_data_ops Pointer to handler backend data operations (encode, decode) + * @param flags Configuration flags @ref conf_handler_flags_t + * @param item_size Size of the configuration item + * @param loc Location of the configuration data + */ +#define CONF_PRIMITIVE_HANDLER(name, id, data_ops, be_ops, be_data_ops, flags, item_size, loc) \ + conf_handler_t name = \ + _CONF_HANDLER_INITIALIZER((const conf_handler_node_id_t *)id, data_ops, be_ops, be_data_ops, \ + _CONF_FLAGS(flags), item_size, loc) +/** + * @brief Static initializer for a configuration handler node + * @internal + */ +#define _CONF_ARRAY_HANDLER_INITIALIZER(_id, _data_ops, _be_ops, _be_data_ops, _item_size, _loc, _numof) \ +{ \ + .handler = _CONF_HANDLER_INITIALIZER((const conf_handler_node_id_t *)_id, _data_ops, _be_ops, \ + _be_data_ops, CONF_FLAGS_ARRAY(), _item_size, _loc), \ + .array_size = _numof, \ +} +/** + * @brief Macro to instantiate a configuration array handler + * + * @param name Name of the configuration array handler variable + * @param id Pointer to configuration array handler ID @ref CONF_HANDLER_ARRAY_ID + * @param data_ops Pointer to handler data operations + * @param be_ops Pointer to handler backend operations + * @param be_data_ops Pointer to handler backend data operations + * @param item_size Size of a single item in the array + * @param loc Location of the configuration data + * @param numof Number of items in the array + */ +#define CONF_ARRAY_HANDLER(name, id, data_ops, be_ops, be_data_ops, item_size, loc, numof) \ + conf_array_handler_t name = \ + _CONF_ARRAY_HANDLER_INITIALIZER(id, data_ops, be_ops, be_data_ops, item_size, loc, numof) + +/** + * @brief Macro to instantiate a configuration backend for a handler + * + * @param name Name of the configuration backend variable + * @param ops Pointer to backend operations + * @param fmt Encoding format + */ +#define CONF_BACKEND(name, ops, fmt) \ + conf_backend_t name = _CONF_BACKEND_INITIALIZER(ops, fmt) + +/** + * @brief Configuration backend initializer + * @internal + */ +#define _CONF_BACKEND_INITIALIZER(_ops, _fmt) \ +{ \ + .ops = _ops, \ + .fmt = _fmt, \ +} + +/** + * @brief Unique identifier for a configuration item + */ +typedef uint64_t conf_sid_t; + +/** + * @brief Configuration key base type + */ +#define _CONF_KEY_BASE_T \ +struct { \ + conf_sid_t sid; \ + conf_sid_t sid_normal; \ + unsigned offset; \ +} + +/** + * @brief Key buffer type with a static maximum key length + * + * @param len Buffer length to store a key + */ +#define CONF_KEY_T(len) \ +struct { \ + _CONF_KEY_BASE_T; \ + _CONF_KEY_BUF_LEN \ + _CONF_KEY_BUF(len) \ +} + +/** + * @brief Abstraction type of a configuration key buffer + */ +typedef CONF_KEY_T() conf_key_buf_t; + +/** + * @brief Configuration key type + */ +typedef void conf_key_t; + +/** + * @brief Configuration encoding format + */ +typedef enum { + CONF_FMT_RAW, /**< Unformatted */ + CONF_FMT_CBOR, /**< CBOR */ +} conf_fmt_t; + +/** + * @brief Forward declaration of a configuration handler + */ +struct conf_handler; + +/** + * @brief Forward declaration for a persistent configuration storage backend + */ +struct conf_backend; + +/** + * @brief Handler prototype to import a configuration value from persistent storage to + * the internal representation location of the configuration item + * + * This is called by @ref configuration_import() for the handler. + * + * @param[in] handler Reference to the handler + * @param[in] key Configuration key which belongs to the configuration handler + * + * @return 0 on success + */ +typedef int (*conf_data_import_handler) (const struct conf_handler *handler, + conf_key_buf_t *key); + +/** + * @brief Handler prototype to export a configuration value from the internal + * representation location of the configuration item to persistent storage + * + * This is called by @ref configuration_export() for the handler. + * + * @param[in] handler Reference to the handler + * @param[in] key Configuration key which belongs to the configuration handler + * + * @return 0 on success + */ +typedef int (*conf_data_export_handler) (const struct conf_handler *handler, + conf_key_buf_t *key); + +/** + * @brief Handler prototype to delete a configuration value from persistent storage + * + * This is called by @ref configuration_delete() for the handler. + * + * @param[in] handler Reference to the handler + * @param[in] key Configuration key which belongs to the configuration handler + * + * @return 0 on success + */ +typedef int (*conf_data_delete_handler) (const struct conf_handler *handler, + conf_key_buf_t *key); + +/** + * @brief Handler prototype to verify the internal representation of a configuration item + * + * This is called by @ref configuration_verify() for the handler. + * + * @param[in] handler Reference to the handler + * @param[in] key Configuration key which belongs to the configuration handler + * + * @return 0 on success + */ +typedef int (*conf_data_verify_handler) (const struct conf_handler *handler, + conf_key_buf_t *key); + +/** + * @brief Handler prototype to apply the internal representation of a configuration item to + * the configuration subject, e.g. a sensor + * + * This is called by @ref configuration_apply() for the handler. + * @ref configuration_set() only stores the configuration value to the internal location, and + * @ref configuration_apply() applies the value to the subject. + * + * @param[in] handler Reference to the handler + * @param[in] key Configuration key which belongs to the configuration handler + * + * @return 0 on success + */ +typedef int (*conf_data_apply_handler) (const struct conf_handler *handler, + conf_key_buf_t *key); + + +/** + * @brief Handler prototype to encode the internal representation of a configuration item + * from a struct representation to for example CBOR. + * + * This is called by the default export handler before exporting. + * Another use case of this function could be to disguise confidential data which are not + * supposed to be exported to a certain backend. + * + * @param[in] handler Reference to the handler + * @param[in] key Configuration key which belongs to the configuration handler + * @param[in] sid_start Start SID of the handler which is to be exported + * @param[in] fmt Encoding format + * @param[out] enc_data pointer to encoded data to be exported + * @param[out] enc_size in: size of data to be encoded; out: size of encoded data + * + * @return 0 on success + * @return -ENOBUFS if the encoding failed due to a too small buffer and the buffer must be flushed + * @return Any other negative value is an unexpected error and aborts the encoding + */ +typedef int (*conf_data_encode_handler) (const struct conf_handler *handler, + conf_key_buf_t *key, const conf_sid_t *sid_start, + conf_fmt_t fmt, void **enc_data, size_t *enc_size); +/** + * @brief Handler prototype to decode the internal representation of a configuration item + * for example from CBOR to a struct representation. + * + * This is called by the default import handler after importing. + * + * @param[in] handler Reference to the handler, NULL if next value must be skipped + * @param[in] key Configuration key which belongs to the configuration handler, + NULL if the next SID should be decoded + * @param[in] sid_start Start SID of the handler which is to be imported + * @param[in] fmt Encoding format + * @param[in, out] dec_data Decoding buffer to read from + * @param[in, out] dec_size Size left in buffer + * + * @return 0 on success + * @return -ENOBUFS if the decoding failed due to a too small buffer and the buffer must be reset + * @return Any other negative value is an unexpected error and aborts the decoding + */ +typedef int (*conf_data_decode_handler) (const struct conf_handler *handler, + conf_key_buf_t *key, conf_sid_t *sid_start, + conf_fmt_t fmt, const void **dec_data, size_t *dec_size); + +/** + * @brief Configuration handler data operations + */ +typedef struct conf_handler_data_ops { + conf_data_verify_handler verify; /**< Verify the currently set configuration */ + conf_data_apply_handler apply; /**< Apply the currently set configuration */ +} conf_handler_data_ops_t; + +/** + * @brief Configuration handler backend data operations + */ +typedef struct conf_handler_backend_data_ops { + conf_data_encode_handler encode; /**< Encode the currently set configuration */ + conf_data_decode_handler decode; /**< Decode a given configuration */ +} conf_handler_backend_data_ops_t; + +/** + * @brief Configuration handler backend operations + */ +typedef struct conf_handler_backend_ops { + conf_data_import_handler import; /**< Import a configuration value + from persistent storage */ + conf_data_export_handler export; /**< Export a configuration value + to persistent storage */ + conf_data_delete_handler delete; /**< Delete a configuration value + from persistent storage */ +} conf_handler_backend_ops_t; + +/** + * @brief Configuration handler base type + */ +#define _CONF_HANDLER_BASE_T \ +struct { \ + _CONF_SUBTREE \ + conf_sid_t sid_lower; \ +} + +/** + * @brief Configuration handler array identifier + */ +#define CONF_HANDLER_ARRAY_ID_T \ +struct { \ + _CONF_HANDLER_BASE_T; \ + conf_sid_t sid_upper; \ + uint32_t sid_stride; \ +} + +/** + * @brief A subrange of identifiers for a configuration array which handles a variable number of + * (compound) items + */ +typedef CONF_HANDLER_ARRAY_ID_T conf_handler_array_id_t; + +/** + * @brief Configuration handler node identification + */ +#define CONF_HANDLER_NODE_ID_T \ +struct { \ + _CONF_HANDLER_BASE_T; \ + conf_sid_t sid_upper; \ +} + +/** + * @brief A subrange of identifiers for a configuration node which handles a compound item + */ +typedef CONF_HANDLER_NODE_ID_T conf_handler_node_id_t; + +/** + * @brief Configuration handler identification + */ +#define CONF_HANDLER_ID_T \ +struct { \ + _CONF_HANDLER_BASE_T; \ +} + +/** + * @brief An identifier for a configuration handler which has no subitems + */ +typedef CONF_HANDLER_ID_T conf_handler_id_t; + +/** + * @brief Configuration of handler behavior + */ +typedef struct { + uint32_t handles_array :1; /**< True if the handler handles an array of items */ + uint32_t handles_primitive :1; /**< True if the handler handles a primitive item */ + uint32_t primitive_type :6; /**< Type of the primitive item */ +} conf_handler_flags_t; + +enum { + CONF_PRIM_TYPE_UINT = 0, /**< Unsigned integer */ + CONF_PRIM_TYPE_INT, /**< Signed integer */ + CONF_PRIM_TYPE_BSTR, /**< Byte string */ + CONF_PRIM_TYPE_TSTR, /**< Text string */ + /* extend on demand */ +}; + +/** + * @brief Configuration flags initializer + * @internal + */ +#define _CONF_FLAGS(_flags) _flags() +/** + * @brief Configuration array handler flags initializer + * @internal use CONF_FLAGS_ARRAY + */ +#define CONF_FLAGS_ARRAY() { .handles_array = 1 } +/** + * @brief Configuration unsigned integer handler flags initializer + * @internal use CONF_FLAGS_PRIMITIVE_UINT + */ +#define CONF_FLAGS_PRIMITIVE_UINT() { .handles_primitive = 1, .primitive_type = CONF_PRIM_TYPE_UINT } +/** + * @brief Configuration signed integer handler flags initializer + * @internal use CONF_FLAGS_PRIMITIVE_INT + */ +#define CONF_FLAGS_PRIMITIVE_INT() { .handles_primitive = 1, .primitive_type = CONF_PRIM_TYPE_INT } +/** + * @brief Configuration byte string handler flags initializer + * @internal use CONF_FLAGS_PRIMITIVE_BSTR + */ +#define CONF_FLAGS_PRIMITIVE_BSTR() { .handles_primitive = 1, .primitive_type = CONF_PRIM_TYPE_BSTR } +/** + * @brief Configuration text string handler flags initializer + * @internal use CONF_FLAGS_PRIMITIVE_TSTR + */ +#define CONF_FLAGS_PRIMITIVE_TSTR() { .handles_primitive = 1, .primitive_type = CONF_PRIM_TYPE_TSTR } + +/** + * @brief A node in the configuration tree + */ +typedef struct conf_handler { + list_node_t node; /**< Every node is in a list, managed by its parent */ + struct conf_handler *subnodes; /**< Every node has a list of subnodes */ + union { + const conf_handler_array_id_t *array_id; /**< Pointer to handler array identification */ + const conf_handler_node_id_t *node_id; /**< Pointer to handler node identification */ + const conf_handler_id_t *handler_id; /**< Pointer to handler identification */ + }; + const conf_handler_data_ops_t *ops_dat; /**< Handler data operations */ +#if IS_USED(MODULE_CONFIGURATION_CUSTOM_OPERATIONS) || defined(DOXYGEN) + const conf_handler_backend_ops_t *ops_be; /**< Handler backend operations */ + const conf_handler_backend_data_ops_t *ops_be_dat; /**< Handler backend data operations */ +#endif + const struct conf_backend *src_backend; /**< Backend to load configuration data from */ +#if IS_USED(MODULE_CONFIGURATION_DESTINATION_BACKEND) || defined(DOXYGEN) + const struct conf_backend *dst_backend; /**< Backend to store configuration data to */ +#endif + void *data; /**< Pointer to the configuration item data location (may be NULL) */ + uint32_t size; /**< Configuration item size in bytes */ + unsigned level; /**< Level in the configuration tree (root = 0) */ + conf_handler_flags_t conf_flags; /**< Configuration of handler behavior */ + mutex_t mutex; /**< Lock for unique access to the configuration item */ +} conf_handler_t; + +/** + * @brief A node with handler operations, which handle an array of configuration objects + */ +typedef struct conf_array_handler { + conf_handler_t handler; /**< Configuration handler */ + uint32_t array_size; /**< Number of items in the array */ +} conf_array_handler_t; + +/** + * @brief Get the array index of a normalized SID + * @internal + * + * @param[in] array Reference to the array handler + * @param[in] sid SID of the configuration item + * + * @return Array index + */ +static inline uint32_t _sid_array_index(const conf_array_handler_t *array, conf_sid_t sid) +{ + assert(sid > array->handler.array_id->sid_lower); + return (sid - array->handler.array_id->sid_lower - 1) / array->handler.array_id->sid_stride; +} + +/** + * @brief Get the array index remainder of a normalized SID + * @internal + * + * @param[in] array Reference to the array handler + * @param[in] sid SID of the configuration item + * + * @return Array index remainder + */ +static inline uint64_t _sid_array_remainder(const conf_array_handler_t *array, conf_sid_t sid) +{ + assert(sid > array->handler.array_id->sid_lower); + return (sid - array->handler.array_id->sid_lower - 1) % array->handler.array_id->sid_stride; +} + +/** + * @brief Flags to be interpreted by the backend handlers + */ +typedef enum { + CONF_BACKEND_FLAG_START = 1u << 0, /**< Storage backend is not opened yet */ + CONF_BACKEND_FLAG_FINISH = 1u << 1, /**< Storage backend must be closed */ + CONF_BACKEND_FLAG_MORE = 1u << 2, /**< More data is available */ +} conf_backend_flags_t; + +/** + * @brief Handler prototype to load configuration data from a persistent storage backend + * + * This is called by the configuration handler on import. + * + * @param[in] be Reference to the backend + * @param[in] key Configuration key which belongs to the configuration item + * @param[out] val Import location of the configuration value + * @param[in, out] size Maximum size of the value to be imported as input, + * and remaining size as output. + * If offset is 0 it returns the full size of the configuration value + * @param[in] offset Offset in the value to be imported + * (may not be supported by all backends) + * @param[in, out] flg Backend flags + * + * @return 0 on success + */ +typedef int (*conf_backend_load_handler) (const struct conf_backend *be, + conf_key_buf_t *key, void *val, size_t *size, + size_t offset, conf_backend_flags_t *flg); + +/** + * @brief Handler prototype to store configuration data to a persistent storage backend + * + * This is called by the configuration handler on export. + * + * @param[in] be Reference to the backend + * @param[in] key Configuration key which belongs to the configuration item + * @param[in] val Export location of the configuration value + * @param[in, out] size Size of the value to be exported as input, + * and 0 as output + * @param[in] offset Offset in the value to be exported + * (may not be supported by all backends) + * @param[in] flg Backend flags + * + * @return 0 on success + */ +typedef int (*conf_backend_store_handler) (const struct conf_backend *be, + conf_key_buf_t *key, const void *val, size_t *size, + size_t offset, conf_backend_flags_t *flg); + +/** + * @brief Handler prototype to delete configuration data from a persistent storage backend + * + * This is called by the configuration handler on delete. + * + * @param[in] be Reference to the backend + * @param[in] key Configuration key which belongs to the configuration item + * + * @return 0 on success + */ +typedef int (*conf_backend_delete_handler) (const struct conf_backend *be, + conf_key_buf_t *key); + +/** + * @brief Configuration storage backend operations + */ +typedef struct conf_backend_ops { + conf_backend_load_handler be_load; /**< Backend import function */ + conf_backend_store_handler be_store; /**< Backend export function */ + conf_backend_delete_handler be_delete; /**< Backend delete function */ +} conf_backend_ops_t; + +/** + * @brief Configuration backend + */ +typedef struct conf_backend { + const conf_backend_ops_t *ops; /**< Backend operations */ + conf_fmt_t fmt; /**< Encoding format */ +} conf_backend_t; + +/** + * @brief Get or set the source backend of a configuration handler + * + * @param[in] handler Reference to the handler + * + * @return Reference to the source backend + */ +static inline +const struct conf_backend **configuration_get_src_backend(const conf_handler_t *handler) +{ + return (const struct conf_backend **)&handler->src_backend; +} + +/** + * @brief Get or set the destination backend of a configuration handler + * + * @param[in] handler Reference to the handler + * + * @return Reference to the destination backend + */ +static inline +const struct conf_backend **configuration_get_dst_backend(const conf_handler_t *handler) +{ +#if IS_USED(MODULE_CONFIGURATION_DESTINATION_BACKEND) + return (const struct conf_backend **)&handler->dst_backend; +#endif + return (const struct conf_backend **)&handler->src_backend; +} + +/** + * @brief Get access to the key string buffer or NULL + * if the module configuration_strings is not used + * + * @param[in] key Configuration key buffer + * + * @return Pointer to the key string buffer or NULL + */ +static inline char *configuration_key_buf(conf_key_t *key) +{ +#if IS_USED(MODULE_CONFIGURATION_STRINGS) + return ((conf_key_buf_t *)key)->buf; +#else + (void)key; + return ""; +#endif +} + +/** + * @brief Get the root node of the configuration tree + * + * @return Configuration root node + */ +conf_handler_t *configuration_get_root(void); + +/** + * @brief Append a configuration node to the configuration tree + * + * @param[in] parent Parent configuration node + * @param[in] node New configuration node which is + * either an intermediate or a leaf node + */ +void configuration_register(conf_handler_t *parent, conf_handler_t *node); + +/** + * @brief Import a configuration value by its key from the persistent storage backend + * + * @param[in,out] key Identifying configuration key + * + * @return 0 on success + * @return -ENOENT no handler found by key + * @return Negative errno on handler error + */ +int configuration_import(conf_key_t *key); + +/** + * @brief Export a configuration value by its key to the persistent storage backend + * + * This calls verify() internally and skips the keys for which verification fails. + * + * @param[in,out] key Identifying configuration key + * + * @return 0 on success + * @return -ENOENT no handler found by key + * @return Negative errno on handler error + */ +int configuration_export(conf_key_t *key); + +/** + * @brief Delete a configuration value by its key from the persistent storage backend + * + * @param[in,out] key Identifying configuration key + * + * @return 0 on success + * @return -ENOENT no handler found by key + */ +int configuration_delete(conf_key_t *key); + +/** + * @brief Set the value of a configuration item identified by key + * + * @param[in,out] key Identifying configuration key + * @param[in] value New configuration value + * @param[in] size Size of the configuration value + * + * @return 0 on success + * @return -ENOENT no handler found by key + * @return Negative errno on handler error + */ +int configuration_set(conf_key_t *key, const void *value, size_t *size); + +/** + * @brief Get the value of a configuration item identified by key + * + * @param[in,out] key Identifying configuration key + * @param[out] value Configuration value + * @param[in, out] size Maximum size of the configuration value + * + * @return 0 on success + * @return -ENOENT no handler found by key + * @return Negative errno on handler error + */ +int configuration_get(conf_key_t *key, void *value, size_t *size); + +/** + * @brief Lock a subtree of the configuration tree for unique modification permission + * + * @warning Do not lock overlapping subtrees! + * + * @param[in,out] key Key to identify the subtree + * + * @return 0 on success + * @return -ENOENT no handler found by key + */ +int configuration_lock(conf_key_t *key); + +/** + * @brief Unlock a subtree of the configuration tree after modification + * + * @param[in,out] key Key to identify the subtree + * + * @return 0 on success + * @return -ENOENT no handler found by key + */ +int configuration_unlock(conf_key_t *key); + +/** + * @brief Verify the correctness of a configuration subtree + * + * @param[in,out] key Key to identify the subtree + * @param[in] try_reimport If true, try to reimport the configuration + * value if verification fails when a bad value was set + * + * @return 0 on success + * @return -ENOENT no handler found by key + * @return Negative errno on handler error + */ +int configuration_verify(conf_key_t *key, bool try_reimport); + +/** + * @brief Apply the configuration subtree + * + * @param[in,out] key Key to identify the subtree + * + * @return 0 on success + * @return -ENOENT no handler found by key + */ +int configuration_apply(conf_key_t *key); + +/** + * @brief Default handler backend operations + */ +extern const conf_handler_backend_ops_t configuration_default_backend_ops; +/** + * @brief Default handler backend data operations + */ +extern const conf_handler_backend_data_ops_t configuration_default_backend_data_ops; + +#ifdef __cplusplus +} +#endif +#endif /* CONFIGURATION_H */ +/** @} */ From 87897a36c78d635834ae1a1266ac81dd8346e85d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20H=C3=BC=C3=9Fler?= Date: Sat, 29 Apr 2023 20:20:40 +0200 Subject: [PATCH 03/12] sys/confiuration: add FlashDB backend --- sys/configuration/backend/Makefile | 5 + sys/configuration/backend/flashdb.c | 217 ++++++++++++++++++ .../include/configuration_backend_flashdb.h | 128 +++++++++++ 3 files changed, 350 insertions(+) create mode 100644 sys/configuration/backend/Makefile create mode 100644 sys/configuration/backend/flashdb.c create mode 100644 sys/configuration/include/configuration_backend_flashdb.h diff --git a/sys/configuration/backend/Makefile b/sys/configuration/backend/Makefile new file mode 100644 index 000000000000..3dde28c04393 --- /dev/null +++ b/sys/configuration/backend/Makefile @@ -0,0 +1,5 @@ +MODULE := configuration_backend +SUBMODULES := 1 +BASE_MODULE := configuration_backend + +include $(RIOTBASE)/Makefile.base diff --git a/sys/configuration/backend/flashdb.c b/sys/configuration/backend/flashdb.c new file mode 100644 index 000000000000..189ec77df178 --- /dev/null +++ b/sys/configuration/backend/flashdb.c @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2023 ML!PA Consulting Gmbh + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup sys_configuration + * @{ + * + * @file + * @brief Implementation of the FlashDB configuration backend + * + * @author Fabian Hüßler + * + * @} + */ + +#include +#include + +#include "configuration.h" +#include "configuration_backend_flashdb.h" +#include "macros/math.h" +#include "macros/units.h" +#include "mutex.h" +#include "mtd_default.h" +#include "vfs_default.h" +#include "fal_cfg.h" + +static mutex_t _kvdb_locker = MUTEX_INIT; +static struct fdb_kvdb _kvdb = { 0 }; + +__attribute__((weak)) +mtd_dev_t *configuration_backend_flashdb_mtd_choose_dev(void) +{ + return FAL_MTD; +} + +__attribute__((weak)) +const char *configuration_backend_flashdb_mtd_choose_partition(void) +{ + return FAL_PART0_LABEL; +} + +__attribute__((weak)) +mtd_dev_t *configuration_backend_flashdb_vfs_choose_dev(void) +{ + return mtd_default_get_dev(0); +} + +__attribute__((weak)) +const char *configuration_backend_flashdb_vfs_choose_path(void) +{ + return VFS_DEFAULT_DATA"/"CONFIGURATION_FLASHDB_VFS_FOLDER; +} + +static void _lock(fdb_db_t db) +{ + mutex_lock(db->user_data); +} + +static void _unlock(fdb_db_t db) +{ + mutex_unlock(db->user_data); +} + +static int _be_fdb_init(mtd_dev_t *mtd) +{ + assert(mtd); + uint32_t size; +#if IS_USED(MODULE_CONFIGURATION_BACKEND_FLASHDB_VFS) + bool file_mode = true; + fdb_kvdb_control(&_kvdb, FDB_KVDB_CTRL_SET_FILE_MODE, &file_mode); + int fail; + if ((fail = vfs_mkdir(configuration_backend_flashdb_vfs_choose_path(), 0777)) < 0) { + if (fail != -EEXIST) { + /* probably not mounted (try with vfs_auto_format) */ + return fail; + } + } +#endif + /* The MTD must be initialized! */ + size = mtd->pages_per_sector * mtd->page_size; + size = DIV_ROUND_UP((CONFIGURATION_FLASHDB_MIN_SECTOR_SIZE_DEFAULT_KiB * KiB(1)), size) * size; + /* sector size must be set before max size */ + fdb_kvdb_control(&_kvdb, FDB_KVDB_CTRL_SET_SEC_SIZE, &size); +#if IS_USED(MODULE_CONFIGURATION_BACKEND_FLASHDB_VFS) + size *= CONFIGURATION_FLASHDB_VFS_MAX_SECTORS; + fdb_kvdb_control(&_kvdb, FDB_KVDB_CTRL_SET_MAX_SIZE, &size); +#endif + fdb_kvdb_control(&_kvdb, FDB_KVDB_CTRL_SET_LOCK, (void *)(uintptr_t)_lock); + fdb_kvdb_control(&_kvdb, FDB_KVDB_CTRL_SET_UNLOCK, (void *)(uintptr_t)_unlock); + if (fdb_kvdb_init(&_kvdb, + "kvdb_configuration", +#if IS_USED(MODULE_CONFIGURATION_BACKEND_FLASHDB_VFS) + configuration_backend_flashdb_vfs_choose_path(), +#elif IS_USED(MODULE_CONFIGURATION_BACKEND_FLASHDB_MTD) + configuration_backend_flashdb_mtd_choose_partition(), +#endif + NULL, + &_kvdb_locker) != FDB_NO_ERR) { + return -EINVAL; + } + return 0; +} + +static int _be_fdb_reset(void) { + fdb_err_t result; + if ((result = fdb_kv_set_default(&_kvdb)) != FDB_NO_ERR) { + return -EIO; + } + return 0; +} + +static int _be_fdb_load(const struct conf_backend *be, + conf_key_buf_t *key, void *val, size_t *size, + size_t offset, conf_backend_flags_t *flg) +{ + (void)be; + (void)flg; + + if (offset) { + /* reading partial records is not supported */ + return -ENOTSUP; + } + + struct fdb_blob blob; + size_t sz; + if ((sz = fdb_kv_get_blob(&_kvdb, configuration_key_buf(key), + fdb_blob_make(&blob, val, *size))) <= 0) { + return -EIO; + } + if (!blob.saved.len) { + return -ENODATA; + } + assert(*size >= sz); + *size -= sz; + return 0; + +} + +static int _be_fdb_store(const struct conf_backend *be, + conf_key_buf_t *key, const void *val, size_t *size, + size_t offset, conf_backend_flags_t *flg) +{ + (void)be; + (void)flg; + + if (offset) { + /* writing partial records is not supported */ + return -ENOTSUP; + } + + struct fdb_blob blob; + fdb_err_t err; + if ((err = fdb_kv_set_blob(&_kvdb, configuration_key_buf(key), + fdb_blob_make(&blob, val, *size))) != FDB_NO_ERR) { + return -EIO; + } + if (!blob.saved.len) { + return -ENODATA; + } + return 0; +} + +static int _be_fdb_delete(const struct conf_backend *be, conf_key_buf_t *key) +{ + (void)be; + fdb_err_t err; + if ((err = fdb_kv_del(&_kvdb, configuration_key_buf(key))) != FDB_NO_ERR && + err != FDB_KV_NAME_ERR) { + return -EIO; + } + return 0; +} + +const conf_backend_ops_t conf_backend_fdb_ops = { + .be_load = _be_fdb_load, + .be_store = _be_fdb_store, + .be_delete = _be_fdb_delete, +}; + +int configuration_backend_flashdb_reset(void) +{ + return _be_fdb_reset(); +} + +int configuration_backend_flashdb_init(mtd_dev_t *mtd) +{ + return _be_fdb_init(mtd); +} + +void auto_init_configuration_backend_flashdb(void) +{ +#if IS_USED(MODULE_CONFIGURATION_BACKEND_FLASHDB_MTD) + extern void fdb_mtd_init(mtd_dev_t *mtd); + fdb_mtd_init(configuration_backend_flashdb_mtd_choose_dev()); + int fail = configuration_backend_flashdb_init(configuration_backend_flashdb_mtd_choose_dev()); +#else + int fail = configuration_backend_flashdb_init(configuration_backend_flashdb_vfs_choose_dev()); +#endif + assert(!fail); (void)fail; +#if (IS_USED(MODULE_CONFIGURATION_BACKEND_RESET_FLASHDB)) + fail = configuration_backend_flashdb_reset(); + assert(!fail); +#endif +} + +#ifndef AUTO_INIT_PRIO_MOD_CONFIGURATION_BACKEND_FLASHDB +#define AUTO_INIT_PRIO_MOD_CONFIGURATION_BACKEND_FLASHDB 1010 +#endif + +AUTO_INIT_CONFIGURATION(auto_init_configuration_backend_flashdb, + AUTO_INIT_PRIO_MOD_CONFIGURATION_BACKEND_FLASHDB); diff --git a/sys/configuration/include/configuration_backend_flashdb.h b/sys/configuration/include/configuration_backend_flashdb.h new file mode 100644 index 000000000000..cb4c55ad874b --- /dev/null +++ b/sys/configuration/include/configuration_backend_flashdb.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2023 ML!PA Consulting Gmbh + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_configuration + * + * @{ + * + * @file + * @brief Interface for FlashDB as a configuration backend + * + * @author Fabian Hüßler + */ + +#ifndef CONFIGURATION_BACKEND_FLASHDB_H +#define CONFIGURATION_BACKEND_FLASHDB_H + +#include "board.h" +#include "modules.h" +#include "mtd.h" +#include "vfs_default.h" +#include "configuration.h" +#if IS_USED(MODULE_FLASHDB_KVDB) +#include "fal_cfg.h" +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(CONFIGURATION_FLASHDB_MIN_SECTOR_SIZE_DEFAULT_KiB) || defined(DOXYGEN) +/** + * @brief Virtual sector size to use for the configuration FlashDB + */ +#define CONFIGURATION_FLASHDB_MIN_SECTOR_SIZE_DEFAULT_KiB CONFIG_FLASHDB_MIN_SECTOR_SIZE_DEFAULT_KiB +#endif + +#if !defined(CONFIGURATION_FLASHDB_VFS_MAX_SECTORS) || defined(DOXYGEN) +/** + * @brief Maximum number of sectors in the VFS to use for the configuration file + * in the FlashDB VFS mode + */ +#define CONFIGURATION_FLASHDB_VFS_MAX_SECTORS 4 +#endif + +#if !defined(CONFIGURATION_FLASHDB_VFS_FOLDER) || defined(DOXYGEN) +/** + * @brief Folder name in the VFS for configuration data in FlashDB VFS mode + */ +#define CONFIGURATION_FLASHDB_VFS_FOLDER "fdb_kvdb_configuration" +#endif + +/** + * @brief __attribute__((weak)) function to select the FAL device for FlashDB + * when the module configuration_backend_flashdb_mtd is used + * + * The default implementation is to return @ref FAL_MTD. + * + * @return MTD device to use in the FAL mode of FlashDB + */ +mtd_dev_t *configuration_backend_flashdb_mtd_choose_dev(void); + +/** + * @brief __attribute__((weak)) function to select the FAL partition for FlashDB + * when the module configuration_backend_flashdb_mtd is used + * + * The default implementation is to return @ref FAL_PART0_LABEL. + * + * @return FAL partition label to use in the FAL mode of FlashDB + */ +const char *configuration_backend_flashdb_mtd_choose_partition(void); + +/** + * @brief __attribute__((weak)) function to select the MTD for FlashDB + * when the module configuration_backend_flashdb_vfs is used + * + * The default implementation is to return @ref MTD_0. + * + * @return MTD device to use in the VFS mode of FlashDB + */ +mtd_dev_t *configuration_backend_flashdb_vfs_choose_dev(void); + +/** + * @brief __attribute__((weak)) function to select the path for FlashDB + * when the module configuration_backend_flashdb_vfs is used + * + * The default implementation is to return VFS_DEFAULT_DATA"/"CONFIGURATION_FLASHDB_VFS_FOLDER. + * @ref CONFIGURATION_FLASHDB_VFS_FOLDER + * + * @return Path to use in the VFS mode of FlashDB + */ +const char *configuration_backend_flashdb_vfs_choose_path(void); + +/** + * @brief Reset the FlashDB backend, which deletes all configuration data + * + * @return 0 on success + */ +int configuration_backend_flashdb_reset(void); + +/** + * @brief Initialize the FlashDB backend + * + * @pre MTD must have been initialized with @ref mtd_init() + * + * @param[in] mtd FAL device in FlashDB FAL mode or VFS MTD + * + * @return 0 on success + */ +int configuration_backend_flashdb_init(mtd_dev_t *mtd); + +/** + * @brief FlashDB backend operations + */ +extern const conf_backend_ops_t conf_backend_fdb_ops; + +#ifdef __cplusplus +} +#endif + +#endif /* CONFIGURATION_BACKEND_FLASHDB_H */ +/** @} */ From e132f26487a3f9077055b606445b6f99a94613ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20H=C3=BC=C3=9Fler?= Date: Sat, 29 Apr 2023 20:40:15 +0200 Subject: [PATCH 04/12] tests/sys: add test for runtime configuration module --- tests/sys/configuration/Makefile | 43 + .../configuration_backend_ram/Makefile | 3 + .../configuration_backend_ram.c | 158 ++ .../include/configuration_backend_ram.h | 42 + .../sys/configuration/include/persist_types.h | 209 +++ tests/sys/configuration/main.c | 1625 +++++++++++++++++ 6 files changed, 2080 insertions(+) create mode 100644 tests/sys/configuration/Makefile create mode 100644 tests/sys/configuration/configuration_backend_ram/Makefile create mode 100644 tests/sys/configuration/configuration_backend_ram/configuration_backend_ram.c create mode 100644 tests/sys/configuration/include/configuration_backend_ram.h create mode 100644 tests/sys/configuration/include/persist_types.h create mode 100644 tests/sys/configuration/main.c diff --git a/tests/sys/configuration/Makefile b/tests/sys/configuration/Makefile new file mode 100644 index 000000000000..f6f1a558f17c --- /dev/null +++ b/tests/sys/configuration/Makefile @@ -0,0 +1,43 @@ +include ../Makefile.sys_common + +TEST_CONFIGURATION_BACKEND ?= configuration_backend_ram + +TEST_CONFIGURATION_SUPPORTED_BACKENDS ?= \ + configuration_backend_ram \ + configuration_backend_flashdb_mtd \ + configuration_backend_flashdb_vfs + +ifeq (,$(filter $(TEST_CONFIGURATION_BACKEND),$(TEST_CONFIGURATION_SUPPORTED_BACKENDS))) + $(error "Supported backends are: $(TEST_CONFIGURATION_SUPPORTED_BACKENDS)") +endif + +# set the backend to be used for the test +CFLAGS += -DTEST_CONFIGURATION_BACKEND=$(TEST_CONFIGURATION_BACKEND) +# define a preprocessor variable to indicate the used backend for the test +BACKEND = $(subst configuration_backend_,,$(TEST_CONFIGURATION_BACKEND)) +CFLAGS += -DTEST_CONFIGURATION_BACKEND_$(call uppercase_and_underscore,$(BACKEND)) + +INCLUDES += -I$(APPDIR)/include + +ifeq ($(TEST_CONFIGURATION_BACKEND),configuration_backend_ram) + DIRS += configuration_backend_ram + NO_PSEUDOMODULES += configuration_backend_ram +endif + +# configuration_backend_flashdb_vfs results in hard faults with little stack size +CFLAGS += -DTHREAD_STACKSIZE_MAIN=2*THREAD_STACKSIZE_LARGE + +# for configuration_backend_flashdb_vfs you may need to add: +# CFLAGS+="-DDEBUG_ASSERT_VERBOSE=1 +# -DFAL_MTD=MTD_0 +# -DVFS_DEFAULT_DATA=VFS_DEFAULT_NVM\(0\)" +# TEST_CONFIGURATION_BACKEND=configuration_backend_flashdb_vfs +# USEMODULE="vfs_aut_format +# configuration_backend_reset_flashdb" +# BOARD=same54-xpro make flash term + +USEMODULE += embunit +USEMODULE += configuration +USEMODULE += $(TEST_CONFIGURATION_BACKEND) + +include $(RIOTBASE)/Makefile.include diff --git a/tests/sys/configuration/configuration_backend_ram/Makefile b/tests/sys/configuration/configuration_backend_ram/Makefile new file mode 100644 index 000000000000..74f4f248d2af --- /dev/null +++ b/tests/sys/configuration/configuration_backend_ram/Makefile @@ -0,0 +1,3 @@ +INCLUDES += -I$(APPDIR)/include + +include $(RIOTBASE)/Makefile.base diff --git a/tests/sys/configuration/configuration_backend_ram/configuration_backend_ram.c b/tests/sys/configuration/configuration_backend_ram/configuration_backend_ram.c new file mode 100644 index 000000000000..fbaf060c0c7b --- /dev/null +++ b/tests/sys/configuration/configuration_backend_ram/configuration_backend_ram.c @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2023 ML!PA Consulting Gmbh + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * + * @{ + * + * @file Implementation of test backend in RAM + * + * @author Fabian Hüßler + * + * @} + */ + +#include +#include +#include +#include + +#include "configuration.h" +#include "configuration_backend_ram.h" + +#include "container.h" +#include "persist_types.h" + +#ifndef CSTRLEN +#define CSTRLEN(str) (ARRAY_SIZE((const char[]){str}) - 1) +#endif + +struct configuration persist_conf = { + .food = { + .bread = { + .white = {"1.00"}, + .whole_grain = {"1.20"}, + }, + .cake = { + .cheesecake = {"1.99"}, + . donut = {"1.00"}, + }, + }, + .drinks = { + .coffee = {"0.50"}, + .tea = {"0.60"}, + .cocoa = {"1.00"}, + }, + .orders = { + { .items = { {"sugar"}, {"tomatoes"} } }, + { .items = { {"coffee"}, {"milk"} } }, + { .items = { {"bread"}, {"coffee"} } }, + }, +}; + +static uint8_t _enc_dec_buffer[CONFIG_CONFIGURATION_BACKEND_RAM_BUF_SIZE]; + +struct kv { + const char *key; + conf_sid_t sid; + void *value; + bool deleted; + size_t len; +}; + +/* A real backend would not have to store the keys statically */ +static struct kv _kv[] = { + {"/", 0, _enc_dec_buffer, true, 0}, + {"/food/bread/white", TEST_FOOD_BREAD_WHITE_SID, &persist_conf.food.bread.white, false, CSTRLEN("1.00")}, + {"/food/bread/whole_grain", TEST_FOOD_BREAD_WHOLE_GRAIN_SID, &persist_conf.food.bread.whole_grain, false, CSTRLEN("1.20")}, + + {"/food/cake/cheesecake", TEST_FOOD_CAKE_CHEESECAKE_SID, &persist_conf.food.cake.cheesecake, false, CSTRLEN("1.99")}, + {"/food/cake/donut", TEST_FOOD_CAKE_DONUT_SID, &persist_conf.food.cake.donut, false, CSTRLEN("1.00")}, + + {"/drinks/coffee", TEST_DRINKS_COFFEE_SID, &persist_conf.drinks.coffee, false, CSTRLEN("0.50")}, + {"/drinks/tea", TEST_DRINKS_TEA_SID, &persist_conf.drinks.tea, false, CSTRLEN("0.60")}, + {"/drinks/cocoa", TEST_DRINKS_COCOA_SID, &persist_conf.drinks.cocoa, false, CSTRLEN("1.00")}, + + {"/orders/0", TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (0 * TEST_ORDERS_INDEX_STRIDE), &persist_conf.orders[0], false, sizeof(persist_conf.orders[0]) }, + {"/orders/1", TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (1 * TEST_ORDERS_INDEX_STRIDE), &persist_conf.orders[1], false, sizeof(persist_conf.orders[1]) }, + {"/orders/2", TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (2 * TEST_ORDERS_INDEX_STRIDE), &persist_conf.orders[2], false, sizeof(persist_conf.orders[2]) }, +}; + +static int _be_ram_load(const struct conf_backend *be, + conf_key_buf_t *key, void *val, size_t *size, + size_t offset, conf_backend_flags_t *flg) +{ + (void)be; + (void)flg; + + assert(offset + *size <= CONFIG_CONFIGURATION_BACKEND_RAM_BUF_SIZE); + for (unsigned i = 0; i < ARRAY_SIZE(_kv); i++) { + if ((!strcmp(configuration_key_buf(key), _kv[i].key) || + _kv[i].sid == key->sid) && !_kv[i].deleted) { + + size_t rd = *size; + if (!offset) { + if (*size < _kv[i].len) { + *size = _kv[i].len; + *flg |= CONF_BACKEND_FLAG_MORE; + } + else { + rd = _kv[i].len; + *size = rd; + } + } + memcpy(val, (const uint8_t *)_kv[i].value + offset, rd); + return 0; + } + } + return -ENOENT; +} + +static int _be_ram_store(const struct conf_backend *be, + conf_key_buf_t *key, const void *val, size_t *size, + size_t offset, conf_backend_flags_t *flg) +{ + (void)be; + (void)flg; + + assert(offset + *size <= CONFIG_CONFIGURATION_BACKEND_RAM_BUF_SIZE); + for (unsigned i = 0; i < ARRAY_SIZE(_kv); i++) { + if (!strcmp(configuration_key_buf(key), _kv[i].key) || + _kv[i].sid == key->sid) { + memcpy((uint8_t *)_kv[i].value + offset, val, *size); + if (*flg & CONF_BACKEND_FLAG_FINISH) { + _kv[i].deleted = false; + _kv[i].len = offset + *size; + *flg &= ~CONF_BACKEND_FLAG_FINISH; + } + return 0; + } + } + return -ENOENT; +} + +static int _be_ram_delete(const struct conf_backend *be, + conf_key_buf_t *key) +{ + (void)be; + for (unsigned i = 0; i < ARRAY_SIZE(_kv); i++) { + if (!strcmp(configuration_key_buf(key), _kv[i].key) || + _kv[i].sid == key->sid) { + _kv[i].deleted = true; + return 0; + } + } + return -ENOENT; +} + +const conf_backend_ops_t conf_backend_ram_ops = { + .be_load = _be_ram_load, + .be_store = _be_ram_store, + .be_delete = _be_ram_delete, +}; diff --git a/tests/sys/configuration/include/configuration_backend_ram.h b/tests/sys/configuration/include/configuration_backend_ram.h new file mode 100644 index 000000000000..7cdb786e04b3 --- /dev/null +++ b/tests/sys/configuration/include/configuration_backend_ram.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 ML!PA Consulting Gmbh + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * + * @{ + * + * @file Test backend in RAM + * + * @author Fabian Hüßler + */ + +#ifndef CONFIGURATION_BACKEND_RAM_H +#define CONFIGURATION_BACKEND_RAM_H + +#include "configuration.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef CONFIG_CONFIGURATION_BACKEND_RAM_BUF_SIZE +/** + * @brief Static buffer size for the RAM backend to store the encoded configuration + */ +#define CONFIG_CONFIGURATION_BACKEND_RAM_BUF_SIZE (1024) +#endif + +extern const conf_backend_ops_t conf_backend_ram_ops; + +#ifdef __cplusplus +} +#endif + +#endif /* CONFIGURATION_BACKEND_RAM_H */ +/** @} */ diff --git a/tests/sys/configuration/include/persist_types.h b/tests/sys/configuration/include/persist_types.h new file mode 100644 index 000000000000..d38d08af1ce7 --- /dev/null +++ b/tests/sys/configuration/include/persist_types.h @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2023 ML!PA Consulting Gmbh + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * + * @{ + * + * @file Test configuration types + * + * @author Fabian Hüßler + */ + +#ifndef PERSIST_TYPES_H +#define PERSIST_TYPES_H + +#include + +#include "container.h" +#include "configuration.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Food configuration item + */ +typedef struct food { + char price[6]; /**< Food price as a string */ +} food_t; + +/** + * @brief Drinks configuration item + */ +typedef struct drink { + char price[6]; /**< Drinks price as a string */ +} drink_t; + +/** + * @brief Order configuration item + */ +typedef struct order_item_t { + char item[12]; /**< Ordered item name */ +} order_item_t; + +/** + * @brief Order configuration + */ +typedef struct order { + order_item_t items[2]; /**< Ordered items */ +} order_t; + + +#define TEST_CONFIGURATION_MAX_ORDERS 3 + +/* TODO: automatic generation from .sid file */ + +#ifndef CONFIG_TEST_FOOD_LOWER_SID +#define CONFIG_TEST_FOOD_LOWER_SID (0x7FFFFFFFFFFFFFDA) +#endif +# ifndef CONFIG_TEST_FOOD_BREAD_LOWER_SID +# define CONFIG_TEST_FOOD_BREAD_LOWER_SID (CONFIG_TEST_FOOD_LOWER_SID + 1) +# endif +# ifndef CONFIG_TEST_FOOD_BREAD_WHITE_SID +# define CONFIG_TEST_FOOD_BREAD_WHITE_SID (CONFIG_TEST_FOOD_BREAD_LOWER_SID + 1) +# endif +# ifndef CONFIG_TEST_FOOD_BREAD_WHOLE_GRAIN_SID +# define CONFIG_TEST_FOOD_BREAD_WHOLE_GRAIN_SID (CONFIG_TEST_FOOD_BREAD_LOWER_SID + 2) +# endif +# ifndef CONFIG_TEST_FOOD_BREAD_UPPER_SID +# define CONFIG_TEST_FOOD_BREAD_UPPER_SID (CONFIG_TEST_FOOD_BREAD_LOWER_SID + 2) +# endif +# ifndef CONFIG_TEST_FOOD_CAKE_LOWER_SID +# define CONFIG_TEST_FOOD_CAKE_LOWER_SID (CONFIG_TEST_FOOD_BREAD_UPPER_SID + 1) +# endif +# ifndef CONFIG_TEST_FOOD_CAKE_CHEESECAKE_SID +# define CONFIG_TEST_FOOD_CAKE_CHEESECAKE_SID (CONFIG_TEST_FOOD_CAKE_LOWER_SID + 1) +# endif +# ifndef CONFIG_TEST_FOOD_CAKE_DONUT_SID +# define CONFIG_TEST_FOOD_CAKE_DONUT_SID (CONFIG_TEST_FOOD_CAKE_LOWER_SID + 2) +# endif +# ifndef CONFIG_TEST_FOOD_CAKE_UPPER_SID +# define CONFIG_TEST_FOOD_CAKE_UPPER_SID (CONFIG_TEST_FOOD_CAKE_LOWER_SID + 2) +# endif +#ifndef CONFIG_TEST_FOOD_UPPER_SID +#define CONFIG_TEST_FOOD_UPPER_SID (0x7FFFFFFFFFFFFFE3) +#endif +#ifndef CONFIG_TEST_DRINKS_LOWER_SID +#define CONFIG_TEST_DRINKS_LOWER_SID (0x7FFFFFFFFFFFFFE4) +#endif +# ifndef CONFIG_TEST_DRINKS_COFFEE_SID +# define CONFIG_TEST_DRINKS_COFFEE_SID (CONFIG_TEST_DRINKS_LOWER_SID + 1) +# endif +# ifndef CONFIG_TEST_DRINKS_TEA_SID +# define CONFIG_TEST_DRINKS_TEA_SID (CONFIG_TEST_DRINKS_LOWER_SID + 2) +# endif +# ifndef CONFIG_TEST_DRINKS_COCOA_SID +# define CONFIG_TEST_DRINKS_COCOA_SID (CONFIG_TEST_DRINKS_LOWER_SID + 3) +# endif +#ifndef CONFIG_TEST_DRINKS_UPPER_SID +#define CONFIG_TEST_DRINKS_UPPER_SID (0x7FFFFFFFFFFFFFEC) +#endif +#ifndef CONFIG_TEST_ORDERS_LOWER_SID +#define CONFIG_TEST_ORDERS_LOWER_SID (0x7FFFFFFFFFFFFFED) /* /orders */ +#endif +# define CONFIG_TEST_ORDERS_INDEX_LOWER_SID (1) /* /orders[0] */ +# ifndef CONFIG_TEST_ORDERS_INDEX_STRIDE +# define CONFIG_TEST_ORDERS_INDEX_STRIDE (6) /* /orders[i] */ +# endif +# ifndef CONFIG_TEST_ORDERS_ITEMS_LOWER_SID +# define CONFIG_TEST_ORDERS_ITEMS_LOWER_SID (CONFIG_TEST_ORDERS_LOWER_SID + CONFIG_TEST_ORDERS_INDEX_LOWER_SID + 1) /* /orders[0]/items */ +# endif +# define CONFIG_TEST_ORDERS_ITEMS_INDEX_LOWER_SID (1) /* /orders[0]/items[0] */ +# ifndef CONFIG_TEST_ORDERS_ITEMS_INDEX_STRIDE +# define CONFIG_TEST_ORDERS_ITEMS_INDEX_STRIDE (2) /* /orders[0]/items[i] */ +# endif +# ifndef CONFIG_TEST_ORDERS_ITEMS_ITEM_SID +# define CONFIG_TEST_ORDERS_ITEMS_ITEM_SID (CONFIG_TEST_ORDERS_ITEMS_LOWER_SID + CONFIG_TEST_ORDERS_ITEMS_INDEX_LOWER_SID + 1) /* /orders[0]/items[0]/item */ +# endif +# ifndef CONFIG_TEST_ORDERS_ITEMS_UPPER_SID +# define CONFIG_TEST_ORDERS_ITEMS_UPPER_SID (CONFIG_TEST_ORDERS_ITEMS_LOWER_SID + (CONFIG_TEST_ORDERS_ITEMS_INDEX_STRIDE * 2)) +# endif +#ifndef CONFIG_TEST_ORDERS_UPPER_SID +#define CONFIG_TEST_ORDERS_UPPER_SID (CONFIG_TEST_ORDERS_LOWER_SID + (CONFIG_TEST_ORDERS_INDEX_STRIDE * TEST_CONFIGURATION_MAX_ORDERS)) +#endif + +/* ISO C restricts enumerator values to range of ‘int’ */ + +#define TEST_FOOD_LOWER_SID ((conf_sid_t)CONFIG_TEST_FOOD_LOWER_SID) /* 0x7FFFFFFFFFFFFFFDA: { */ +# define TEST_FOOD_BREAD_LOWER_SID ((conf_sid_t)CONFIG_TEST_FOOD_BREAD_LOWER_SID) /* 0x7FFFFFFFFFFFFFFDB: { */ +# define TEST_FOOD_BREAD_WHITE_SID ((conf_sid_t)CONFIG_TEST_FOOD_BREAD_WHITE_SID) /* 0x7FFFFFFFFFFFFFDC: "" */ +# define TEST_FOOD_BREAD_WHOLE_GRAIN_SID ((conf_sid_t)CONFIG_TEST_FOOD_BREAD_WHOLE_GRAIN_SID) /* 0x7FFFFFFFFFFFFFDD: "" */ +# define TEST_FOOD_BREAD_UPPER_SID ((conf_sid_t)CONFIG_TEST_FOOD_BREAD_UPPER_SID) /* } */ +# define TEST_FOOD_CAKE_LOWER_SID ((conf_sid_t)CONFIG_TEST_FOOD_CAKE_LOWER_SID) /* 0x7FFFFFFFFFFFFFDE: { */ +# define TEST_FOOD_CAKE_CHEESECAKE_SID ((conf_sid_t)CONFIG_TEST_FOOD_CAKE_CHEESECAKE_SID) /* 0x7FFFFFFFFFFFFFDF: "" */ +# define TEST_FOOD_CAKE_DONUT_SID ((conf_sid_t)CONFIG_TEST_FOOD_CAKE_DONUT_SID) /* 0x7FFFFFFFFFFFFFE0: "" */ +# define TEST_FOOD_CAKE_UPPER_SID ((conf_sid_t)CONFIG_TEST_FOOD_CAKE_UPPER_SID) /* } */ +#define TEST_FOOD_UPPER_SID ((conf_sid_t)CONFIG_TEST_FOOD_UPPER_SID) /* } */ +#define TEST_DRINKS_LOWER_SID ((conf_sid_t)CONFIG_TEST_DRINKS_LOWER_SID) /* 0x7FFFFFFFFFFFFFE4: { */ +# define TEST_DRINKS_COFFEE_SID ((conf_sid_t)CONFIG_TEST_DRINKS_COFFEE_SID) /* 0x7FFFFFFFFFFFFFE5: "" */ +# define TEST_DRINKS_TEA_SID ((conf_sid_t)CONFIG_TEST_DRINKS_TEA_SID) /* 0x7FFFFFFFFFFFFFE6: "" */ +# define TEST_DRINKS_COCOA_SID ((conf_sid_t)CONFIG_TEST_DRINKS_COCOA_SID) /* 0x7FFFFFFFFFFFFFE7: "" */ +#define TEST_DRINKS_UPPER_SID ((conf_sid_t)CONFIG_TEST_DRINKS_UPPER_SID) /* } */ +#define TEST_ORDERS_LOWER_SID ((conf_sid_t)CONFIG_TEST_ORDERS_LOWER_SID) /* 0x7FFFFFFFFFFFFFED: [ */ +# define TEST_ORDERS_INDEX_LOWER_SID ((conf_sid_t)CONFIG_TEST_ORDERS_INDEX_LOWER_SID) /* [0] 0x7FFFFFFFFFFFFFEE: { */ +# define TEST_ORDERS_INDEX_STRIDE ((uint32_t)CONFIG_TEST_ORDERS_INDEX_STRIDE) /* 0x7FFFFFFFFFFFFFEF: [ */ +# define TEST_ORDERS_ITEMS_LOWER_SID ((conf_sid_t)CONFIG_TEST_ORDERS_ITEMS_LOWER_SID) /* [0] 0x7FFFFFFFFFFFFFF0: { */ +# define TEST_ORDERS_ITEMS_INDEX_LOWER_SID ((conf_sid_t)CONFIG_TEST_ORDERS_ITEMS_INDEX_LOWER_SID) /* 0x7FFFFFFFFFFFFFF1: "" */ +# define TEST_ORDERS_ITEMS_INDEX_STRIDE ((uint32_t)CONFIG_TEST_ORDERS_ITEMS_INDEX_STRIDE) /* } */ +# define TEST_ORDERS_ITEMS_ITEM_SID ((conf_sid_t)CONFIG_TEST_ORDERS_ITEMS_ITEM_SID) /* [1] 0x7FFFFFFFFFFFFFF2: { */ +# define TEST_ORDERS_ITEMS_UPPER_SID ((conf_sid_t)CONFIG_TEST_ORDERS_ITEMS_UPPER_SID) /* 0x7FFFFFFFFFFFFFF3: "" */ +#define TEST_ORDERS_UPPER_SID ((conf_sid_t)CONFIG_TEST_ORDERS_UPPER_SID) /* } */ + /* ] */ + /* } */ + /* [1] 0x7FFFFFFFFFFFFFF4: { */ + /* 0x7FFFFFFFFFFFFFF5: [ */ + /* [0] 0x7FFFFFFFFFFFFFF6: { */ + /* 0x7FFFFFFFFFFFFFF7: "" */ + /* } */ + /* [1] 0x7FFFFFFFFFFFFFF8: { */ + /* 0x7FFFFFFFFFFFFFF9: "" */ + /* } */ + /* ] */ + /* } */ + /* [2] 0x7FFFFFFFFFFFFFFA: { */ + /* 0x7FFFFFFFFFFFFFFB: [ */ + /* [0] 0x7FFFFFFFFFFFFFFC: { */ + /* 0x7FFFFFFFFFFFFFFD: "" */ + /* } */ + /* [1] 0x7FFFFFFFFFFFFFFE: { */ + /* 0x7FFFFFFFFFFFFFFF: "" */ + /* } */ + /* ] */ + /* } */ + /* ] */ +/** + * @brief Full configuration compound item + */ +struct configuration { + struct { + struct { + food_t white; /**< location of "/food/bread/white" */ + food_t whole_grain; /**< location of "/food/bread/whole_grain" */ + } bread; /**< location of "/food/bread" */ + struct { + food_t cheesecake; /**< location of "/food/cake/cheesecake" */ + food_t donut; /**< location of "/food/cake/donut" */ + } cake; /**< location of "/food/cake" */ + } food; /**< location of "/food" */ + struct { + drink_t coffee; /**< location of "/drinks/coffee" */ + drink_t tea; /**< location of "/drinks/tea" */ + drink_t cocoa; /**< location of "/drinks/cocoa" */ + } drinks; /**< location of "/drinks" */ + order_t orders[TEST_CONFIGURATION_MAX_ORDERS]; /**< location of "/orders" */ +}; + +#ifdef __cplusplus +} +#endif + +#endif /* PERSIST_TYPES_H */ +/** @} */ diff --git a/tests/sys/configuration/main.c b/tests/sys/configuration/main.c new file mode 100644 index 000000000000..1ab06ec77d39 --- /dev/null +++ b/tests/sys/configuration/main.c @@ -0,0 +1,1625 @@ +/* + * Copyright (C) 2023 ML!PA Consulting Gmbh + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * + * @{ + * + * @file + * @author Fabian Hüßler + * + * @} + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compiler_hints.h" +#include "configuration.h" +#include "container.h" +#include "embUnit.h" + +#include "configuration_backend_ram.h" +#include "configuration_backend_flashdb.h" +#include "include/persist_types.h" +#include "persist_types.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#if defined(TEST_CONFIGURATION_BACKEND_RAM) +extern struct configuration persist_conf; +#else +static struct configuration persist_conf; /* dummy */ +#endif + +#if IS_USED(MODULE_CONFIGURATION_STRINGS) +#define _TEST_ASSERT_KEY_STREQ(k, s) TEST_ASSERT(!strcmp(configuration_key_buf(k), s)) +#else +#define _TEST_ASSERT_KEY_STREQ(k, s) +#endif + +static CONF_KEY(key_buf, UINT64_MAX, 40); + +struct configuration _conf = { + .food = { + .bread = { + .white = {"2.00"}, + .whole_grain = {"2.20"} + }, + .cake = { + .cheesecake = {"2.99"}, + .donut = {"2.00"}, + }, + }, + .drinks = { + .coffee = {"1.00"}, + .tea = {"1.00"}, + .cocoa = {"1.50"}, + } +}; + +static inline bool _verify_price(const char *price) +{ + const char *start = price; + char *end = NULL; + long num = strtoll(start, &end, 10); + if (start == end || (errno == ERANGE && (num == LONG_MAX || num == LONG_MIN))) { + return false; + } + if (*end && *end != '.') { + return false; + } + if (*end) { + start = end + 1; + end = NULL; + num = strtoll(start, &end, 10); + if (start == end || (errno == ERANGE && (num == LONG_MAX || num == LONG_MIN))) { + return false; + } + } + return true; +} + +int _any_food_verify(const struct conf_handler *handler, + conf_key_buf_t *key, + const food_t *food) +{ + assert(handler); (void)handler; + assert(key); + assert(food); + + if (!_verify_price(food->price)) { + return -EINVAL; + } + + return 0; +} + +int _any_food_apply(const struct conf_handler *handler, + conf_key_buf_t *key, + const food_t *food) +{ + assert(handler); (void)handler; + assert(key); + assert(food); + + printf("test configuration: Applying %s to %s\n", + food->price, configuration_key_buf(key)); + + return 0; +} + +int _any_drinks_verify(const struct conf_handler *handler, + conf_key_buf_t *key, + const drink_t *drink) +{ + assert(handler); (void)handler; + assert(key); + assert(drink); + + if (!_verify_price(drink->price)) { + return -EINVAL; + } + + return 0; +} + +int _any_drinks_apply(const struct conf_handler *handler, + conf_key_buf_t *key, + const drink_t *drink) +{ + assert(handler); (void)handler; + assert(key); + assert(drink); + + printf("test configuration: Applying %s to %s\n", + drink->price, configuration_key_buf(key)); + + return 0; +} + +static int _food_white_verify(const conf_handler_t *handler, + conf_key_buf_t *key) +{ + return _any_food_verify(handler, key, &_conf.food.bread.white); +} + +static int _food_white_apply(const conf_handler_t *handler, + conf_key_buf_t *key) +{ + return _any_food_apply(handler, key, &_conf.food.bread.white); +} + +static const conf_handler_data_ops_t _bread_white_data_ops = { + .verify = _food_white_verify, + .apply = _food_white_apply, +}; + +static int _food_whole_grain_verify(const conf_handler_t *handler, + conf_key_buf_t *key) +{ + return _any_food_verify(handler, key, &_conf.food.bread.whole_grain); +} + +static int _food_whole_grain_apply(const conf_handler_t *handler, + conf_key_buf_t *key) +{ + return _any_food_apply(handler, key, &_conf.food.bread.whole_grain); +} + +static const conf_handler_data_ops_t _bread_whole_grain_data_ops = { + .verify = _food_whole_grain_verify, + .apply = _food_whole_grain_apply, +}; + +static int _food_cheesecake_verify(const conf_handler_t *handler, + conf_key_buf_t *key) +{ + return _any_food_verify(handler, key, &_conf.food.cake.cheesecake); +} + +static int _food_cheesecake_apply(const conf_handler_t *handler, + conf_key_buf_t *key) +{ + return _any_food_apply(handler, key, &_conf.food.cake.cheesecake); +} + +static const conf_handler_data_ops_t _cake_cheesecake_data_ops = { + .verify = _food_cheesecake_verify, + .apply = _food_cheesecake_apply, +}; + +static int _food_donut_verify(const conf_handler_t *handler, + conf_key_buf_t *key) +{ + return _any_food_verify(handler, key, &_conf.food.cake.donut); +} + +static int _food_donut_apply(const conf_handler_t *handler, + conf_key_buf_t *key) +{ + return _any_food_apply(handler, key, &_conf.food.cake.donut); +} + +static const conf_handler_data_ops_t _cake_donut_data_ops = { + .verify = _food_donut_verify, + .apply = _food_donut_apply, +}; + +static int _drink_coffee_verify(const conf_handler_t *handler, + conf_key_buf_t *key) +{ + return _any_drinks_verify(handler, key, &_conf.drinks.coffee); +} + +static int _drink_coffee_apply(const conf_handler_t *handler, + conf_key_buf_t *key) +{ + return _any_drinks_apply(handler, key, &_conf.drinks.coffee); +} + +static const conf_handler_data_ops_t _drinks_coffee_data_ops = { + .verify = _drink_coffee_verify, + .apply = _drink_coffee_apply, +}; + +static int _drink_tea_verify(const conf_handler_t *handler, + conf_key_buf_t *key) +{ + return _any_drinks_verify(handler, key, &_conf.drinks.tea); +} + +static int _drink_tea_apply(const conf_handler_t *handler, + conf_key_buf_t *key) +{ + return _any_drinks_apply(handler, key, &_conf.drinks.tea); +} + +static const conf_handler_data_ops_t _drinks_tea_data_ops = { + .verify = _drink_tea_verify, + .apply = _drink_tea_apply, +}; + +static int _drink_cocoa_verify(const conf_handler_t *handler, + conf_key_buf_t *key) +{ + return _any_drinks_verify(handler, key, &_conf.drinks.cocoa); +} + +static int _drink_cocoa_apply(const conf_handler_t *handler, + conf_key_buf_t *key) +{ + return _any_drinks_apply(handler, key, &_conf.drinks.cocoa); +} + +static const conf_handler_data_ops_t _drinks_cocoa_data_ops = { + .verify = _drink_cocoa_verify, + .apply = _drink_cocoa_apply, +}; + +static int _orders_verify(const conf_handler_t *handler, + conf_key_buf_t *key) +{ + (void)handler; (void)key; + printf("test configuration: Verifying %s\n", configuration_key_buf(key)); + return 0; +} + +static int _orders_apply(const conf_handler_t *handler, + conf_key_buf_t *key) +{ + (void)handler; (void)key; + printf("test configuration: Applying %s\n", configuration_key_buf(key)); + return 0; +} + +static const conf_handler_data_ops_t _orders_data_ops = { + .verify = _orders_verify, + .apply = _orders_apply, +}; + +static CONF_HANDLER_NODE_ID(_products_food_conf_handler_id, + TEST_FOOD_LOWER_SID, TEST_FOOD_UPPER_SID, + "food"); + +static CONF_HANDLER(_products_food_conf_handler, + &_products_food_conf_handler_id, + NULL, + &configuration_default_backend_ops, + &configuration_default_backend_data_ops, + sizeof(_conf.food), + &_conf.food); + +static CONF_HANDLER_NODE_ID(_products_bread_handler_node_id, + TEST_FOOD_BREAD_LOWER_SID, TEST_FOOD_BREAD_UPPER_SID, + "bread"); + +static CONF_HANDLER(_products_bread_handler, + &_products_bread_handler_node_id, + &_bread_white_data_ops, + &configuration_default_backend_ops, + &configuration_default_backend_data_ops, + sizeof(_conf.food.bread), + &_conf.food.bread); + +static CONF_HANDLER_ID(_products_bread_white_handler_node_id, + TEST_FOOD_BREAD_WHITE_SID, + "white"); + +static CONF_PRIMITIVE_HANDLER(_products_bread_white_handler, + &_products_bread_white_handler_node_id, + &_bread_white_data_ops, + &configuration_default_backend_ops, + &configuration_default_backend_data_ops, + CONF_FLAGS_PRIMITIVE_TSTR, + sizeof(_conf.food.bread.white), + &_conf.food.bread.white); + +static CONF_HANDLER_ID(_products_bread_whole_grain_handler_node_id, + TEST_FOOD_BREAD_WHOLE_GRAIN_SID, + "whole_grain"); + +static CONF_PRIMITIVE_HANDLER(_products_bread_whole_grain_handler, + &_products_bread_whole_grain_handler_node_id, + &_bread_whole_grain_data_ops, + &configuration_default_backend_ops, + &configuration_default_backend_data_ops, + CONF_FLAGS_PRIMITIVE_TSTR, + sizeof(_conf.food.bread.whole_grain), + &_conf.food.bread.whole_grain); + +static CONF_HANDLER_NODE_ID(_products_cake_handler_node_id, + TEST_FOOD_CAKE_LOWER_SID, TEST_FOOD_CAKE_UPPER_SID, + "cake"); + +static CONF_HANDLER(_products_cake_handler, + &_products_cake_handler_node_id, + NULL, + &configuration_default_backend_ops, + &configuration_default_backend_data_ops, + sizeof(_conf.food.cake), + &_conf.food.cake); + +static CONF_HANDLER_ID(_products_cake_cheesecake_handler_node_id, + TEST_FOOD_CAKE_CHEESECAKE_SID, + "cheesecake"); + +static CONF_PRIMITIVE_HANDLER(_products_cake_cheesecake_handler, + &_products_cake_cheesecake_handler_node_id, + &_cake_cheesecake_data_ops, + &configuration_default_backend_ops, + &configuration_default_backend_data_ops, + CONF_FLAGS_PRIMITIVE_TSTR, + sizeof(_conf.food.cake.cheesecake), + &_conf.food.cake.cheesecake); + +static CONF_HANDLER_ID(_products_cake_donut_handler_node_id, + TEST_FOOD_CAKE_DONUT_SID, + "donut"); + +static CONF_PRIMITIVE_HANDLER(_products_cake_donut_handler, + &_products_cake_donut_handler_node_id, + &_cake_donut_data_ops, + &configuration_default_backend_ops, + &configuration_default_backend_data_ops, + CONF_FLAGS_PRIMITIVE_TSTR, + sizeof(_conf.food.cake.donut), + &_conf.food.cake.donut); + +static CONF_HANDLER_NODE_ID(_products_drinks_conf_handler_node_id, + TEST_DRINKS_LOWER_SID, TEST_DRINKS_UPPER_SID, + "drinks"); + +static CONF_HANDLER(_products_drinks_conf_handler, + &_products_drinks_conf_handler_node_id, + NULL, + &configuration_default_backend_ops, + &configuration_default_backend_data_ops, + sizeof(_conf.drinks), + &_conf.drinks); + +static CONF_HANDLER_ID(_products_drinks_coffee_conf_handler_node_id, + TEST_DRINKS_COFFEE_SID, + "coffee"); + +static CONF_PRIMITIVE_HANDLER(_products_drinks_coffee_conf_handler, + &_products_drinks_coffee_conf_handler_node_id, + &_drinks_coffee_data_ops, + &configuration_default_backend_ops, + &configuration_default_backend_data_ops, + CONF_FLAGS_PRIMITIVE_TSTR, + sizeof(_conf.drinks.coffee), + &_conf.drinks.coffee); + +static CONF_HANDLER_ID(_products_drinks_tea_conf_handler_node_id, + TEST_DRINKS_TEA_SID, + "tea"); + +static CONF_PRIMITIVE_HANDLER(_products_drinks_tea_conf_handler, + &_products_drinks_tea_conf_handler_node_id, + &_drinks_tea_data_ops, + &configuration_default_backend_ops, + &configuration_default_backend_data_ops, + CONF_FLAGS_PRIMITIVE_TSTR, + sizeof(_conf.drinks.tea), + &_conf.drinks.tea); + +static CONF_HANDLER_ID(_products_drinks_cocoa_conf_handler_node_id, + TEST_DRINKS_COCOA_SID, + "cocoa"); + +static CONF_PRIMITIVE_HANDLER(_products_drinks_cocoa_conf_handler, + &_products_drinks_cocoa_conf_handler_node_id, + &_drinks_cocoa_data_ops, + &configuration_default_backend_ops, + &configuration_default_backend_data_ops, + CONF_FLAGS_PRIMITIVE_TSTR, + sizeof(_conf.drinks.cocoa), + &_conf.drinks.cocoa); + +static CONF_HANDLER_ARRAY_ID(_products_orders_conf_handler_id, + TEST_ORDERS_LOWER_SID, TEST_ORDERS_UPPER_SID, TEST_ORDERS_INDEX_STRIDE, + "orders"); + +static CONF_ARRAY_HANDLER(_products_orders_conf_handler, + &_products_orders_conf_handler_id, + &_orders_data_ops, + &configuration_default_backend_ops, + &configuration_default_backend_data_ops, + sizeof(_conf.orders[0]), + &_conf.orders, + ARRAY_SIZE(_conf.orders)); + +static CONF_HANDLER_ARRAY_ID(_products_orders_items_conf_handler_id, + TEST_ORDERS_ITEMS_LOWER_SID, TEST_ORDERS_ITEMS_UPPER_SID, TEST_ORDERS_ITEMS_INDEX_STRIDE, + "items"); + +static CONF_ARRAY_HANDLER(_products_orders_items_conf_handler, + &_products_orders_items_conf_handler_id, + NULL, + &configuration_default_backend_ops, + &configuration_default_backend_data_ops, + sizeof(_conf.orders[0].items[0]), + &_conf.orders[0].items, + ARRAY_SIZE(_conf.orders[0].items)); + +static CONF_HANDLER_ID(_products_orders_items_item_conf_handler_id, + TEST_ORDERS_ITEMS_ITEM_SID, + "item"); + +static CONF_PRIMITIVE_HANDLER(_products_orders_items_item_conf_handler, + &_products_orders_items_item_conf_handler_id, + NULL, + NULL, + &configuration_default_backend_data_ops, + CONF_FLAGS_PRIMITIVE_TSTR, + sizeof(_conf.orders[0].items[0]), + &_conf.orders[0].items[0]); + +#ifndef TEST_CONFIGURATION_BACKEND_OPS +#if defined(TEST_CONFIGURATION_BACKEND_RAM) +#define TEST_CONFIGURATION_BACKEND_OPS &conf_backend_ram_ops +#elif defined(TEST_CONFIGURATION_BACKEND_FLASHDB_MTD) || \ + defined(TEST_CONFIGURATION_BACKEND_FLASHDB_VFS) +#define TEST_CONFIGURATION_BACKEND_OPS &conf_backend_flashdb_ops +#else +#define TEST_CONFIGURATION_BACKEND_OPS NULL +#endif +#endif + +static const CONF_BACKEND(_products_backend, + TEST_CONFIGURATION_BACKEND_OPS, + CONF_FMT_RAW +); + +static void _init_backend(void) +{ + *configuration_get_src_backend(&_products_orders_conf_handler.handler) = &_products_backend; + *configuration_get_dst_backend(&_products_orders_conf_handler.handler) = &_products_backend; + *configuration_get_src_backend(&_products_orders_items_conf_handler.handler) = &_products_backend; + *configuration_get_dst_backend(&_products_orders_items_conf_handler.handler) = &_products_backend; + *configuration_get_src_backend(&_products_drinks_cocoa_conf_handler) = &_products_backend; + *configuration_get_dst_backend(&_products_drinks_cocoa_conf_handler) = &_products_backend; + *configuration_get_src_backend(&_products_drinks_tea_conf_handler) = &_products_backend; + *configuration_get_dst_backend(&_products_drinks_tea_conf_handler) = &_products_backend; + *configuration_get_src_backend(&_products_drinks_coffee_conf_handler) = &_products_backend; + *configuration_get_dst_backend(&_products_drinks_coffee_conf_handler) = &_products_backend; + *configuration_get_src_backend(&_products_cake_donut_handler) = &_products_backend; + *configuration_get_dst_backend(&_products_cake_donut_handler) = &_products_backend; + *configuration_get_src_backend(&_products_cake_cheesecake_handler) = &_products_backend; + *configuration_get_dst_backend(&_products_cake_cheesecake_handler) = &_products_backend; + *configuration_get_src_backend(&_products_bread_whole_grain_handler) = &_products_backend; + *configuration_get_dst_backend(&_products_bread_whole_grain_handler) = &_products_backend; + *configuration_get_src_backend(&_products_bread_white_handler) = &_products_backend; + *configuration_get_dst_backend(&_products_bread_white_handler) = &_products_backend; +} + +static void _setup(void) +{ + configuration_register(configuration_get_root(), &_products_food_conf_handler); + configuration_register(configuration_get_root(), &_products_drinks_conf_handler); + configuration_register(configuration_get_root(), &_products_orders_conf_handler.handler); + configuration_register(&_products_food_conf_handler, &_products_bread_handler); + configuration_register(&_products_food_conf_handler, &_products_cake_handler); + configuration_register(&_products_bread_handler, &_products_bread_white_handler); + configuration_register(&_products_bread_handler, &_products_bread_whole_grain_handler); + configuration_register(&_products_cake_handler, &_products_cake_cheesecake_handler); + configuration_register(&_products_cake_handler, &_products_cake_donut_handler); + configuration_register(&_products_drinks_conf_handler, &_products_drinks_coffee_conf_handler); + configuration_register(&_products_drinks_conf_handler, &_products_drinks_tea_conf_handler); + configuration_register(&_products_drinks_conf_handler, &_products_drinks_cocoa_conf_handler); + configuration_register(&_products_orders_conf_handler.handler, + &_products_orders_items_conf_handler.handler); + configuration_register(&_products_orders_items_conf_handler.handler, + &_products_orders_items_item_conf_handler); +} + +static void test_configuration_get(void) +{ + struct configuration conf = { 0 }; + size_t conf_size; + + conf_size = sizeof(conf.food); + key_buf.sid = UINT64_MAX; + TEST_ASSERT(configuration_get(&key_buf, &conf.food, &conf_size)); + + memset(&conf, 0, sizeof(conf)); + conf_size = sizeof(conf.food); + key_buf.sid = TEST_FOOD_LOWER_SID; + TEST_ASSERT(!configuration_get(&key_buf, &conf.food, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&conf.food, &_conf.food, conf_size)); + + memset(&conf, 0, sizeof(conf)); + conf_size = sizeof(conf.food.bread); + key_buf.sid = TEST_FOOD_BREAD_LOWER_SID; + TEST_ASSERT(!configuration_get(&key_buf, &conf.food.bread, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/bread"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&conf.food.bread, &_conf.food.bread, conf_size)); + + memset(&conf, 0, sizeof(conf)); + conf_size = sizeof(conf.food.bread.white); + key_buf.sid = TEST_FOOD_BREAD_WHITE_SID; + TEST_ASSERT(!configuration_get(&key_buf, &conf.food.bread.white, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/bread/white"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&conf.food.bread.white, &_conf.food.bread.white, conf_size)); + + memset(&conf, 0, sizeof(conf)); + conf_size = sizeof(conf.food.bread.whole_grain); + key_buf.sid = TEST_FOOD_BREAD_WHOLE_GRAIN_SID; + TEST_ASSERT(!configuration_get(&key_buf, &conf.food.bread.whole_grain, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/bread/whole_grain"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&conf.food.bread.whole_grain, &_conf.food.bread.whole_grain, conf_size)); + + memset(&conf, 0, sizeof(conf)); + conf_size = sizeof(conf.food.cake); + key_buf.sid = TEST_FOOD_CAKE_LOWER_SID; + TEST_ASSERT(!configuration_get(&key_buf, &conf.food.cake, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/cake"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&conf.food.cake, &_conf.food.cake, conf_size)); + + memset(&conf, 0, sizeof(conf)); + conf_size = sizeof(conf.food.cake.cheesecake); + key_buf.sid = TEST_FOOD_CAKE_CHEESECAKE_SID; + TEST_ASSERT(!configuration_get(&key_buf, &conf.food.cake.cheesecake, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/cake/cheesecake"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&conf.food.cake.cheesecake, &_conf.food.cake.cheesecake, conf_size)); + + memset(&conf, 0, sizeof(conf)); + conf_size = sizeof(conf.food.cake.donut); + key_buf.sid = TEST_FOOD_CAKE_DONUT_SID; + TEST_ASSERT(!configuration_get(&key_buf, &conf.food.cake.donut, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/cake/donut"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&conf.food.cake.donut, &_conf.food.cake.donut, conf_size)); + + conf_size = sizeof(conf.drinks); + key_buf.sid = TEST_DRINKS_LOWER_SID; + TEST_ASSERT(!configuration_get(&key_buf, &conf.drinks, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&conf.drinks, &_conf.drinks, conf_size)); +} + +static void test_configuration_set(void) +{ + struct configuration new_conf = { + .food = { + .bread = { + {"2.50"}, + {"2.70"} + }, + .cake = { + {"3.50"}, + {"2.50"}, + } + }, + .drinks = { + {"1.50"}, + {"1.50"}, + {"2.00"}, + } + }; + size_t conf_size; + + struct configuration conf_backup = _conf; + + conf_size = sizeof(new_conf.food); + key_buf.sid = UINT64_MAX; + TEST_ASSERT(configuration_set(&key_buf, &new_conf.food, &conf_size)); + + memset(&_conf, 0, sizeof(_conf)); + conf_size = sizeof(new_conf.food.bread.white); + key_buf.sid = TEST_FOOD_BREAD_WHITE_SID; + TEST_ASSERT(!configuration_set(&key_buf, &new_conf.food.bread.white, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/bread/white"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&new_conf.food.bread.white, &_conf.food.bread.white, sizeof(_conf.food.bread.white))); + + memset(&_conf, 0, sizeof(_conf)); + conf_size = sizeof(new_conf.food.bread.whole_grain); + key_buf.sid = TEST_FOOD_BREAD_WHOLE_GRAIN_SID; + TEST_ASSERT(!configuration_set(&key_buf, &new_conf.food.bread.whole_grain, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/bread/whole_grain"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&new_conf.food.bread.whole_grain, &_conf.food.bread.whole_grain, sizeof(_conf.food.bread.whole_grain))); + + memset(&_conf, 0, sizeof(_conf)); + conf_size = sizeof(new_conf.food.bread); + key_buf.sid = TEST_FOOD_BREAD_LOWER_SID; + TEST_ASSERT(!configuration_set(&key_buf, &new_conf.food.bread, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/bread"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&new_conf.food.bread, &_conf.food.bread, sizeof(_conf.food.bread))); + + memset(&_conf, 0, sizeof(_conf)); + conf_size = sizeof(new_conf.food.cake.cheesecake); + key_buf.sid = TEST_FOOD_CAKE_CHEESECAKE_SID; + TEST_ASSERT(!configuration_set(&key_buf, &new_conf.food.cake.cheesecake, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/cake/cheesecake"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&new_conf.food.cake.cheesecake, &_conf.food.cake.cheesecake, sizeof(_conf.food.cake.cheesecake))); + + memset(&_conf, 0, sizeof(_conf)); + conf_size = sizeof(new_conf.food.cake.donut); + key_buf.sid = TEST_FOOD_CAKE_DONUT_SID; + TEST_ASSERT(!configuration_set(&key_buf, &new_conf.food.cake.donut, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/cake/donut"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&new_conf.food.cake.donut, &_conf.food.cake.donut, sizeof(_conf.food.cake.donut))); + + memset(&_conf, 0, sizeof(_conf)); + conf_size = sizeof(new_conf.food.cake); + key_buf.sid = TEST_FOOD_CAKE_LOWER_SID; + TEST_ASSERT(!configuration_set(&key_buf, &new_conf.food.cake, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/cake"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&new_conf.food.cake, &_conf.food.cake, sizeof(_conf.food.cake))); + + memset(&_conf, 0, sizeof(_conf)); + conf_size = sizeof(new_conf.food); + key_buf.sid = TEST_FOOD_LOWER_SID; + TEST_ASSERT(!configuration_set(&key_buf, &new_conf.food, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&new_conf.food, &_conf.food, sizeof(_conf.food))); + + memset(&_conf, 0, sizeof(_conf)); + conf_size = sizeof(new_conf.drinks); + key_buf.sid = TEST_DRINKS_LOWER_SID; + TEST_ASSERT(!configuration_set(&key_buf, &new_conf.drinks, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&new_conf.drinks, &_conf.drinks, sizeof(_conf.drinks))); + + _conf = conf_backup; +} + +MAYBE_UNUSED +static void test_configuration_import(void) +{ + struct configuration conf_backup = _conf; + + key_buf.sid = UINT64_MAX; + TEST_ASSERT(configuration_import(&key_buf)); + + memset(&_conf, 0, sizeof(_conf)); + key_buf.sid = TEST_FOOD_LOWER_SID; + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food"); + TEST_ASSERT(!memcmp(&persist_conf.food, &_conf.food, sizeof(_conf.food))); + + memset(&_conf, 0, sizeof(_conf)); + key_buf.sid = TEST_FOOD_BREAD_LOWER_SID; + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/bread"); + TEST_ASSERT(!memcmp(&persist_conf.food.bread, &_conf.food.bread, sizeof(_conf.food.bread))); + + memset(&_conf, 0, sizeof(_conf)); + key_buf.sid = TEST_FOOD_BREAD_WHITE_SID; + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/bread/white"); + TEST_ASSERT(!memcmp(&persist_conf.food.bread.white, &_conf.food.bread.white, sizeof(_conf.food.bread.white))); + + memset(&_conf, 0, sizeof(_conf)); + key_buf.sid = TEST_FOOD_BREAD_WHOLE_GRAIN_SID; + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/bread/whole_grain"); + TEST_ASSERT(!memcmp(&persist_conf.food.bread.whole_grain, &_conf.food.bread.whole_grain, sizeof(_conf.food.bread.whole_grain))); + + memset(&_conf, 0, sizeof(_conf)); + key_buf.sid = TEST_FOOD_CAKE_LOWER_SID; + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/cake"); + TEST_ASSERT(!memcmp(&persist_conf.food.cake, &_conf.food.cake, sizeof(_conf.food.cake))); + + memset(&_conf, 0, sizeof(_conf)); + key_buf.sid = TEST_FOOD_CAKE_CHEESECAKE_SID; + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/cake/cheesecake"); + TEST_ASSERT(!memcmp(&persist_conf.food.cake.cheesecake, &_conf.food.cake.cheesecake, sizeof(_conf.food.cake.cheesecake))); + + memset(&_conf, 0, sizeof(_conf)); + key_buf.sid = TEST_FOOD_CAKE_DONUT_SID; + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/cake/donut"); + TEST_ASSERT(!memcmp(&persist_conf.food.cake.donut, &_conf.food.cake.donut, sizeof(_conf.food.cake.donut))); + + key_buf.sid = TEST_DRINKS_LOWER_SID; + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks"); + TEST_ASSERT(!memcmp(&persist_conf.drinks, &_conf.drinks, sizeof(_conf.drinks))); + + _conf = conf_backup; +} + +MAYBE_UNUSED +static void test_configuration_export(void) +{ + struct configuration new_conf = { + .food = { + .bread = { + {"2.50"}, + {"2.70"} + }, + .cake = { + {"3.50"}, + {"2.50"}, + } + }, + .drinks = { + {"1.50"}, + {"1.50"}, + {"2.00"}, + } + }; + struct configuration conf_backup = _conf; + struct configuration persist_conf_backup = persist_conf; + + _conf = new_conf; + + key_buf.sid = UINT64_MAX; + TEST_ASSERT(configuration_export(&key_buf)); + memset(&persist_conf, 0, sizeof(persist_conf)); + + key_buf.sid = TEST_FOOD_BREAD_WHITE_SID; + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/bread/white"); + TEST_ASSERT(!memcmp(&persist_conf.food.bread.white, &_conf.food.bread.white, sizeof(_conf.food.bread.white))); + + memset(&persist_conf, 0, sizeof(persist_conf)); + key_buf.sid = TEST_FOOD_BREAD_WHOLE_GRAIN_SID; + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/bread/whole_grain"); + TEST_ASSERT(!memcmp(&new_conf.food.bread.whole_grain, &_conf.food.bread.whole_grain, sizeof(_conf.food.bread.whole_grain))); + + memset(&persist_conf, 0, sizeof(persist_conf)); + key_buf.sid = TEST_FOOD_BREAD_LOWER_SID; + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/bread"); + TEST_ASSERT(!memcmp(&new_conf.food.bread, &_conf.food.bread, sizeof(_conf.food.bread))); + + memset(&persist_conf, 0, sizeof(persist_conf)); + key_buf.sid = TEST_FOOD_CAKE_CHEESECAKE_SID; + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/cake/cheesecake"); + TEST_ASSERT(!memcmp(&new_conf.food.cake.cheesecake, &_conf.food.cake.cheesecake, sizeof(_conf.food.cake.cheesecake))); + + memset(&persist_conf, 0, sizeof(persist_conf)); + key_buf.sid = TEST_FOOD_CAKE_DONUT_SID; + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/cake/donut"); + TEST_ASSERT(!memcmp(&new_conf.food.cake.donut, &_conf.food.cake.donut, sizeof(_conf.food.cake.donut))); + + memset(&persist_conf, 0, sizeof(persist_conf)); + key_buf.sid = TEST_FOOD_CAKE_LOWER_SID; + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/cake"); + TEST_ASSERT(!memcmp(&new_conf.food.cake, &_conf.food.cake, sizeof(_conf.food.cake))); + + memset(&persist_conf, 0, sizeof(persist_conf)); + key_buf.sid = TEST_FOOD_LOWER_SID; + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food"); + TEST_ASSERT(!memcmp(&new_conf.food, &_conf.food, sizeof(_conf.food))); + + memset(&persist_conf, 0, sizeof(persist_conf)); + key_buf.sid = TEST_DRINKS_LOWER_SID; + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks"); + TEST_ASSERT(!memcmp(&new_conf.drinks, &_conf.drinks, sizeof(_conf.drinks))); + + _conf = conf_backup; + persist_conf = persist_conf_backup; +} + +static void test_configuration_import_modify_export_import(void) +{ + struct configuration new_conf = { + .food = { + .bread = { + {"2.50"}, + {"2.70"} + }, + .cake = { + {"3.50"}, + {"2.50"}, + } + }, + .drinks = { + {"1.50"}, + {"1.50"}, + {"2.00"}, + } + }; + struct configuration conf_backup = _conf; + struct configuration persist_conf_backup = persist_conf; + size_t conf_size; + + memset(&_conf, 0, sizeof(_conf)); + + key_buf.sid = TEST_FOOD_LOWER_SID; + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food"); + + key_buf.sid = TEST_DRINKS_LOWER_SID; + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks"); + + conf_size = sizeof(new_conf.food); + key_buf.sid = TEST_FOOD_LOWER_SID; + TEST_ASSERT(!configuration_set(&key_buf, &new_conf.food, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food"); + TEST_ASSERT(conf_size == 0); + + conf_size = sizeof(new_conf.drinks); + key_buf.sid = TEST_DRINKS_LOWER_SID; + TEST_ASSERT(!configuration_set(&key_buf, &new_conf.drinks, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks"); + TEST_ASSERT(conf_size == 0); + + key_buf.sid = TEST_FOOD_LOWER_SID; + configuration_delete(&key_buf); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food"); + + key_buf.sid = TEST_DRINKS_LOWER_SID; + configuration_delete(&key_buf); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks"); + + key_buf.sid = TEST_FOOD_LOWER_SID; + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food"); + + key_buf.sid = TEST_DRINKS_LOWER_SID; + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks"); + + memset(&_conf, 0, sizeof(_conf)); + + key_buf.sid = TEST_FOOD_LOWER_SID; + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food"); + TEST_ASSERT(!memcmp(&_conf.food, &new_conf.food, sizeof(_conf.food))); + + key_buf.sid = TEST_DRINKS_LOWER_SID; + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks"); + TEST_ASSERT(!memcmp(&_conf.drinks, &new_conf.drinks, sizeof(new_conf.drinks))); + + _conf = conf_backup; + persist_conf = persist_conf_backup; +} + +static void test_configuration_delete(void) +{ + struct configuration new_conf = { + .food = { + .bread = { + {"2.50"}, + {"2.70"} + }, + .cake = { + {"3.50"}, + {"2.50"}, + } + }, + .drinks = { + {"1.50"}, + {"1.50"}, + {"2.00"}, + } + }; + struct configuration conf_backup = _conf; + struct configuration persist_conf_backup = persist_conf; + size_t conf_size; + + key_buf.sid = UINT64_MAX; + TEST_ASSERT(configuration_delete(&key_buf)); + + key_buf.sid = TEST_FOOD_BREAD_LOWER_SID; + TEST_ASSERT(!configuration_delete(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/bread"); + + conf_size = sizeof(new_conf.food); + key_buf.sid = TEST_FOOD_LOWER_SID; + TEST_ASSERT(!configuration_set(&key_buf, &new_conf.food, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food"); + TEST_ASSERT(conf_size == 0); + + conf_size = sizeof(new_conf.drinks); + key_buf.sid = TEST_DRINKS_LOWER_SID; + TEST_ASSERT(!configuration_set(&key_buf, &new_conf.drinks, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks"); + TEST_ASSERT(conf_size == 0); + + key_buf.sid = TEST_FOOD_LOWER_SID; + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food"); + + key_buf.sid = TEST_DRINKS_LOWER_SID; + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks"); + + key_buf.sid = TEST_FOOD_LOWER_SID; + TEST_ASSERT(!configuration_delete(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food"); + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food"); + + key_buf.sid = TEST_FOOD_BREAD_WHITE_SID; + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/bread/white"); + + key_buf.sid = TEST_DRINKS_LOWER_SID; + TEST_ASSERT(!configuration_delete(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks"); + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks"); + + key_buf.sid = TEST_DRINKS_COFFEE_SID; + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks/coffee"); + + _conf = conf_backup; + persist_conf = persist_conf_backup; +} + +static void test_configuration_verify_apply(void) +{ + struct configuration new_conf = { + .food = { + .bread = { + .white = {"sale"}, + .whole_grain = {"2.70"} + }, + .cake = { + .cheesecake = {"3.50"}, + .donut = {"2.50"}, + } + }, + .drinks = { + .coffee = {"free"}, + .tea = {"1.50"}, + .cocoa = {"2.00"}, + } + }; + size_t conf_size; + + struct configuration conf_backup = _conf; + + key_buf.sid = TEST_FOOD_LOWER_SID; + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food"); + + conf_size = sizeof(new_conf.food.bread.white); + key_buf.sid = TEST_FOOD_BREAD_WHITE_SID; + TEST_ASSERT(!configuration_set(&key_buf, &new_conf.food.bread.white, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/bread/white"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&new_conf.food.bread.white, &_conf.food.bread.white, sizeof(_conf.food.bread.white))); + + key_buf.sid = TEST_FOOD_BREAD_LOWER_SID; + TEST_ASSERT(configuration_verify(&key_buf, false)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/bread"); + TEST_ASSERT(!configuration_verify(&key_buf, true)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/bread"); + TEST_ASSERT(!configuration_apply(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food/bread"); + + key_buf.sid = TEST_DRINKS_LOWER_SID; + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks"); + + conf_size = sizeof(new_conf.drinks.coffee); + key_buf.sid = TEST_DRINKS_COFFEE_SID; + TEST_ASSERT(!configuration_set(&key_buf, &new_conf.drinks.coffee, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks/coffee"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&new_conf.drinks.coffee, &_conf.drinks.coffee, sizeof(_conf.drinks.coffee))); + + TEST_ASSERT(configuration_verify(&key_buf, false)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks/coffee"); + TEST_ASSERT(!configuration_verify(&key_buf, true)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks/coffee"); + TEST_ASSERT(!configuration_apply(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks/coffee"); + + _conf = conf_backup; +} + +static void test_configuration_array_set(void) +{ + + order_t orders[TEST_CONFIGURATION_MAX_ORDERS] = { + { + .items = { + { .item = "donut", }, + { .item = "coffee", }, + }, + }, + { + .items = { + { .item = "cheesecake", }, + { .item = "coffee", }, + }, + }, + { + .items = { + { .item = "coffee", }, + { .item = "coffee", }, + }, + }, + }; + + struct configuration conf_backup = _conf; + + size_t conf_size = sizeof(orders); + key_buf.sid = TEST_ORDERS_LOWER_SID; + TEST_ASSERT(!configuration_set(&key_buf, orders, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&_conf.orders, &orders, sizeof(orders))); + + conf_size = sizeof(orders[0]); + key_buf.sid = CONFIG_TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (3 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(configuration_set(&key_buf, orders, &conf_size)); + + conf_size = sizeof(orders[0]); + key_buf.sid = CONFIG_TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (0 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_set(&key_buf, NULL, NULL)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/0"); + TEST_ASSERT(!memcmp(&_conf.orders[0], &((order_t){0}), sizeof(orders[0]))); + + conf_size = sizeof(orders[1]); + key_buf.sid = CONFIG_TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (1 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_set(&key_buf, NULL, NULL)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/1"); + TEST_ASSERT(!memcmp(&_conf.orders[1], &((order_t){0}), sizeof(orders[1]))); + + conf_size = sizeof(orders[2]); + key_buf.sid = CONFIG_TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (2 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_set(&key_buf, NULL, NULL)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/2"); + TEST_ASSERT(!memcmp(&_conf.orders[0], &((order_t){0}), sizeof(orders[2]))); + + + order_t order_item_0 = { + .items = { {"juice"}, {"tea"} }, + }; + + conf_size = sizeof(order_item_0.items[0]); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + TEST_ORDERS_ITEMS_INDEX_LOWER_SID + (0 * TEST_ORDERS_ITEMS_INDEX_STRIDE) + + (0 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_set(&key_buf, &order_item_0.items[0], &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/0/items/0"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&_conf.orders[0].items[0], &order_item_0.items[0], sizeof(order_item_0.items[0]))); + + conf_size = sizeof(order_item_0.items[1]); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + TEST_ORDERS_ITEMS_INDEX_LOWER_SID + (1 * TEST_ORDERS_ITEMS_INDEX_STRIDE) + + (0 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_set(&key_buf, &order_item_0.items[1], &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/0/items/1"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&_conf.orders[0].items[1], &order_item_0.items[1], sizeof(order_item_0.items[1]))); + + order_t order_item_1 = { + .items = { {"fish"}, {"chips"} }, + }; + + conf_size = sizeof(order_item_1); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + (2 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_set(&key_buf, &order_item_1.items, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/2/items"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&_conf.orders[2].items, &order_item_1.items, sizeof(order_item_1.items))); + + conf_size = sizeof(order_item_1); + key_buf.sid = UINT64_MAX; + TEST_ASSERT(configuration_set(&key_buf, &order_item_1.items, &conf_size)); + + _conf = conf_backup; +} + +static void test_configuration_array_get(void) +{ + struct configuration conf = { 0 }; + size_t conf_size; + + conf_size = sizeof(conf.orders[0]); + key_buf.sid = TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (3 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(configuration_get(&key_buf, &conf.orders[0], &conf_size)); + + conf_size = sizeof(conf.orders); + key_buf.sid = UINT64_MAX; + TEST_ASSERT(configuration_get(&key_buf, &conf.orders, &conf_size)); + + conf_size = sizeof(conf.orders[0].items[0]); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + TEST_ORDERS_ITEMS_INDEX_LOWER_SID + (0 * TEST_ORDERS_ITEMS_INDEX_STRIDE) + + (0 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_get(&key_buf, &conf.orders[0].items[0], &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/0/items/0"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&conf.orders[0].items[0], &_conf.orders[0].items[0], conf_size)); + + conf_size = sizeof(conf.orders[0].items[1]); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + TEST_ORDERS_ITEMS_INDEX_LOWER_SID + (1 * TEST_ORDERS_ITEMS_INDEX_STRIDE) + + (0 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_get(&key_buf, &conf.orders[0].items[1], &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/0/items/1"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&conf.orders[0].items[1], &_conf.orders[0].items[1], conf_size)); + + conf_size = sizeof(conf.orders[1].items[0]); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + TEST_ORDERS_ITEMS_INDEX_LOWER_SID + (0 * TEST_ORDERS_ITEMS_INDEX_STRIDE) + + (1 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_get(&key_buf, &conf.orders[1].items[0], &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/1/items/0"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&conf.orders[1].items[0], &_conf.orders[1].items[0], conf_size)); + + conf_size = sizeof(conf.orders[1].items[1]); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + TEST_ORDERS_ITEMS_INDEX_LOWER_SID + (1 * TEST_ORDERS_ITEMS_INDEX_STRIDE) + + (1 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_get(&key_buf, &conf.orders[1].items[1], &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/1/items/1"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&conf.orders[1].items[1], &_conf.orders[1].items[1], conf_size)); + + conf_size = sizeof(conf.orders[2].items[0]); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + TEST_ORDERS_ITEMS_INDEX_LOWER_SID + (0 * TEST_ORDERS_ITEMS_INDEX_STRIDE) + + + (2 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_get(&key_buf, &conf.orders[2].items[0], &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/2/items/0"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&conf.orders[2].items[0], &_conf.orders[2].items[0], conf_size)); + + conf_size = sizeof(conf.orders[2].items[1]); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + TEST_ORDERS_ITEMS_INDEX_LOWER_SID + (1 * TEST_ORDERS_ITEMS_INDEX_STRIDE) + + (2 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_get(&key_buf, &conf.orders[2].items[1], &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/2/items/1"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&conf.orders[2].items[1], &_conf.orders[2].items[1], conf_size)); +} + +MAYBE_UNUSED +static void test_configuration_array_import(void) +{ + struct configuration conf_backup = _conf; + + memset(&_conf, 0, sizeof(_conf)); + key_buf.sid = TEST_ORDERS_LOWER_SID; + TEST_ASSERT(!configuration_import(&key_buf)); + TEST_ASSERT(!memcmp(&_conf.orders, &persist_conf.orders, sizeof(_conf.orders))); + + memset(&_conf, 0, sizeof(_conf)); + key_buf.sid = UINT64_MAX; + TEST_ASSERT(configuration_import(&key_buf)); + + memset(&_conf, 0, sizeof(_conf)); + key_buf.sid = TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (0 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/0"); + TEST_ASSERT(!memcmp(&_conf.orders[0], &persist_conf.orders[0], + sizeof(_conf.orders[0]))); + + memset(&_conf, 0, sizeof(_conf)); + key_buf.sid = TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (1 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/1"); + TEST_ASSERT(!memcmp(&_conf.orders[1], &persist_conf.orders[1], + sizeof(_conf.orders[1]))); + + memset(&_conf, 0, sizeof(_conf)); + key_buf.sid = TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (2 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/2"); + TEST_ASSERT(!memcmp(&_conf.orders[2], &persist_conf.orders[2], + sizeof(_conf.orders[2]))); + + memset(&_conf, 0, sizeof(_conf)); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + (0 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/0/items"); + /* /orders/0/items has no import handler */ + TEST_ASSERT(memcmp(&_conf.orders[0].items, &persist_conf.orders[0].items, + sizeof(_conf.orders[0].items))); + + memset(&_conf, 0, sizeof(_conf)); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + (1 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/1/items"); + /* /orders/1/items has no import handler */ + TEST_ASSERT(memcmp(&_conf.orders[1].items, &persist_conf.orders[1].items, + sizeof(_conf.orders[1].items))); + + memset(&_conf, 0, sizeof(_conf)); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + (2 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/2/items"); + /* /orders/2/items has no import handler */ + TEST_ASSERT(memcmp(&_conf.orders[2].items, &persist_conf.orders[2].items, + sizeof(_conf.orders[2].items))); + + memset(&_conf, 0, sizeof(_conf)); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + TEST_ORDERS_ITEMS_INDEX_LOWER_SID + (0 * TEST_ORDERS_ITEMS_INDEX_STRIDE) + + (0 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/0/items/0"); + /* /orders/0/items/0 has no import handler */ + TEST_ASSERT(memcmp(&_conf.orders[0].items[0], &persist_conf.orders[0].items[0], + sizeof(_conf.orders[0].items[0]))); + + memset(&_conf, 0, sizeof(_conf)); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + TEST_ORDERS_ITEMS_INDEX_LOWER_SID + (1 * TEST_ORDERS_ITEMS_INDEX_STRIDE) + + (0 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/0/items/1"); + /* /orders/0/items/1 has no import handler */ + TEST_ASSERT(memcmp(&_conf.orders[0].items[1], &persist_conf.orders[0].items[1], + sizeof(_conf.orders[0].items[1]))); + + _conf = conf_backup; +} + +MAYBE_UNUSED +static void test_configuration_array_export(void) +{ + struct configuration new_conf = { + .orders = { + { + .items = { + { .item = "donut", }, + { .item = "coffee", }, + }, + }, + { + .items = { + { .item = "cheesecake", }, + { .item = "coffee", }, + }, + }, + { + .items = { + { .item = "coffee", }, + { .item = "coffee", }, + }, + }, + }, + }; + struct configuration conf_backup = _conf; + struct configuration persist_conf_backup = persist_conf; + + _conf = new_conf; + + key_buf.sid = UINT64_MAX; + TEST_ASSERT(configuration_export(&key_buf)); + + memset(&persist_conf, 0, sizeof(persist_conf)); + key_buf.sid = TEST_ORDERS_LOWER_SID; + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders"); + TEST_ASSERT(!memcmp(&persist_conf.orders, &_conf.orders, sizeof(_conf.orders))); + + memset(&persist_conf, 0, sizeof(persist_conf)); + key_buf.sid = TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (0 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/0"); + TEST_ASSERT(!memcmp(&persist_conf.orders[0], &_conf.orders[0], sizeof(_conf.orders[0]))); + + memset(&persist_conf, 0, sizeof(persist_conf)); + key_buf.sid = TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (1 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/1"); + TEST_ASSERT(!memcmp(&persist_conf.orders[1], &_conf.orders[1], sizeof(_conf.orders[1]))); + + memset(&persist_conf, 0, sizeof(persist_conf)); + key_buf.sid = TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (2 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/2"); + TEST_ASSERT(!memcmp(&persist_conf.orders[2], &_conf.orders[2], sizeof(_conf.orders[2]))); + + memset(&persist_conf, 0, sizeof(persist_conf)); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + (1 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/1/items"); + /* /orders/1/items has no export handler */ + TEST_ASSERT(memcmp(&persist_conf.orders[1].items, &_conf.orders[1].items, + sizeof(_conf.orders[1].items))); + + memset(&persist_conf, 0, sizeof(persist_conf)); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + (0 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/0/items"); + /* /orders/1/items has no export handler */ + TEST_ASSERT(memcmp(&persist_conf.orders[0].items, &_conf.orders[0].items, + sizeof(_conf.orders[0].items))); + + memset(&persist_conf, 0, sizeof(persist_conf)); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + (1 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/1/items"); + /* /orders/1/items has no export handler */ + TEST_ASSERT(memcmp(&persist_conf.orders[1].items, &_conf.orders[1].items, + sizeof(_conf.orders[1].items))); + + memset(&persist_conf, 0, sizeof(persist_conf)); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + (2 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/2/items"); + /* /orders/1/items has no export handler */ + TEST_ASSERT(memcmp(&persist_conf.orders[2].items, &_conf.orders[2].items, + sizeof(_conf.orders[2].items))); + + memset(&persist_conf, 0, sizeof(persist_conf)); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + TEST_ORDERS_ITEMS_INDEX_LOWER_SID + (0 * TEST_ORDERS_ITEMS_INDEX_STRIDE) + + (0 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/0/items/0"); + /* /orders/1/items has no export handler */ + TEST_ASSERT(memcmp(&persist_conf.orders[0].items[0], &_conf.orders[0].items[0], + sizeof(_conf.orders[0].items[0]))); + + memset(&persist_conf, 0, sizeof(persist_conf)); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + TEST_ORDERS_ITEMS_INDEX_LOWER_SID + (1 * TEST_ORDERS_ITEMS_INDEX_STRIDE) + + (0 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/0/items/1"); + /* /orders/1/items has no export handler */ + TEST_ASSERT(memcmp(&persist_conf.orders[0].items[1], &_conf.orders[0].items[1], + sizeof(_conf.orders[0].items[1]))); + + _conf = conf_backup; + persist_conf = persist_conf_backup; +} + +static void test_configuration_array_delete(void) +{ + struct configuration new_conf = { + .orders = { + { + .items = { + { .item = "donut", }, + { .item = "coffee", }, + }, + }, + { + .items = { + { .item = "cheesecake", }, + { .item = "coffee", }, + }, + }, + { + .items = { + { .item = "coffee", }, + { .item = "coffee", }, + }, + }, + }, + }; + struct configuration conf_backup = _conf; + struct configuration persist_conf_backup = persist_conf; + + _conf = new_conf; + + key_buf.sid = UINT64_MAX; + TEST_ASSERT(configuration_delete(&key_buf)); + + key_buf.sid = TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (0 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_delete(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/0"); + key_buf.sid = TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (1 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_delete(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/1"); + key_buf.sid = TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (2 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_delete(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/2"); + + key_buf.sid = TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (0 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/0"); + TEST_ASSERT(!memcmp(&_conf.orders, &new_conf.orders, sizeof(_conf.orders))); + key_buf.sid = TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (1 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/1"); + TEST_ASSERT(!memcmp(&_conf.orders, &new_conf.orders, sizeof(_conf.orders))); + key_buf.sid = TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (2 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/2"); + TEST_ASSERT(!memcmp(&_conf.orders, &new_conf.orders, sizeof(_conf.orders))); + + key_buf.sid = TEST_ORDERS_LOWER_SID; + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders"); + + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + (0 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_delete(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/0/items"); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + (1 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_delete(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/1/items"); + key_buf.sid = TEST_ORDERS_ITEMS_LOWER_SID + (2 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_delete(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/2/items"); + + key_buf.sid = TEST_ORDERS_LOWER_SID; + TEST_ASSERT(!configuration_set(&key_buf, NULL, NULL)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders"); + + key_buf.sid = TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (0 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/0"); + TEST_ASSERT(!memcmp(&_conf.orders[0], &new_conf.orders[0], sizeof(_conf.orders[0]))); + key_buf.sid = TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (1 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/1"); + TEST_ASSERT(!memcmp(&_conf.orders[1], &new_conf.orders[1], sizeof(_conf.orders[1]))); + key_buf.sid = TEST_ORDERS_LOWER_SID + TEST_ORDERS_INDEX_LOWER_SID + (2 * TEST_ORDERS_INDEX_STRIDE); + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders/2"); + TEST_ASSERT(!memcmp(&_conf.orders[2], &new_conf.orders[2], sizeof(_conf.orders[2]))); + + persist_conf = persist_conf_backup; + _conf = conf_backup; +} + +static void test_configuration_array_import_modify_export_import(void) +{ + struct configuration new_conf = { + .orders = { + { + .items = { + { .item = "donut", }, + { .item = "coffee", }, + }, + }, + { + .items = { + { .item = "cheesecake", }, + { .item = "coffee", }, + }, + }, + { + .items = { + { .item = "coffee", }, + { .item = "coffee", }, + }, + }, + }, + }; + struct configuration conf_backup = _conf; + struct configuration persist_conf_backup = persist_conf; + size_t conf_size; + + memset(&_conf, 0, sizeof(_conf)); + + key_buf.sid = TEST_ORDERS_LOWER_SID; + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders"); + + conf_size = sizeof(new_conf.orders); + key_buf.sid = TEST_ORDERS_LOWER_SID; + TEST_ASSERT(!configuration_set(&key_buf, &new_conf.orders, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&_conf.orders, &new_conf.orders, sizeof(_conf.orders))); + + key_buf.sid = TEST_ORDERS_LOWER_SID; + TEST_ASSERT(!configuration_delete(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders"); + + key_buf.sid = TEST_ORDERS_LOWER_SID; + TEST_ASSERT(!configuration_export(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders"); + TEST_ASSERT(!configuration_import(&key_buf)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders"); + TEST_ASSERT(!memcmp(&_conf.orders, &new_conf.orders, sizeof(_conf.orders))); + + _conf = conf_backup; + persist_conf = persist_conf_backup; +} + +static void test_configuration_encode_decode(void) +{ + struct configuration conf_backup = _conf; + struct configuration persist_conf_backup = persist_conf; + memset(&persist_conf, 0, sizeof(persist_conf)); + struct configuration conf = { 0 }; (void)conf; + + key_buf.sid = 0; + TEST_ASSERT(!configuration_export(&key_buf)); + TEST_ASSERT(!configuration_import(&key_buf)); + key_buf.sid = TEST_FOOD_LOWER_SID; + size_t conf_size = sizeof(conf.food); + TEST_ASSERT(!configuration_get(&key_buf, &conf.food, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/food"); + TEST_ASSERT(conf_size == 0); + key_buf.sid = TEST_DRINKS_LOWER_SID; + conf_size = sizeof(conf.drinks); + TEST_ASSERT(!configuration_get(&key_buf, &conf.drinks, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/drinks"); + TEST_ASSERT(conf_size == 0); + key_buf.sid = TEST_ORDERS_LOWER_SID; + conf_size = sizeof(conf.orders); + TEST_ASSERT(!configuration_get(&key_buf, &conf.orders, &conf_size)); + _TEST_ASSERT_KEY_STREQ(&key_buf, "/orders"); + TEST_ASSERT(conf_size == 0); + TEST_ASSERT(!memcmp(&conf, &_conf, sizeof(conf))); + + _conf = conf_backup; + persist_conf = persist_conf_backup; +} + +Test* test_configuration(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_configuration_get), + new_TestFixture(test_configuration_set), +#if defined(TEST_CONFIGURATION_BACKEND_RAM) + /* we only know what is stored on the pseudo backend */ + new_TestFixture(test_configuration_import), + new_TestFixture(test_configuration_export), +#endif + new_TestFixture(test_configuration_delete), + new_TestFixture(test_configuration_import_modify_export_import), + new_TestFixture(test_configuration_verify_apply), + new_TestFixture(test_configuration_array_set), + new_TestFixture(test_configuration_array_get), +#if defined(TEST_CONFIGURATION_BACKEND_RAM) + /* we only know what is stored on the pseudo backend */ + new_TestFixture(test_configuration_array_import), + new_TestFixture(test_configuration_array_export), +#endif + new_TestFixture(test_configuration_array_delete), + new_TestFixture(test_configuration_array_import_modify_export_import), + new_TestFixture(test_configuration_encode_decode), + }; + + EMB_UNIT_TESTCALLER(tests_configuration, NULL, NULL, fixtures); + return (Test *)&tests_configuration; +} + +static const CONF_BACKEND(_root_backend, + TEST_CONFIGURATION_BACKEND_OPS, + CONF_FMT_CBOR +); + +/* override weak function */ +void auto_init_configuration_root_backend_init(void) +{ + *configuration_get_src_backend(configuration_get_root()) = &_root_backend; + *configuration_get_dst_backend(configuration_get_root()) = &_root_backend; +} + +int main(void) +{ + _setup(); + _init_backend(); + + TESTS_START(); + TESTS_RUN(test_configuration()); + TESTS_END(); + + puts("Tests done"); +} From f6e06e64b0e3f6fd02e9362f1b1b2b9fc4716b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20H=C3=BC=C3=9Fler?= Date: Wed, 13 Sep 2023 21:50:53 +0200 Subject: [PATCH 05/12] include/mtd_default.h: include mtd_flashpage.h --- drivers/include/mtd_default.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/include/mtd_default.h b/drivers/include/mtd_default.h index 3feeec33d3f3..bc3756ddcaba 100644 --- a/drivers/include/mtd_default.h +++ b/drivers/include/mtd_default.h @@ -33,6 +33,10 @@ extern "C" { #include "mtd_emulated.h" #endif +#if defined(MODULE_MTD_FLASHPAGE) +#include "mtd_flashpage.h" +#endif + #if defined(MODULE_MTD_SDCARD_DEFAULT) extern mtd_sdcard_t mtd_sdcard_dev0; #endif From d255c36425d1080632265f2c724290dd63361229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20H=C3=BC=C3=9Fler?= Date: Tue, 23 Apr 2024 18:46:20 +0200 Subject: [PATCH 06/12] sys/configuration: add delta encoding/decoding --- sys/configuration/Makefile.dep | 17 +++++++++++++ sys/configuration/Makefile.include | 12 +++++++++ sys/configuration/configuration.c | 22 ++++++++++++++-- sys/configuration/default_handlers.c | 38 +++++++++++++++++++++++----- sys/include/configuration.h | 4 ++- 5 files changed, 83 insertions(+), 10 deletions(-) diff --git a/sys/configuration/Makefile.dep b/sys/configuration/Makefile.dep index 1cf4f3442b4b..8249fdee5f8b 100644 --- a/sys/configuration/Makefile.dep +++ b/sys/configuration/Makefile.dep @@ -22,3 +22,20 @@ ifneq (,$(filter configuration_backend_flashdb%,$(USEMODULE))) USEMODULE += flashdb_mtd endif endif + +ifneq (,$(filter configuration_strings,$(USEMODULE))) + USEMODULE += configuration_handler_parent +endif + +ifneq (,$(filter configuration_delta_encoding,$(USEMODULE))) + USEMODULE += configuration_handler_parent +endif + +ifneq (,$(filter configuration_delta_decoding,$(USEMODULE))) + USEMODULE += configuration_handler_parent +endif + +ifneq (,$(filter configuration_delta_encoding_decoding,$(USEMODULE))) + USEMODULE += configuration_delta_encoding + USEMODULE += configuration_delta_decoding +endif diff --git a/sys/configuration/Makefile.include b/sys/configuration/Makefile.include index b20fe1175a11..29001793c591 100644 --- a/sys/configuration/Makefile.include +++ b/sys/configuration/Makefile.include @@ -4,6 +4,9 @@ USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_configuration) # any backend pseudo module PSEUDOMODULES += configuration_backend_% +# Store a parent reference in the configuration handler +PSEUDOMODULES += configuration_handler_parent + # use if you want to use custom handlers for import, export, delete, encode, decode PSEUDOMODULES += configuration_custom_operations @@ -12,3 +15,12 @@ PSEUDOMODULES += configuration_destination_backend # use if you want to construct a string path for a configuration item PSEUDOMODULES += configuration_strings + +# encode SIDs relative to their parent SID (delta encoding) +PSEUDOMODULES += configuration_delta_encoding + +# decode SIDs relative to their parent SID (delta decoding) +PSEUDOMODULES += configuration_delta_decoding + +# alias for configuration_delta_encoding and configuration_delta_decoding +PSEUDOMODULES += configuration_delta_encoding_decoding diff --git a/sys/configuration/configuration.c b/sys/configuration/configuration.c index 5db465a8291f..70a37e64cc8b 100644 --- a/sys/configuration/configuration.c +++ b/sys/configuration/configuration.c @@ -164,6 +164,19 @@ static bool _sid_in_array_bounds(const conf_array_handler_t *array, conf_sid_t s return _sid_array_index(array, sid) < array->array_size; } +#if IS_USED(MODULE_CONFIGURATION_STRINGS) +static int _handler_level(const conf_handler_t *handler) +{ + assert(handler); + int level = 0; + while (handler->parent) { + level++; + handler = handler->parent; + } + return level; +} +#endif + static int _configuration_append_segment(const conf_handler_t *next, conf_key_buf_t *key, char *key_buf, size_t key_buf_len) { @@ -173,7 +186,7 @@ static int _configuration_append_segment(const conf_handler_t *next, conf_key_bu char *buf = key_buf; size_t size = key_buf_len; if (*next->node_id->subtree) { - for (int l = 0; l < (int)next->level - 1; l++) { + for (int l = 0; l < _handler_level(next) - 1; l++) { if (*buf++ != '/') { return -EINVAL; } @@ -778,7 +791,9 @@ void configuration_register(conf_handler_t *parent, conf_handler_t *node) while (*end) { end = (conf_handler_t **)&(*end)->node.next; } - node->level = parent->level + 1; +#if IS_USED(MODULE_CONFIGURATION_HANDLER_PARENT) + node->parent = parent; +#endif *end = node; } @@ -915,6 +930,9 @@ int configuration_decode_internal(conf_path_iterator_t *iter, conf_iterator_rest #endif bool skip = false; /* skip structure is parsed SID is not known */ if (!ret) { +#if IS_USED(MODULE_CONFIGURATION_DELTA_DECODING) + key->sid += iter->stack[iter->sp - 1].node->node_id->sid_lower; +#endif key->offset = 0; key->sid_normal = key->sid; /* for a new SID we must start from the root to get the full key->offset */ diff --git a/sys/configuration/default_handlers.c b/sys/configuration/default_handlers.c index e44f19ca159a..95e845ad4737 100644 --- a/sys/configuration/default_handlers.c +++ b/sys/configuration/default_handlers.c @@ -280,9 +280,13 @@ static int _encode_node_handler_cbor(const conf_handler_t *handler, #if IS_USED(MODULE_NANOCBOR) nanocbor_encoder_t enc; nanocbor_encoder_init(&enc, *enc_data, *enc_size); - if (key->sid_normal== handler->node_id->sid_lower) { + if (key->sid_normal == handler->node_id->sid_lower) { if (*sid_start != key->sid) { - if (nanocbor_fmt_uint(&enc, handler->node_id->sid_lower) < 0) { + uint64_t sid = key->sid; +#if IS_USED(MODULE_CONFIGURATION_DELTA_ENCODING) + sid = key->sid - handler->parent->node_id->sid_lower; +#endif + if (nanocbor_fmt_uint(&enc, sid) < 0) { DEBUG("configuration: failed to encode node SID %"PRIu64" handler %p\n", key->sid, (const void *)handler); return -ENOBUFS; @@ -363,7 +367,11 @@ static int _encode_array_handler_cbor(const conf_handler_t *handler, nanocbor_encoder_init(&enc, *enc_data, *enc_size); if (key->sid_normal == arr_handler->handler.array_id->sid_lower) { if (*sid_start != key->sid) { - if (nanocbor_fmt_uint(&enc, arr_handler->handler.array_id->sid_lower) < 0) { + uint64_t sid = key->sid; +#if IS_USED(MODULE_CONFIGURATION_DELTA_ENCODING) + sid = key->sid - handler->parent->array_id->sid_lower; +#endif + if (nanocbor_fmt_uint(&enc, sid) < 0) { DEBUG("configuration: failed to encode array SID %"PRIu64" handler %p\n", key->sid, (const void *)arr_handler); return -ENOBUFS; @@ -488,7 +496,11 @@ static int _encode_uint_cbor(const conf_handler_t *handler, const void *data = ((const uint8_t *)handler->data) + key->offset; nanocbor_encoder_t enc; nanocbor_encoder_init(&enc, *enc_data, *enc_size); - if (nanocbor_fmt_uint(&enc, key->sid) < 0) { + uint64_t sid = key->sid; +#if IS_USED(MODULE_CONFIGURATION_DELTA_ENCODING) + sid = key->sid - handler->parent->node_id->sid_lower; +#endif + if (nanocbor_fmt_uint(&enc, sid) < 0) { DEBUG("configuration: failed to encode SID %"PRIu64" for CBOR uint handler %p\n", key->sid, (const void *)handler); return -ENOBUFS; @@ -579,7 +591,11 @@ static int _encode_int_cbor(const conf_handler_t *handler, const void *data = ((const uint8_t *)handler->data) + key->offset; nanocbor_encoder_t enc; nanocbor_encoder_init(&enc, *enc_data, *enc_size); - if (nanocbor_fmt_uint(&enc, key->sid) < 0) { + uint64_t sid = key->sid; +#if IS_USED(MODULE_CONFIGURATION_DELTA_ENCODING) + sid = key->sid - handler->parent->node_id->sid_lower; +#endif + if (nanocbor_fmt_uint(&enc, sid) < 0) { DEBUG("configuration: failed to encode SID %"PRIu64" for CBOR int handler %p\n", key->sid, (const void *)handler); return -ENOBUFS; @@ -670,7 +686,11 @@ static int _encode_byte_string_cbor(const conf_handler_t *handler, const uint8_t *data = ((const uint8_t *)handler->data) + key->offset; nanocbor_encoder_t enc; nanocbor_encoder_init(&enc, *enc_data, *enc_size); - if (nanocbor_fmt_uint(&enc, key->sid) < 0) { + uint64_t sid = key->sid; +#if IS_USED(MODULE_CONFIGURATION_DELTA_ENCODING) + sid = key->sid - handler->parent->node_id->sid_lower; +#endif + if (nanocbor_fmt_uint(&enc, sid) < 0) { DEBUG("configuration: failed to encode SID %"PRIu64" for CBOR byte string handler %p\n", key->sid, (const void *)handler); return -ENOBUFS; @@ -727,7 +747,11 @@ static int _encode_text_string_cbor(const conf_handler_t *handler, const char *data = ((const char *)handler->data) + key->offset; nanocbor_encoder_t enc; nanocbor_encoder_init(&enc, *enc_data, *enc_size); - if (nanocbor_fmt_uint(&enc, key->sid) < 0) { + uint64_t sid = key->sid; +#if IS_USED(MODULE_CONFIGURATION_DELTA_ENCODING) + sid = key->sid - handler->parent->node_id->sid_lower; +#endif + if (nanocbor_fmt_uint(&enc, sid) < 0) { DEBUG("configuration: failed to encode SID %"PRIu64" for CBOR test string handler %p\n", key->sid, (const void *)handler); return -ENOBUFS; diff --git a/sys/include/configuration.h b/sys/include/configuration.h index 11447e9d5f82..c22a1500d2dc 100644 --- a/sys/include/configuration.h +++ b/sys/include/configuration.h @@ -632,6 +632,9 @@ enum { */ typedef struct conf_handler { list_node_t node; /**< Every node is in a list, managed by its parent */ +#if IS_USED(MODULE_CONFIGURATION_HANDLER_PARENT) + struct conf_handler *parent; /**< Pointer to the parent node */ +#endif struct conf_handler *subnodes; /**< Every node has a list of subnodes */ union { const conf_handler_array_id_t *array_id; /**< Pointer to handler array identification */ @@ -649,7 +652,6 @@ typedef struct conf_handler { #endif void *data; /**< Pointer to the configuration item data location (may be NULL) */ uint32_t size; /**< Configuration item size in bytes */ - unsigned level; /**< Level in the configuration tree (root = 0) */ conf_handler_flags_t conf_flags; /**< Configuration of handler behavior */ mutex_t mutex; /**< Lock for unique access to the configuration item */ } conf_handler_t; From e76147e122d40021b1f3832ca730aacc78509f29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20H=C3=BC=C3=9Fler?= Date: Wed, 24 Apr 2024 21:03:29 +0200 Subject: [PATCH 07/12] sys/configuration: drop configuration_backend_flashdb_vfs --- sys/configuration/Makefile.dep | 5 +-- sys/configuration/backend/flashdb.c | 40 +------------------ .../include/configuration_backend_flashdb.h | 22 ---------- 3 files changed, 3 insertions(+), 64 deletions(-) diff --git a/sys/configuration/Makefile.dep b/sys/configuration/Makefile.dep index 8249fdee5f8b..105eb71d975e 100644 --- a/sys/configuration/Makefile.dep +++ b/sys/configuration/Makefile.dep @@ -15,10 +15,7 @@ ifneq (,$(filter configuration_backend_flashdb%,$(USEMODULE))) USEMODULE += configuration_strings USEMODULE += configuration_backend_flashdb USEMODULE += flashdb_kvdb - ifneq (,$(filter configuration_backend_flashdb_vfs,$(USEMODULE))) - USEMODULE += flashdb_vfs - USEMODULE += vfs_default - else ifneq (,$(filter configuration_backend_flashdb_mtd,$(USEMODULE))) + ifneq (,$(filter configuration_backend_flashdb_mtd,$(USEMODULE))) USEMODULE += flashdb_mtd endif endif diff --git a/sys/configuration/backend/flashdb.c b/sys/configuration/backend/flashdb.c index 189ec77df178..0130df973ae1 100644 --- a/sys/configuration/backend/flashdb.c +++ b/sys/configuration/backend/flashdb.c @@ -20,6 +20,7 @@ #include #include +#include #include "configuration.h" #include "configuration_backend_flashdb.h" @@ -27,7 +28,6 @@ #include "macros/units.h" #include "mutex.h" #include "mtd_default.h" -#include "vfs_default.h" #include "fal_cfg.h" static mutex_t _kvdb_locker = MUTEX_INIT; @@ -45,18 +45,6 @@ const char *configuration_backend_flashdb_mtd_choose_partition(void) return FAL_PART0_LABEL; } -__attribute__((weak)) -mtd_dev_t *configuration_backend_flashdb_vfs_choose_dev(void) -{ - return mtd_default_get_dev(0); -} - -__attribute__((weak)) -const char *configuration_backend_flashdb_vfs_choose_path(void) -{ - return VFS_DEFAULT_DATA"/"CONFIGURATION_FLASHDB_VFS_FOLDER; -} - static void _lock(fdb_db_t db) { mutex_lock(db->user_data); @@ -70,36 +58,16 @@ static void _unlock(fdb_db_t db) static int _be_fdb_init(mtd_dev_t *mtd) { assert(mtd); - uint32_t size; -#if IS_USED(MODULE_CONFIGURATION_BACKEND_FLASHDB_VFS) - bool file_mode = true; - fdb_kvdb_control(&_kvdb, FDB_KVDB_CTRL_SET_FILE_MODE, &file_mode); - int fail; - if ((fail = vfs_mkdir(configuration_backend_flashdb_vfs_choose_path(), 0777)) < 0) { - if (fail != -EEXIST) { - /* probably not mounted (try with vfs_auto_format) */ - return fail; - } - } -#endif /* The MTD must be initialized! */ - size = mtd->pages_per_sector * mtd->page_size; + uint32_t size = mtd->pages_per_sector * mtd->page_size; size = DIV_ROUND_UP((CONFIGURATION_FLASHDB_MIN_SECTOR_SIZE_DEFAULT_KiB * KiB(1)), size) * size; /* sector size must be set before max size */ fdb_kvdb_control(&_kvdb, FDB_KVDB_CTRL_SET_SEC_SIZE, &size); -#if IS_USED(MODULE_CONFIGURATION_BACKEND_FLASHDB_VFS) - size *= CONFIGURATION_FLASHDB_VFS_MAX_SECTORS; - fdb_kvdb_control(&_kvdb, FDB_KVDB_CTRL_SET_MAX_SIZE, &size); -#endif fdb_kvdb_control(&_kvdb, FDB_KVDB_CTRL_SET_LOCK, (void *)(uintptr_t)_lock); fdb_kvdb_control(&_kvdb, FDB_KVDB_CTRL_SET_UNLOCK, (void *)(uintptr_t)_unlock); if (fdb_kvdb_init(&_kvdb, "kvdb_configuration", -#if IS_USED(MODULE_CONFIGURATION_BACKEND_FLASHDB_VFS) - configuration_backend_flashdb_vfs_choose_path(), -#elif IS_USED(MODULE_CONFIGURATION_BACKEND_FLASHDB_MTD) configuration_backend_flashdb_mtd_choose_partition(), -#endif NULL, &_kvdb_locker) != FDB_NO_ERR) { return -EINVAL; @@ -195,13 +163,9 @@ int configuration_backend_flashdb_init(mtd_dev_t *mtd) void auto_init_configuration_backend_flashdb(void) { -#if IS_USED(MODULE_CONFIGURATION_BACKEND_FLASHDB_MTD) extern void fdb_mtd_init(mtd_dev_t *mtd); fdb_mtd_init(configuration_backend_flashdb_mtd_choose_dev()); int fail = configuration_backend_flashdb_init(configuration_backend_flashdb_mtd_choose_dev()); -#else - int fail = configuration_backend_flashdb_init(configuration_backend_flashdb_vfs_choose_dev()); -#endif assert(!fail); (void)fail; #if (IS_USED(MODULE_CONFIGURATION_BACKEND_RESET_FLASHDB)) fail = configuration_backend_flashdb_reset(); diff --git a/sys/configuration/include/configuration_backend_flashdb.h b/sys/configuration/include/configuration_backend_flashdb.h index cb4c55ad874b..dd09973f9b0a 100644 --- a/sys/configuration/include/configuration_backend_flashdb.h +++ b/sys/configuration/include/configuration_backend_flashdb.h @@ -23,7 +23,6 @@ #include "board.h" #include "modules.h" #include "mtd.h" -#include "vfs_default.h" #include "configuration.h" #if IS_USED(MODULE_FLASHDB_KVDB) #include "fal_cfg.h" @@ -76,27 +75,6 @@ mtd_dev_t *configuration_backend_flashdb_mtd_choose_dev(void); */ const char *configuration_backend_flashdb_mtd_choose_partition(void); -/** - * @brief __attribute__((weak)) function to select the MTD for FlashDB - * when the module configuration_backend_flashdb_vfs is used - * - * The default implementation is to return @ref MTD_0. - * - * @return MTD device to use in the VFS mode of FlashDB - */ -mtd_dev_t *configuration_backend_flashdb_vfs_choose_dev(void); - -/** - * @brief __attribute__((weak)) function to select the path for FlashDB - * when the module configuration_backend_flashdb_vfs is used - * - * The default implementation is to return VFS_DEFAULT_DATA"/"CONFIGURATION_FLASHDB_VFS_FOLDER. - * @ref CONFIGURATION_FLASHDB_VFS_FOLDER - * - * @return Path to use in the VFS mode of FlashDB - */ -const char *configuration_backend_flashdb_vfs_choose_path(void); - /** * @brief Reset the FlashDB backend, which deletes all configuration data * From b7f9ac4dcd0d45a120c55774edc1fe861fc2bfc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20H=C3=BC=C3=9Fler?= Date: Wed, 24 Apr 2024 21:03:50 +0200 Subject: [PATCH 08/12] tests/sys/configuration: drop configuration_backend_flashdb_vfs --- tests/sys/configuration/Makefile | 15 +-------------- tests/sys/configuration/main.c | 3 +-- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/tests/sys/configuration/Makefile b/tests/sys/configuration/Makefile index f6f1a558f17c..96fb924e03e9 100644 --- a/tests/sys/configuration/Makefile +++ b/tests/sys/configuration/Makefile @@ -4,8 +4,7 @@ TEST_CONFIGURATION_BACKEND ?= configuration_backend_ram TEST_CONFIGURATION_SUPPORTED_BACKENDS ?= \ configuration_backend_ram \ - configuration_backend_flashdb_mtd \ - configuration_backend_flashdb_vfs + configuration_backend_flashdb_mtd ifeq (,$(filter $(TEST_CONFIGURATION_BACKEND),$(TEST_CONFIGURATION_SUPPORTED_BACKENDS))) $(error "Supported backends are: $(TEST_CONFIGURATION_SUPPORTED_BACKENDS)") @@ -24,18 +23,6 @@ ifeq ($(TEST_CONFIGURATION_BACKEND),configuration_backend_ram) NO_PSEUDOMODULES += configuration_backend_ram endif -# configuration_backend_flashdb_vfs results in hard faults with little stack size -CFLAGS += -DTHREAD_STACKSIZE_MAIN=2*THREAD_STACKSIZE_LARGE - -# for configuration_backend_flashdb_vfs you may need to add: -# CFLAGS+="-DDEBUG_ASSERT_VERBOSE=1 -# -DFAL_MTD=MTD_0 -# -DVFS_DEFAULT_DATA=VFS_DEFAULT_NVM\(0\)" -# TEST_CONFIGURATION_BACKEND=configuration_backend_flashdb_vfs -# USEMODULE="vfs_aut_format -# configuration_backend_reset_flashdb" -# BOARD=same54-xpro make flash term - USEMODULE += embunit USEMODULE += configuration USEMODULE += $(TEST_CONFIGURATION_BACKEND) diff --git a/tests/sys/configuration/main.c b/tests/sys/configuration/main.c index 1ab06ec77d39..9c8b543dbf36 100644 --- a/tests/sys/configuration/main.c +++ b/tests/sys/configuration/main.c @@ -473,8 +473,7 @@ static CONF_PRIMITIVE_HANDLER(_products_orders_items_item_conf_handler, #ifndef TEST_CONFIGURATION_BACKEND_OPS #if defined(TEST_CONFIGURATION_BACKEND_RAM) #define TEST_CONFIGURATION_BACKEND_OPS &conf_backend_ram_ops -#elif defined(TEST_CONFIGURATION_BACKEND_FLASHDB_MTD) || \ - defined(TEST_CONFIGURATION_BACKEND_FLASHDB_VFS) +#elif defined(TEST_CONFIGURATION_BACKEND_FLASHDB_MTD) #define TEST_CONFIGURATION_BACKEND_OPS &conf_backend_flashdb_ops #else #define TEST_CONFIGURATION_BACKEND_OPS NULL From 5bac862683bb8afc41dbdb33911ee77b29f70323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20H=C3=BC=C3=9Fler?= Date: Tue, 30 Apr 2024 22:18:16 +0200 Subject: [PATCH 09/12] sys/configuration: add subkey module --- sys/configuration/Makefile.dep | 4 +++ sys/configuration/Makefile.include | 3 ++ sys/configuration/configuration.c | 43 +++++++++++++++++++++++++++- sys/configuration/default_handlers.c | 17 +++++++++-- sys/include/configuration.h | 9 ++++++ 5 files changed, 72 insertions(+), 4 deletions(-) diff --git a/sys/configuration/Makefile.dep b/sys/configuration/Makefile.dep index 105eb71d975e..2cc7c22321aa 100644 --- a/sys/configuration/Makefile.dep +++ b/sys/configuration/Makefile.dep @@ -36,3 +36,7 @@ ifneq (,$(filter configuration_delta_encoding_decoding,$(USEMODULE))) USEMODULE += configuration_delta_encoding USEMODULE += configuration_delta_decoding endif + +ifneq (,$(filter configuration_key_subkey,$(USEMODULE))) + USEMODULE += configuration_handler_parent +endif diff --git a/sys/configuration/Makefile.include b/sys/configuration/Makefile.include index 29001793c591..6955d36fa11a 100644 --- a/sys/configuration/Makefile.include +++ b/sys/configuration/Makefile.include @@ -24,3 +24,6 @@ PSEUDOMODULES += configuration_delta_decoding # alias for configuration_delta_encoding and configuration_delta_decoding PSEUDOMODULES += configuration_delta_encoding_decoding + +# be able to export sub items which don't have a backend configured +PSEUDOMODULES += configuration_key_subkey diff --git a/sys/configuration/configuration.c b/sys/configuration/configuration.c index 70a37e64cc8b..b3dcfebe8b43 100644 --- a/sys/configuration/configuration.c +++ b/sys/configuration/configuration.c @@ -302,6 +302,9 @@ static int _configuration_prepare_sid(const conf_handler_t **next_handler, conf_ { key->offset = 0; key->sid_normal = key->sid; +#if IS_USED(MODULE_CONFIGURATION_KEY_SUBKEY) + key->subkey = NULL; +#endif #if IS_USED(MODULE_CONFIGURATION_STRINGS) if (key_buf) { memset(key_buf, 0, key_buf_len); @@ -616,12 +619,40 @@ static int _configuration_handler_export_internal(const conf_handler_t *root, if (_configuration_prepare_sid(&root, key, _KEY_BUF(key), _KEY_BUF_LEN(key)) < 0) { return -ENOENT; } + const conf_backend_t *be; +#if IS_USED(MODULE_CONFIGURATION_KEY_SUBKEY) + /* if export node has no backend, find first parent node which has a backend */ + conf_key_range_t subkey = { .sid_lower = key->sid, .sid_upper = key->sid }; + if (root->conf_flags.handles_array) { + if (key->sid == root->array_id->sid_lower) { + subkey.sid_upper += (root->array_id->sid_upper - root->array_id->sid_lower); + } + else { + subkey.sid_upper = key->sid + root->array_id->sid_stride - 1; + } + } + else if (!root->conf_flags.handles_primitive) { + subkey.sid_upper += (root->node_id->sid_upper - root->node_id->sid_lower); + } + be = *configuration_get_dst_backend(root); + while ((!be || !be->ops || !be->ops->be_store) && root && root->parent) { + be = *configuration_get_dst_backend((root = root->parent)); + } + if (!root || !be || !be->ops || !be->ops->be_store) { + return -ENOTSUP; + } + else { + key->sid = root->node_id->sid_lower; + _configuration_prepare_sid(&root, key, _KEY_BUF(key), _KEY_BUF_LEN(key)); + key->subkey = &subkey; + } +#endif int ret = 0; conf_path_iterator_t iter; conf_iterator_restore_t restore = _configuration_path_iterator_init(&iter, root, key, _KEY_BUF(key)); conf_handler_t *handler; while ((handler = _configuration_path_sid_iterator_next(&iter, key, &restore.sid))) { - const conf_backend_t *be = *configuration_get_dst_backend(handler); + be = *configuration_get_dst_backend(handler); if (!be || !be->ops || !be->ops->be_store) { continue; } @@ -874,6 +905,16 @@ int configuration_encode_internal(conf_path_iterator_t *iter, conf_iterator_rest } conf_handler_t *handler; while ((handler = _configuration_handler_encode_iterator_next(iter, key, &restore->sid))) { +#if IS_USED(MODULE_CONFIGURATION_KEY_SUBKEY) + if (key->subkey) { + if (key->sid == key->subkey->sid_lower) { + return -ENOBUFS; /* flush encoding buffer */ + } + if (key->sid > key->subkey->sid_upper) { + return 0; + } + } +#endif const conf_backend_t *be = *configuration_get_dst_backend(iter->root); /* do not export a subnode which has an own backend set because this would export the same data to two backends */ diff --git a/sys/configuration/default_handlers.c b/sys/configuration/default_handlers.c index 95e845ad4737..c1b6e3979e0e 100644 --- a/sys/configuration/default_handlers.c +++ b/sys/configuration/default_handlers.c @@ -189,6 +189,15 @@ int configuration_import_handler_default(const conf_handler_t *handler, return 0; } +static bool _check_subkey(const conf_key_buf_t *key) +{ + (void)key; +#if IS_USED(MODULE_CONFIGURATION_KEY_SUBKEY) + return !key->subkey || key->sid > key->subkey->sid_lower; +#endif + return true; +} + int configuration_export_handler_default(const conf_handler_t *handler, conf_key_buf_t *key) { @@ -220,9 +229,11 @@ int configuration_export_handler_default(const conf_handler_t *handler, if (err == -ENOBUFS) { /* need to flush */ sz = sizeof(_enc_buffer) - enc_size; - if ((err = be->ops->be_store(be, key, enc_data, &sz, total, &flg))) { - DEBUG("configuration: backend exporting key %s failed (%d)\n", - configuration_key_buf(key), err); + if (_check_subkey(&enc_key)) { + if ((err = be->ops->be_store(be, key, enc_data, &sz, total, &flg))) { + DEBUG("configuration: backend exporting key %s failed (%d)\n", + configuration_key_buf(key), err); + } } total += sz; enc_data = _enc_buffer; diff --git a/sys/include/configuration.h b/sys/include/configuration.h index c22a1500d2dc..3e3276367093 100644 --- a/sys/include/configuration.h +++ b/sys/include/configuration.h @@ -344,6 +344,14 @@ struct { \ unsigned offset; \ } +/** + * @brief A range of keys, used to select a subkey range to export + */ +typedef struct { + conf_sid_t sid_lower; /**< Lower SID of the range */ + conf_sid_t sid_upper; /**< Upper SID of the range */ +} conf_key_range_t; + /** * @brief Key buffer type with a static maximum key length * @@ -352,6 +360,7 @@ struct { \ #define CONF_KEY_T(len) \ struct { \ _CONF_KEY_BASE_T; \ + conf_key_range_t *subkey; \ _CONF_KEY_BUF_LEN \ _CONF_KEY_BUF(len) \ } From 01d7281aac19a068016d0bc9c94ac2e31d6aca14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20H=C3=BC=C3=9Fler?= Date: Wed, 28 Feb 2024 15:56:05 +0100 Subject: [PATCH 10/12] sys: add riotconf 2 slot configuration module --- sys/Makefile.include | 4 + sys/include/riotconf/hdr.h | 138 ++++++++++++++++ sys/include/riotconf/slot.h | 181 +++++++++++++++++++++ sys/include/riotconf/storage.h | 156 ++++++++++++++++++ sys/riotconf/Makefile | 1 + sys/riotconf/Makefile.dep | 3 + sys/riotconf/Makefile.include | 1 + sys/riotconf/hdr.c | 77 +++++++++ sys/riotconf/slot.c | 283 +++++++++++++++++++++++++++++++++ sys/riotconf/storage.c | 107 +++++++++++++ 10 files changed, 951 insertions(+) create mode 100644 sys/include/riotconf/hdr.h create mode 100644 sys/include/riotconf/slot.h create mode 100644 sys/include/riotconf/storage.h create mode 100644 sys/riotconf/Makefile create mode 100644 sys/riotconf/Makefile.dep create mode 100644 sys/riotconf/Makefile.include create mode 100644 sys/riotconf/hdr.c create mode 100644 sys/riotconf/slot.c create mode 100644 sys/riotconf/storage.c diff --git a/sys/Makefile.include b/sys/Makefile.include index 3dffd0b48e0f..878a2067d237 100644 --- a/sys/Makefile.include +++ b/sys/Makefile.include @@ -117,6 +117,10 @@ ifneq (,$(filter riotboot,$(FEATURES_USED))) include $(RIOTBASE)/sys/riotboot/Makefile.include endif +ifneq (,$(filter riotconf,$(USEMODULE))) + include $(RIOTBASE)/sys/riotconf/Makefile.include +endif + ifneq (,$(filter skald, $(USEMODULE))) include $(RIOTBASE)/sys/net/ble/skald/Makefile.include endif diff --git a/sys/include/riotconf/hdr.h b/sys/include/riotconf/hdr.h new file mode 100644 index 000000000000..63ffda3d8871 --- /dev/null +++ b/sys/include/riotconf/hdr.h @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2024 ML!PA Consulting GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup sys_riotconf_hdr RIOT configuration header API + * @ingroup sys + * @{ + * + * @file + * @brief RIOT configuration partition header + * + * @author Fabian Hüßler + * + * @} + */ + +#ifndef RIOTCONF_HDR_H +#define RIOTCONF_HDR_H + +#include +#include + +#include "checksum/fletcher32.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Magic number for riotconf_header "c0nf" + */ +#define RIOTCONF_MAGIC 0x63306e66 + +/** + * @brief RIOT configuration status flags typedef for extensibility + */ +typedef uint32_t riotconf_hdr_state_t; + +/** + * @brief RIOT configuration slot typedef for extensibility + */ +typedef int riotconf_slot_t; + +/** + * @brief RIOT configuration status flags + */ +enum { + /** + * @brief Configuration checksum has been validated + */ + RIOTCONF_HDR_STATE_CHECKSUM_VALIDATED = 0x01u, + /** + * @brief Configuration values have been verified + */ + RIOTCONF_HDR_STATE_CONFIGURATION_VERIFIED = 0x02u, +}; + +/** + * @brief RIOT configuration header + */ +typedef struct riotconf_hdr { + uint32_t magic; /**< magic number for fast rejection */ + uint32_t sequence; /**< anti rollback counter */ + uint32_t version; /**< version to check compatibility */ + uint32_t size; /**< data size */ + uint32_t checksum; /**< checksum computed over header and data */ + riotconf_hdr_state_t state; /**< state flags */ +} riotconf_hdr_t; + +/** + * @brief RIOT configuration header checksum context + */ +typedef struct riotconf_hdr_checksum_ctx { + fletcher32_ctx_t ctx; /**< internal fletcher context */ +} riotconf_hdr_checksum_ctx_t; + +/** + * @brief Initialize a checksum context and prepare a riotconf header for calculation calculation + * + * @param[out] ctx Checksum context to initialize + * @param[in, out] hdr Header to prepare for checksum calculation + */ +void riotconf_hdr_checksum_init(riotconf_hdr_checksum_ctx_t *ctx, riotconf_hdr_t *hdr); + +/** + * @brief Update a checksum context with data + * + * @pre riotconf_hdr_checksum_init() has been called on @p ctx + * + * @param[in, out] ctx Checksum context to update + * @param[in] data Data to update the checksum with + * @param[in] size Size of the data + */ +void riotconf_hdr_checksum_update(riotconf_hdr_checksum_ctx_t *ctx, void *data, size_t size); + +/** + * @brief Finalize a checksum context and write the checksum to the header + * + * @pre riotconf_hdr_checksum_init() has been called on @p ctx + * + * @param[in, out] ctx Checksum context to finalize + * @param[in, out] hdr Header to write the checksum to + */ +void riotconf_hdr_checksum_final(riotconf_hdr_checksum_ctx_t *ctx, riotconf_hdr_t *hdr); + +/** + * @brief Validate a RIOT configuration header + * + * @param[in] hdr Header to validate + * + * @return 0 on success + * @return -EINVAL on magic number mismatch + */ +int riotconf_hdr_validate(riotconf_hdr_t *hdr); + +/** + * @brief Convert a RIOT configuration header from host to network byte order + * + * @param[in,out] hdr Header to convert + */ +void riotconf_hdr_hton(riotconf_hdr_t *hdr); + +/** + * @brief Convert a RIOT configuration header from network to host byte order + * + * @param[in,out] hdr Header to convert + */ +void riotconf_hdr_ntoh(riotconf_hdr_t *hdr); + +#ifdef __cplusplus +} +#endif +#endif /* RIOTCONF_HDR_H */ diff --git a/sys/include/riotconf/slot.h b/sys/include/riotconf/slot.h new file mode 100644 index 000000000000..ac33d8523e71 --- /dev/null +++ b/sys/include/riotconf/slot.h @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2024 ML!PA Consulting GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup sys_riotconf_slot Helpers to manipulate RIOT configuration partitions + * @ingroup sys + * @{ + * + * @file + * @brief RIOT configuration partition management + * + * @author Fabian Hüßler + * + * @} + */ + +#ifndef RIOTCONF_SLOT_H +#define RIOTCONF_SLOT_H + +#include +#include "riotconf/hdr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef RIOTCONF_SLOT_NUMOF +/** + * @brief Number of configuration slots + */ +#define RIOTCONF_SLOT_NUMOF 2 +#endif + +/** + * @brief A user supplied callback to evaluate the suitability of a valid configuration slot + */ +typedef int (*riotconf_slot_compat_cb_t) (const riotconf_hdr_t *hdr); + +/** + * @brief Get the configuration slot with the highest sequence number + * + * @param[in] compatible Callback to evaluate the suitability of a valid configuration slot + * - for example to check for a required version - + * + * @return The configuration slot with the highest sequence number + */ +riotconf_slot_t riotconf_slot_highest_seq(riotconf_slot_compat_cb_t compatible); + +/** + * @brief Get an updatable configuration slot which is either invalid or has a lowest sequence number + * but is not the current slot + * + * @param[in] current The current configuration slot + * + * @return The configuration slot of the slot to update + */ +riotconf_slot_t riotconf_slot_other(riotconf_slot_t current); + +/** + * @brief The application can mark a valid slot with any encoded state + * + * The @ref RIOTCONF_HDR_STATE_CHECKSUM_VALIDATED flag is reserved internally. + * The @ref RIOTCONF_HDR_STATE_CONFIGURATION_VERIFIED may be set if the configuration + * has been verified for correctness. + * + * @param[in] slot Configuration slot + * @param[in] state State to encode + * + * @return The state of the slot + */ +int riotconf_slot_set_state(riotconf_slot_t slot, uint32_t state); + +/** + * @brief Read configuration data from a slot + * + * @param[in] slot Configuration slot + * @param[in] data Buffer to read data into + * @param[in] offset Offset in data to read from + * @param[in] size Number of bytes to read + * + * @return 0 on success + * @return <0 on error + */ +int riotconf_slot_read(riotconf_slot_t slot, void *data, size_t offset, size_t size); + +/** + * @brief Open the storage device for reading configuration data + * + * @post The returned buffer @p buf contains the configuration header + * but it may not contain valid data yet. + * + * @param[in] slot Configuration slot + * @param[out] buf Reurns a pointer to an internal sector buffer + to relieve the caller from allocating memory + * @param[out] size Size of the returned buffer + * + * @return 0 on success + * @return <0 on error + */ +int riotconf_slot_start_read(riotconf_slot_t slot, void **buf, size_t *size); + +/** + * @brief Finish reading configuration data + * + * @param[in] slot Configuration slot + */ +void riotconf_slot_finish_read(riotconf_slot_t slot); + +/** + * @brief Start writing configuration data + * + * The first sector containing the header is saved before erase. + * + * @note If the output header has a valid magic number, you may assume that also version and sequence number are valid. + * + * @warning The configuration on the device is invalidated to not have valid but partially written data + * + * @param[in] slot Configuration slot + * @param[out] hdr If not NUll, it will contain the header of the current slot which was erased + * + * @return 0 on success + * @return <0 on error + */ +int riotconf_slot_start_write(riotconf_slot_t slot, riotconf_hdr_t *hdr); + +/** + * @brief Write configuration data to a slot + * + * @param[in] slot Configuration slot + * @param[in] data Buffer to write data from + * @param[in] offset Offset in data to write to + * @param[in] size Number of bytes to write + * + * @return 0 on success + * @return <0 on error + */ +int riotconf_slot_write(riotconf_slot_t slot, const void *data, size_t offset, size_t size); + +/** + * @brief Finish writing configuration data + * + * This function writes a new header with provided values and calculates the checksum. + * + * @param[in] slot Configuration slot + * @param[in] seq Sequence number to write to the header + * @param[in] version Version number to write to the header + * @param[in] size Size of the configuration data + */ +void riotconf_slot_finish_write(riotconf_slot_t slot, uint32_t seq, uint32_t version, size_t size); + +/** + * @brief Validate a configuration slot, that means the header contains the magic number + * and the checksum is correct + * + * @param[in] slot Configuration slot + * @param[out] hdr Header that has been written to the slot + * + * @return 0 on success + * @return <0 on error + */ +int riotconf_slot_validate(riotconf_slot_t slot, riotconf_hdr_t *hdr); + +/** + * @brief Invalidate a configuration slot + * + * @param[in] slot Configuration slot + * + * @return 0 on success + * @return <0 on error + */ +int riotconf_slot_invalidate(riotconf_slot_t slot); + +#ifdef __cplusplus +} +#endif +#endif /* RIOTCONF_SLOT_H */ diff --git a/sys/include/riotconf/storage.h b/sys/include/riotconf/storage.h new file mode 100644 index 000000000000..0b25582fa623 --- /dev/null +++ b/sys/include/riotconf/storage.h @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2024 ML!PA Consulting GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup sys_riotconf_storage RIOT configuration storage API + * @ingroup sys + * @{ + * + * @file + * @brief RIOT configuration storage API + * + * @author Fabian Hüßler + * + * @} + */ + +#ifndef RIOTCONF_STORAGE_H +#define RIOTCONF_STORAGE_H + +#include +#include +#include + +#include "mtd.h" +#include "riotconf/hdr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Make sure that the sector buffer passed in @ref riotconf_storage_init() + * is aligned to the riotconf header + */ +#define RIOTCONF_BUF_ALIGN __attribute__((aligned(alignof(riotconf_hdr_t)))); + +/** + * @brief RIOT configuration storage + */ +typedef struct riotconf_storage { + mtd_dev_t *dev; /**< based on any MTD */ + uint32_t sector_offset; /**< sector of configuration header */ + void *sector_buf; /**< buffer for sector read/write */ + size_t sector_size; /**< size of sector (>= x * MTD sector size) */ +} riotconf_storage_t; + +/** + * @brief Get access to a storage device that stores the provided configuration slot + * + * @param[in] slot Configuration slot + * + * @return Pointer to storage device + */ +riotconf_storage_t *riotconf_storage_get(riotconf_slot_t slot); + +/** + * @brief Initialize a configuration slot with a storage device + * + * @pre The storage device @p dev must have been initialized with @ref mtd_init() + * @pre The size @p sector_size must align with the physical sector size of the MTD + * + * @param[in] slot Configuration slot + * @param[in] dev MTD device + * @param[in] sector_offset Number of sectors to skip on the MTD before the configuration header + * @param[in] sector_buf Buffer provided for read/write operations + * @param[in] sector_size Size of the provided buffer + */ +void riotconf_storage_init(riotconf_slot_t slot, mtd_dev_t *dev, uint32_t sector_offset, void *sector_buf, size_t sector_size); + +/** + * @brief Open the storage device for reading configuration + * + * @param[out] dev Storage device + * @param[out] buf Returns a pointer to an internal sector buffer + to relieve the caller from allocating memory + * @param[out] size Size of the returned buffer + */ +void riotconf_storage_start_read(riotconf_storage_t *dev, void **buf, size_t *size); + +/** + * @brief Read data from the storage device + * + * @param[in] dev Storage device + * @param[out] buf Buffer to store the read data + * which can be the one obtained from @ref riotconf_storage_start_read() + * @param[in] offset Offset in the storage device + * @param[in] size Number of bytes to read + * + * @return 0 on success + * @return <0 on error + */ +int riotconf_storage_read(riotconf_storage_t *dev, void *buf, size_t offset, size_t size); + +/** + * @brief Finish configuration reading from the storage device + * + * @param[in] dev Storage device + */ +void riotconf_storage_finish_read(riotconf_storage_t *dev); + +/** + * @brief Erase a sector on the storage device + * + * @param[in] dev Storage device + * @param[in] sector Start sector to erase + * @param[in] count Number of sectors to erase + * + * @return 0 on success + * @return <0 on error + */ +int riotconf_storage_erase_sector(riotconf_storage_t *dev, uint32_t sector, uint32_t count); + +/** + * @brief Open the storage device for writing configuration + * + * The first sector containing the header is saved before erase. + * + * @warning The configuration on the device is invalidated to not have valid but partially written data + * + * @param[in] dev Storage device + * + * @return 0 on success + * @return <0 on error + */ +int riotconf_storage_start_write(riotconf_storage_t *dev); + +/** + * @brief Write to the storage device + * + * @param[in] dev Storage device + * @param[in] data Data to write + * @param[in] offset Offset in the storage device + * @param[in] size Number of bytes to write + * + * @return 0 on success + * @return <0 on error + */ +int riotconf_storage_write(riotconf_storage_t *dev, const void *data, size_t offset, size_t size); + +/** + * @brief Finish configuration writing to the storage device + * + * @param dev Storage device + * @param hdr Configuration header to be written to the device + */ +void riotconf_storage_finish_write(riotconf_storage_t *dev, const riotconf_hdr_t *hdr); + +#ifdef __cplusplus +} +#endif +#endif /* RIOTCONF_STORAGE_H */ diff --git a/sys/riotconf/Makefile b/sys/riotconf/Makefile new file mode 100644 index 000000000000..48422e909a47 --- /dev/null +++ b/sys/riotconf/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/riotconf/Makefile.dep b/sys/riotconf/Makefile.dep new file mode 100644 index 000000000000..449c08e24eab --- /dev/null +++ b/sys/riotconf/Makefile.dep @@ -0,0 +1,3 @@ +USEMODULE += checksum +USEMODULE += mtd_write_page +USEMODULE += od diff --git a/sys/riotconf/Makefile.include b/sys/riotconf/Makefile.include new file mode 100644 index 000000000000..7de815a57f76 --- /dev/null +++ b/sys/riotconf/Makefile.include @@ -0,0 +1 @@ +PSEUDOMODULES += riotconf_slot_print diff --git a/sys/riotconf/hdr.c b/sys/riotconf/hdr.c new file mode 100644 index 000000000000..575ec709b4c9 --- /dev/null +++ b/sys/riotconf/hdr.c @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 ML!PA Consulting GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_riotconf_hdr + * @{ + * + * @file + * @brief RIOT configuration header + * + * @author Fabian Hüßler + * + * @} + */ + +#include +#include +#include + +#include "byteorder.h" +#include "checksum/fletcher32.h" +#include "log.h" +#include "riotconf/hdr.h" + +void riotconf_hdr_checksum_init(riotconf_hdr_checksum_ctx_t *ctx, riotconf_hdr_t *hdr) +{ + hdr->magic = htonl(RIOTCONF_MAGIC); + hdr->checksum = 0; + hdr->state = 0; + fletcher32_init(&ctx->ctx); +} + +void riotconf_hdr_checksum_update(riotconf_hdr_checksum_ctx_t *ctx, void *data, size_t size) +{ + /* pad with zero for the last update */ + memset(((uint8_t *)data) + size, 0, size % sizeof(uint16_t)); + fletcher32_update(&ctx->ctx, data, size + (size % sizeof(uint16_t))); +} + +void riotconf_hdr_checksum_final(riotconf_hdr_checksum_ctx_t *ctx, riotconf_hdr_t *hdr) +{ + hdr->checksum = fletcher32_finish(&ctx->ctx); +} + +int riotconf_hdr_validate(riotconf_hdr_t *hdr) +{ + if (hdr->magic != RIOTCONF_MAGIC) { + LOG_DEBUG("%s: riotconf_hdr magic number invalid\n", __func__); + return -EINVAL; + } + return 0; +} + +void riotconf_hdr_hton(riotconf_hdr_t *hdr) +{ + hdr->magic = htonl(hdr->magic); + hdr->sequence = htonl(hdr->sequence); + hdr->version = htonl(hdr->version); + hdr->size = htonl(hdr->size); + hdr->checksum = htonl(hdr->checksum); + hdr->state = htonl(hdr->state); +} + +void riotconf_hdr_ntoh(riotconf_hdr_t *hdr) +{ + hdr->magic = ntohl(hdr->magic); + hdr->sequence = ntohl(hdr->sequence); + hdr->version = ntohl(hdr->version); + hdr->size = ntohl(hdr->size); + hdr->checksum = ntohl(hdr->checksum); + hdr->state = ntohl(hdr->state); +} diff --git a/sys/riotconf/slot.c b/sys/riotconf/slot.c new file mode 100644 index 000000000000..21f71cd20246 --- /dev/null +++ b/sys/riotconf/slot.c @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2024 ML!PA Consulting GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_riotconf_slot + * @{ + * + * @file + * @brief RIOT configuration slot API + * + * @author Fabian Hüßler + * + * @} + */ + +#include +#include +#include +#include + +#include "byteorder.h" +#include "log.h" +#include "macros/utils.h" +#include "riotconf/hdr.h" +#include "riotconf/slot.h" +#include "riotconf/storage.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +riotconf_slot_t riotconf_slot_highest_seq(riotconf_slot_compat_cb_t compatible) +{ + riotconf_slot_t slot = -1; + uint32_t highest = 0; + riotconf_hdr_t hdr; + for (riotconf_slot_t i = 0; i < RIOTCONF_SLOT_NUMOF; i++) { + if (riotconf_slot_validate(i, &hdr) < 0) { + continue; + } + if (hdr.sequence >= highest) { + if (compatible && !compatible(&hdr)) { + continue; + } + highest = hdr.sequence; + slot = i; + } + } + return slot; +} + +riotconf_slot_t riotconf_slot_other(riotconf_slot_t current) +{ + riotconf_slot_t slot = -1; + uint32_t lowest = UINT32_MAX; + riotconf_hdr_t hdr; + for (riotconf_slot_t i = 0; i < RIOTCONF_SLOT_NUMOF; i++) { + if (i == current) { + continue; + } + if (!riotconf_slot_validate(i, &hdr)) { + slot = i; + break; /* any invalid slot may be overridden for an update */ + } + else if (hdr.sequence <= lowest) { + slot = i; + } + } + return slot; +} + +int riotconf_slot_set_state(riotconf_slot_t slot, uint32_t state) +{ + riotconf_storage_t *dev = riotconf_storage_get(slot); + if (!dev) { + return -ENODEV; + } + riotconf_hdr_t hdr; + if (riotconf_slot_validate(slot, &hdr)) { + return -EINVAL; + } + /* the validation flag is reserved for riotconf */ + state &= ~((uint32_t)RIOTCONF_HDR_STATE_CHECKSUM_VALIDATED); + hdr.state = state | (hdr.state & RIOTCONF_HDR_STATE_CHECKSUM_VALIDATED); + if (riotconf_storage_start_write(dev)) { + return -EIO; + } + riotconf_hdr_hton(&hdr); + riotconf_storage_finish_write(dev, &hdr); + return 0; +} + +int riotconf_slot_read(riotconf_slot_t slot, void *data, size_t offset, size_t size) +{ + riotconf_storage_t *dev = riotconf_storage_get(slot); + if (!dev) { + return -ENODEV; + } + offset += sizeof(riotconf_hdr_t); + return riotconf_storage_read(dev, data, offset, size); +} + +int riotconf_slot_start_read(riotconf_slot_t slot, void **buf, size_t *size) +{ + riotconf_storage_t *dev = riotconf_storage_get(slot); + if (!dev) { + return -ENODEV; + } + riotconf_storage_start_read(dev, buf, size); + riotconf_storage_read(dev, *buf, 0, sizeof(riotconf_hdr_t)); + return 0; +} + +void riotconf_slot_finish_read(riotconf_slot_t slot) +{ + riotconf_storage_t *dev = riotconf_storage_get(slot); + if (!dev) { + return; + } + riotconf_storage_finish_read(dev); +} + +int riotconf_slot_start_write(riotconf_slot_t slot, riotconf_hdr_t *hdr) +{ + riotconf_storage_t *dev = riotconf_storage_get(slot); + if (!dev) { + return -ENODEV; + } + int ret = riotconf_storage_start_write(dev); + if (hdr) { + memcpy(hdr, dev->sector_buf, sizeof(*hdr)); + riotconf_hdr_ntoh(hdr); + } + return ret; +} + +int riotconf_slot_write(riotconf_slot_t slot, const void *data, size_t offset, size_t size) +{ + riotconf_storage_t *dev = riotconf_storage_get(slot); + if (!dev) { + return -ENODEV; + } + offset += sizeof(riotconf_hdr_t); + return riotconf_storage_write(dev, data, offset, size); +} + +static void _riotconf_checksum(riotconf_storage_t *dev, void *sec_buf, size_t sec_size, riotconf_hdr_t *hdr) +{ + riotconf_hdr_t *h = (riotconf_hdr_t *)sec_buf; + riotconf_hdr_checksum_ctx_t chk; + riotconf_hdr_checksum_init(&chk, h); + riotconf_hdr_state_t state_backup = hdr->state; + memcpy(hdr, h, sizeof(*hdr)); + size_t total = sizeof(*h) + ntohl(h->size); + size_t size = MIN(total, sec_size); + riotconf_hdr_checksum_update(&chk, sec_buf, size); + total -= size; + for (unsigned i = 1; total > 0; i++) { + size = MIN(total, sec_size); + riotconf_storage_read(dev, sec_buf, i * sec_size, size); + riotconf_hdr_checksum_update(&chk, sec_buf, size); + total -= size; + } + riotconf_hdr_checksum_final(&chk, hdr); + riotconf_hdr_ntoh(hdr); + hdr->state = state_backup; +} + +void riotconf_slot_finish_write(riotconf_slot_t slot, uint32_t seq, uint32_t version, size_t size) +{ + riotconf_storage_t *dev = riotconf_storage_get(slot); + if (!dev) { + return; + } + riotconf_hdr_t hdr = { + .magic = 0, + .sequence = seq, + .version = version, + .size = size, + .state = 0 + }; + riotconf_hdr_hton(&hdr); + riotconf_storage_finish_write(dev, &hdr); + + void *sector; + size_t sector_size; + riotconf_storage_start_read(dev, §or, §or_size); + riotconf_storage_read(dev, sector, 0, sector_size); + memcpy(sector, &hdr, sizeof(hdr)); + _riotconf_checksum(dev, sector, sector_size, &hdr); + riotconf_storage_finish_read(dev); + + assert(hdr.sequence == seq); + assert(hdr.version == version); + assert(hdr.size == size); + riotconf_hdr_hton(&hdr); + riotconf_storage_start_write(dev); + riotconf_storage_finish_write(dev, &hdr); +} + +int riotconf_slot_validate(riotconf_slot_t slot, riotconf_hdr_t *hdr) +{ + riotconf_storage_t *dev = riotconf_storage_get(slot); + if (!dev) { + return -ENODEV; + } + void *sector; + size_t sector_size; + riotconf_storage_start_read(dev, §or, §or_size); + if (riotconf_storage_read(dev, sector, 0, sector_size)) { + return -EIO; + } + riotconf_hdr_t *h = (riotconf_hdr_t *)sector; + memcpy(hdr, h, sizeof(*hdr)); + riotconf_hdr_ntoh(hdr); + if (riotconf_hdr_validate(hdr)) { + return -EINVAL; + } + uint32_t checksum = hdr->checksum; + _riotconf_checksum(dev, sector, sector_size, hdr); + riotconf_storage_finish_read(dev); + if (checksum != hdr->checksum) { + LOG_DEBUG("%s: riotconf_slot checksum invalid\n", __func__); + return -EINVAL; + } + /* technically this write could fail making it invalid */ + if (!(hdr->state & RIOTCONF_HDR_STATE_CHECKSUM_VALIDATED)) { + hdr->state |= RIOTCONF_HDR_STATE_CHECKSUM_VALIDATED; + riotconf_hdr_hton(hdr); + riotconf_storage_start_write(dev); + riotconf_storage_finish_write(dev, hdr); + riotconf_hdr_ntoh(hdr); + } + return 0; +} + +int riotconf_slot_invalidate(riotconf_slot_t slot) +{ + riotconf_storage_t *dev = riotconf_storage_get(slot); + if (!dev) { + return -ENODEV; + } + if (riotconf_storage_start_write(dev)) { + return -EIO; + } + riotconf_hdr_t hdr = { 0 }; + riotconf_storage_finish_write(dev, &hdr); + return 0; +} + +#if IS_USED(MODULE_RIOTCONF_SLOT_PRINT) +#include "od.h" +void riotconf_slot_print(riotconf_slot_t slot) +{ + void *sec; + size_t sec_size; + int ret = riotconf_slot_start_read(slot, &sec, &sec_size); + if (ret) { + DEBUG("Cannot read slot %u\n", slot); + return; + } + riotconf_hdr_t h = *(riotconf_hdr_t *)sec; + riotconf_hdr_ntoh(&h); + if (h.magic != RIOTCONF_MAGIC) { + riotconf_slot_finish_read(slot); + DEBUG("Slot %u is not valid\n", slot); + return; + } + printf("Slot: %d, seq: %"PRIu32", version: %"PRIu32", size %"PRIu32"\n", + slot, h.sequence, h.version, h.size); + size_t off = 0; + while (off < h.size) { + riotconf_slot_read(slot, sec, off, sec_size); + od_hex_dump(sec, MIN(sec_size, h.size - off), OD_WIDTH_DEFAULT); + off += sec_size; + } + riotconf_slot_finish_read(slot); +} +#endif diff --git a/sys/riotconf/storage.c b/sys/riotconf/storage.c new file mode 100644 index 000000000000..4f26c40a90c8 --- /dev/null +++ b/sys/riotconf/storage.c @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2024 ML!PA Consulting GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_riotconf_storage + * @{ + * + * @file + * @brief RIOT configuration storage + * + * @author Fabian Hüßler + * + * @} + */ + +#include +#include +#include + +#include "macros/utils.h" +#include "mtd.h" +#include "riotconf/slot.h" +#include "riotconf/storage.h" + +riotconf_storage_t _riotconf_slots[RIOTCONF_SLOT_NUMOF]; + +riotconf_storage_t *riotconf_storage_get(riotconf_slot_t slot) +{ + assert(slot < RIOTCONF_SLOT_NUMOF); + return &_riotconf_slots[slot]; +} + +void riotconf_storage_init(riotconf_slot_t slot, mtd_dev_t *dev, uint32_t sector_offset, void *sector_buf, size_t sector_size) +{ + assert(slot < RIOTCONF_SLOT_NUMOF); + assert(dev); + assert(sector_buf); + assert(sector_size); + assert(sector_size >= dev->page_size * dev->pages_per_sector); + assert(sector_size % (dev->page_size * dev->pages_per_sector) == 0); + riotconf_storage_t storage = { + .dev = dev, + .sector_offset = sector_offset, + .sector_buf = sector_buf, + .sector_size = sector_size + }; + _riotconf_slots[slot] = storage; +} + +void riotconf_storage_start_read(riotconf_storage_t *dev, void **buf, size_t *size) +{ + *buf = dev->sector_buf; + *size = dev->sector_size; +} + +int riotconf_storage_read(riotconf_storage_t *dev, void *buf, size_t offset, size_t size) +{ + uint32_t page = dev->sector_offset * dev->dev->pages_per_sector; + return mtd_read_page(dev->dev, buf, page, offset, size); +} + +void riotconf_storage_finish_read(riotconf_storage_t *dev) +{ + /* nothing to do */ + (void)dev; +} + +int riotconf_storage_erase_sector(riotconf_storage_t *dev, uint32_t sector, uint32_t count) +{ + uint32_t sec = dev->sector_offset + sector; + return mtd_erase_sector(dev->dev, sec, count); +} + +int riotconf_storage_start_write(riotconf_storage_t *dev) +{ + int rd = riotconf_storage_read(dev, dev->sector_buf, 0, dev->sector_size); + if (rd < 0) { + return rd; + } + return riotconf_storage_erase_sector(dev, 0, 1); +} + +int riotconf_storage_write(riotconf_storage_t *dev, const void *data, size_t offset, size_t size) +{ + if (offset < dev->sector_size) { + size_t l = MIN(size, dev->sector_size - offset); + memcpy(((uint8_t *)dev->sector_buf) + offset, data, l); + size -= l; + data = (const uint8_t *)data + l; + } + uint32_t page = dev->sector_offset * dev->dev->pages_per_sector; + return mtd_write_page(dev->dev, data, page, offset, size); +} + +void riotconf_storage_finish_write(riotconf_storage_t *dev, const riotconf_hdr_t *hdr) +{ + riotconf_hdr_t *h = (riotconf_hdr_t *)dev->sector_buf; + memcpy(h, hdr, sizeof(*hdr)); + uint32_t page = dev->sector_offset * dev->dev->pages_per_sector; + /* sector has been erased before */ + mtd_write_page_raw(dev->dev, dev->sector_buf, page, 0, dev->sector_size); +} From 798a988e4d9e21f4e9e500821b3ffca4e60d8ded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20H=C3=BC=C3=9Fler?= Date: Wed, 28 Feb 2024 15:57:34 +0100 Subject: [PATCH 11/12] tests: add riotconf test --- tests/riotconf/Makefile | 7 + tests/riotconf/main.c | 229 +++++++++++++++++++++++++++++++++ tests/riotconf/tests/01-run.py | 14 ++ 3 files changed, 250 insertions(+) create mode 100644 tests/riotconf/Makefile create mode 100644 tests/riotconf/main.c create mode 100755 tests/riotconf/tests/01-run.py diff --git a/tests/riotconf/Makefile b/tests/riotconf/Makefile new file mode 100644 index 000000000000..4d963e22e7ea --- /dev/null +++ b/tests/riotconf/Makefile @@ -0,0 +1,7 @@ +include ../Makefile.tests_common + +USEMODULE += embunit +USEMODULE += mtd_emulated +USEMODULE += riotconf + +include $(RIOTBASE)/Makefile.include diff --git a/tests/riotconf/main.c b/tests/riotconf/main.c new file mode 100644 index 000000000000..fe60dd146a8e --- /dev/null +++ b/tests/riotconf/main.c @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2024 ML!PA Consulting GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief riotconf test + * + * @author Fabian Hüßler + * + * @} + */ + +#include + +#include "container.h" +#include "embUnit.h" +#include "embUnit/AssertImpl.h" +#include "mtd.h" +#include "mtd_emulated.h" +#include "riotconf/hdr.h" +#include "riotconf/slot.h" +#include "riotconf/storage.h" + +#ifndef TEST_SECTOR_COUNT +#define TEST_SECTOR_COUNT 64 +#endif +#ifndef TEST_PAGES_PER_SECTOR +#define TEST_PAGES_PER_SECTOR 4 +#endif +#ifndef TEST_PAGE_SIZE +#define TEST_PAGE_SIZE 128 +#endif +#ifndef TEST_SECTOR_SIZE +#define TEST_SECTOR_SIZE (TEST_PAGES_PER_SECTOR * TEST_PAGE_SIZE) +#endif +#define TEST_MTD_SIZE (TEST_SECTOR_COUNT * TEST_PAGES_PER_SECTOR * TEST_PAGE_SIZE) + +static mtd_emulated_t _mtd[RIOTCONF_SLOT_NUMOF]; +static uint8_t _mtd_mem[RIOTCONF_SLOT_NUMOF][TEST_MTD_SIZE]; +static uint8_t _mtd_buf[RIOTCONF_SLOT_NUMOF][TEST_SECTOR_SIZE]; + +static void _setup(void) +{ + for (int i = 0; i < RIOTCONF_SLOT_NUMOF; i++) { + memset(_mtd_mem[i], 0, sizeof(_mtd_mem[i])); + _mtd[i] = (mtd_emulated_t) { + .base = { + .driver = &_mtd_emulated_driver, + .sector_count = TEST_SECTOR_COUNT, + .pages_per_sector = TEST_PAGES_PER_SECTOR, + .page_size = TEST_PAGE_SIZE, + .write_size = 1, + }, + .size = ARRAY_SIZE(_mtd_mem[i]), + .memory = _mtd_mem[i], + .init_done = false, + }; + mtd_init(&_mtd[i].base); + riotconf_storage_init(i, &_mtd[i].base, 0, &_mtd_buf[i], ARRAY_SIZE(_mtd_buf[i])); + } +} + +static void _test_riotconf_slot_invalid(void) +{ + int ret; + for (riotconf_slot_t i = 0; i < RIOTCONF_SLOT_NUMOF; i++) { + riotconf_hdr_t hdr; + ret = riotconf_slot_validate(i, &hdr); + TEST_ASSERT_MESSAGE(ret, "Empty slot should not be valid"); + } +} + +static void _test_riotconf_slot_invalid_checksum(void) +{ + char data[] = "Hello World!"; + riotconf_hdr_t hdr = { + .magic = RIOTCONF_MAGIC, + .checksum = 0x11223344, + .size = sizeof(data), + }; + riotconf_hdr_hton(&hdr); + mtd_write(&_mtd[0].base, &hdr, 0, sizeof(hdr)); + mtd_write(&_mtd[0].base, data, sizeof(hdr), sizeof(data)); + memset(&hdr, 0, sizeof(hdr)); + int ret = riotconf_slot_validate(0, &hdr); + TEST_ASSERT_MESSAGE(ret, "Checksum should be invalid"); +} + +static void _test_riotconf_slot_valid_checksum(void) +{ + char data[] = "Hello World!"; + riotconf_hdr_t hdr = { + .magic = RIOTCONF_MAGIC, + .checksum = 0x2393f289, + .size = sizeof(data), + }; + riotconf_hdr_hton(&hdr); + mtd_write(&_mtd[0].base, &hdr, 0, sizeof(hdr)); + mtd_write(&_mtd[0].base, data, sizeof(hdr), sizeof(data)); + memset(&hdr, 0, sizeof(hdr)); + int ret = riotconf_slot_validate(0, &hdr); + TEST_ASSERT_MESSAGE(!ret, "Checksum should be valid"); + TEST_ASSERT_EQUAL_INT(hdr.checksum, 0x2393f289); + TEST_ASSERT(hdr.state & RIOTCONF_HDR_STATE_CHECKSUM_VALIDATED); +} + +static void _test_riotconf_slot_read(void) +{ + char data[] = "Hello World!"; + riotconf_hdr_t hdr = { + .magic = RIOTCONF_MAGIC, + .checksum = 0x2393f289, + .size = sizeof(data), + }; + riotconf_hdr_hton(&hdr); + mtd_write(&_mtd[0].base, &hdr, 0, sizeof(hdr)); + mtd_write(&_mtd[0].base, data, sizeof(hdr), sizeof(data)); + memset(&hdr, 0, sizeof(hdr)); + + void *sec; + size_t sec_size; + int ret = riotconf_slot_start_read(0, &sec, &sec_size); + TEST_ASSERT(sec == &_mtd_buf[0]); + TEST_ASSERT_EQUAL_INT(sec_size, TEST_SECTOR_SIZE); + riotconf_hdr_ntoh((riotconf_hdr_t *)sec); + TEST_ASSERT(((riotconf_hdr_t *)sec)->magic == RIOTCONF_MAGIC); + TEST_ASSERT(((riotconf_hdr_t *)sec)->checksum == 0x2393f289); + TEST_ASSERT(((riotconf_hdr_t *)sec)->size == sizeof(data)); + + ret = riotconf_slot_read(0, sec, 0, sizeof(data)); + TEST_ASSERT(!ret); + TEST_ASSERT(!memcmp(data, sec, sizeof(data))); + ret = riotconf_slot_read(0, sec, 1, sizeof(data) - 1); + TEST_ASSERT(!ret); + TEST_ASSERT(!memcmp(data + 1, sec, sizeof(data) - 1)); + riotconf_slot_finish_read(0); +} + +static void _test_riotconf_slot_write(void) +{ + char data[] = "Hello World!"; + int ret = riotconf_slot_start_write(0, NULL); + TEST_ASSERT(!ret); + ret = riotconf_slot_write(0, data, 0, sizeof(data)); + TEST_ASSERT(!ret); + riotconf_slot_finish_write(0, 1, 123, sizeof(data)); + + void *sec; + size_t sec_size; + riotconf_slot_start_read(0, &sec, &sec_size); + riotconf_hdr_ntoh(((riotconf_hdr_t *)sec)); + TEST_ASSERT(((riotconf_hdr_t *)sec)->magic == RIOTCONF_MAGIC); + TEST_ASSERT(((riotconf_hdr_t *)sec)->size == sizeof(data)); + TEST_ASSERT(((riotconf_hdr_t *)sec)->sequence == 1); + TEST_ASSERT(((riotconf_hdr_t *)sec)->version == 123); + TEST_ASSERT(((riotconf_hdr_t *)sec)->state == 0); +} + +static int _user_slot_compatible(const riotconf_hdr_t *hdr) +{ + return hdr->version == 123; +} + +static void _test_riotconf_slot_highest(void) +{ + char data0[] = "0 Hello World! 0"; + riotconf_hdr_t hdr0 = { + .magic = RIOTCONF_MAGIC, + .checksum = 0x7462c952, + .size = sizeof(data0), + .version = 123 + }; + riotconf_hdr_hton(&hdr0); + mtd_write(&_mtd[0].base, &hdr0, 0, sizeof(hdr0)); + mtd_write(&_mtd[0].base, data0, sizeof(hdr0), sizeof(data0)); + memset(&hdr0, 0, sizeof(hdr0)); + + char data1[] = "1 Hello World! 1"; + riotconf_hdr_t hdr1 = { + .magic = RIOTCONF_MAGIC, + .checksum = 0x7565e7b5, + .size = sizeof(data1), + .sequence = 1, + .version = 124 + }; + riotconf_hdr_hton(&hdr1); + mtd_write(&_mtd[1].base, &hdr1, 0, sizeof(hdr1)); + mtd_write(&_mtd[1].base, data1, sizeof(hdr1), sizeof(data1)); + memset(&hdr1, 0, sizeof(hdr1)); + + int ret = riotconf_slot_highest_seq(NULL); + TEST_ASSERT_EQUAL_INT(1, ret); + ret = riotconf_slot_highest_seq(_user_slot_compatible); + TEST_ASSERT_EQUAL_INT(0, ret); + riotconf_slot_invalidate(0); + ret = riotconf_slot_highest_seq(_user_slot_compatible); + TEST_ASSERT_EQUAL_INT(-1, ret); +} + +Test *tests_riotconf(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(_test_riotconf_slot_invalid), + new_TestFixture(_test_riotconf_slot_invalid_checksum), + new_TestFixture(_test_riotconf_slot_valid_checksum), + new_TestFixture(_test_riotconf_slot_read), + new_TestFixture(_test_riotconf_slot_write), + new_TestFixture(_test_riotconf_slot_highest), + }; + + EMB_UNIT_TESTCALLER(riotconf_tests, _setup, NULL, fixtures); + return (Test *)&riotconf_tests; +} + +int main(void) +{ + TESTS_START(); + TESTS_RUN(tests_riotconf()); + TESTS_END(); + return 0; +} diff --git a/tests/riotconf/tests/01-run.py b/tests/riotconf/tests/01-run.py new file mode 100755 index 000000000000..f43f3442e295 --- /dev/null +++ b/tests/riotconf/tests/01-run.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2024 ML!PA Consulting GmbH +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +import sys +from testrunner import run_check_unittests + + +if __name__ == "__main__": + sys.exit(run_check_unittests()) From d1b77bb41c1eb554f231eea47473ef65fc108257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20H=C3=BC=C3=9Fler?= Date: Wed, 20 Mar 2024 17:12:48 +0100 Subject: [PATCH 12/12] sys/configuration: add riotconf backend --- sys/configuration/backend/riotconf.c | 185 ++++++++++++++++++ .../include/configuration_backend_riotconf.h | 102 ++++++++++ 2 files changed, 287 insertions(+) create mode 100644 sys/configuration/backend/riotconf.c create mode 100644 sys/configuration/include/configuration_backend_riotconf.h diff --git a/sys/configuration/backend/riotconf.c b/sys/configuration/backend/riotconf.c new file mode 100644 index 000000000000..b7902b8d0d42 --- /dev/null +++ b/sys/configuration/backend/riotconf.c @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2024 ML!PA Consulting Gmbh + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup sys_configuration + * @{ + * + * @file + * @brief Implementation of the default riotconf configuration backend + * + * @author Fabian Hüßler + * + * @} + */ + +#include +#include +#include +#include +#include +#include + +#include "configuration.h" +#include "macros/utils.h" +#include "mutex.h" +#include "mtd_default.h" +#include "riotconf/slot.h" +#include "riotconf/storage.h" + +#include "configuration_backend_riotconf.h" + +static riotconf_slot_t _current = -EINVAL; +static riotconf_hdr_t _current_hdr; + +__attribute__((weak)) +int configuration_backend_riotconf_accept(const riotconf_hdr_t *hdr) +{ +#ifdef CONFIGURATION_BACKENT_RIOTCONF_VERSION + return hdr->version == CONFIGURATION_BACKENT_RIOTCONF_VERSION; +#endif + (void)hdr; + return 1; +} + +__attribute__((weak)) +void configuration_backend_riotconf_storage_init(void) +{ +} + +static int _be_riotconf_init(void) +{ + riotconf_slot_t current = riotconf_slot_highest_seq(configuration_backend_riotconf_accept); + if (current < 0) { + current = 0; + } + _current = current; + return 0; +} + +static int _be_riotconf_reset(void) +{ + return riotconf_slot_invalidate(configuration_backend_riotconf_slot_current()); +} + +static int _be_riotconf_load(const struct conf_backend *be, + conf_key_buf_t *key, void *val, size_t *size, + size_t offset, conf_backend_flags_t *flg) +{ + (void)be; + (void)key; + + int ret; + void *sector; + size_t sector_size; + if (_current < 0) { + return -ENOENT; + } + size_t rd = *size; + if (!(*flg & CONF_BACKEND_FLAG_START)) { + riotconf_slot_start_read(_current, §or, §or_size); + *flg |= CONF_BACKEND_FLAG_START; + riotconf_hdr_t *p_hdr = sector; + riotconf_hdr_t hdr = *p_hdr; + riotconf_hdr_ntoh(&hdr); + if (*size < hdr.size) { + *size = hdr.size; + *flg |= CONF_BACKEND_FLAG_MORE; + } + else { + rd = hdr.size; + *size = rd; + *flg |= CONF_BACKEND_FLAG_FINISH; + } + } + if ((ret = riotconf_slot_read(_current, val, offset, rd)) < 0) { + goto fin; + } + if (*flg & CONF_BACKEND_FLAG_FINISH) { +fin: + riotconf_slot_finish_read(_current); + *flg &= ~CONF_BACKEND_FLAG_FINISH; + } + return ret; + +} + +static int _be_riotconf_store(const struct conf_backend *be, + conf_key_buf_t *key, const void *val, size_t *size, + size_t offset, conf_backend_flags_t *flg) +{ + (void)be; + (void)key; + if (_current < 0) { + return -ENOENT; + } + if (!(*flg & CONF_BACKEND_FLAG_START)) { + riotconf_slot_start_write(_current, &_current_hdr); + *flg |= CONF_BACKEND_FLAG_START; + } + riotconf_slot_write(_current, val, offset, *size); + if ((*flg & CONF_BACKEND_FLAG_FINISH)) { + uint32_t version = _current_hdr.magic == RIOTCONF_MAGIC ? _current_hdr.version : 0; + uint32_t sequence = _current_hdr.magic == RIOTCONF_MAGIC ? _current_hdr.sequence : 0; + riotconf_slot_finish_write(_current, sequence, version, offset + *size); + *flg &= ~CONF_BACKEND_FLAG_FINISH; + } + return 0; +} + +static int _be_riotconf_delete(const struct conf_backend *be, conf_key_buf_t *key) +{ + (void)be; + (void)key; + if (_current < 0) { + return -ENOENT; + } + return 0; +} + +const conf_backend_ops_t conf_backend_riotconf_ops = { + .be_load = _be_riotconf_load, + .be_store = _be_riotconf_store, + .be_delete = _be_riotconf_delete, +}; + +riotconf_slot_t configuration_backend_riotconf_slot_current(void) +{ + return _current; +} + +riotconf_hdr_t configuration_backend_riotconf_header_current(void) +{ + riotconf_hdr_t hdr; + riotconf_storage_read(riotconf_storage_get(_current), &hdr, 0, sizeof(hdr)); + return hdr; +} + +int configuration_backend_riotconf_reset(void) +{ + return _be_riotconf_reset(); +} + +int configuration_backend_riotconf_init(void) +{ + return _be_riotconf_init(); +} + +void auto_init_configuration_backend_riotconf(void) +{ + configuration_backend_riotconf_storage_init(); + int fail = configuration_backend_riotconf_init(); + assert(!fail); (void)fail; +} + +#ifndef AUTO_INIT_PRIO_MOD_CONFIGURATION_BACKEND_RIOTCONF +#define AUTO_INIT_PRIO_MOD_CONFIGURATION_BACKEND_RIOTCONF 5679 +#endif + +AUTO_INIT_CONFIGURATION(auto_init_configuration_backend_riotconf, + AUTO_INIT_PRIO_MOD_CONFIGURATION_BACKEND_RIOTCONF); diff --git a/sys/configuration/include/configuration_backend_riotconf.h b/sys/configuration/include/configuration_backend_riotconf.h new file mode 100644 index 000000000000..42a781dc6b20 --- /dev/null +++ b/sys/configuration/include/configuration_backend_riotconf.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2024 ML!PA Consulting Gmbh + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_configuration + * + * @{ + * + * @file + * @brief Interface for riotconf as a configuration backend + * + * @author Fabian Hüßler + */ + +#ifndef CONFIGURATION_BACKEND_RIOTCONF_H +#define CONFIGURATION_BACKEND_RIOTCONF_H + +#include "board.h" +#include "modules.h" +#include "mtd.h" +#include "configuration.h" +#include "riotconf/hdr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if DOXYGEN +/** + * @brief The application may define this to set a preferred configuration version + */ +#define CONFIGURATION_BACKENT_RIOTCONF_VERSION +#endif + +/** + * @brief __attribute__((weak)) function to be overwritten by the application + * to set a more specific criteria for a preferred configuration slot + * + * @param[in] hdr The header of the configuration slot to check + * + * @return 0 if the slot is not accepted, 1 if the slot is accepted + */ +int configuration_backend_riotconf_accept(const riotconf_hdr_t *hdr); + +/** + * @brief Retrieve the current riotconf slot in use + * + * Pass the return value to @ref riotconf_slot_other to get the other slot + * to perform a safe configuration update. + * + * @return riotconf_slot_t + */ +riotconf_slot_t configuration_backend_riotconf_slot_current(void); + +/** + * @brief Retrieve the header of the current configuration slot + * + * @return The header of the current configuration slot + */ +riotconf_hdr_t configuration_backend_riotconf_header_current(void); + +/** + * @brief __attribute__((weak)) function to be overwritten by the application + * to call @ref riotconf_storage_init with board supplied MTDs + */ +void configuration_backend_riotconf_storage_init(void); + +/** + * @brief Initialize the riotconf configuration backend + * + * @return 0 on success, <0 on error + */ +int configuration_backend_riotconf_init(void); + +/** + * @brief Reset the riotconf configuration backend + * + * @return 0 on success, <0 on error + */ +int configuration_backend_riotconf_reset(void); + +/** + * @brief Auto-initialize the riotconf configuration backend + */ +void auto_init_configuration_backend_riotconf(void); + +/** + * @brief riotconf backed operations + */ +extern const conf_backend_ops_t conf_backend_riotconf_ops; + +#ifdef __cplusplus +} +#endif + +#endif /* CONFIGURATION_BACKEND_RIOTCONF_H */ +/** @} */