ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
config_store.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file config_store.cpp
6 * @brief Typed NVS configuration store - implementation.
7 *
8 * ESP32: the Arduino `Preferences` NVS wrapper. Host: a fixed in-memory table so
9 * the typed contract is unit-testable. See config_store.h.
10 */
11
12#include "config_store.h"
13
14#if PC_ENABLE_CONFIG_STORE
15
16#include <string.h>
17
18#ifdef ARDUINO
19
20#include <Preferences.h>
21
22namespace
23{
24// All config-store state, owned by one instance (internal linkage): the NVS Preferences
25// handle and its open flag, grouped so it is one named owner, unreachable cross-TU.
26struct ConfigStoreCtx
27{
28 Preferences prefs;
29 bool open = false;
30};
31ConfigStoreCtx s_cfg;
32} // namespace
33
34bool pc_config_begin(const char *ns)
35{
36 if (s_cfg.open)
37 {
38 s_cfg.prefs.end();
39 }
40 s_cfg.open = s_cfg.prefs.begin(ns, false); // read-write
41 return s_cfg.open;
42}
43
44bool pc_config_set_str(const char *key, const char *val)
45{
46 if (!val)
47 {
48 return false;
49 }
50 return s_cfg.prefs.putString(key, val) == strnlen(val, PC_CONFIG_VAL_MAX + 1);
51}
52
53size_t pc_config_get_str(const char *key, char *out, size_t out_cap, const char *def)
54{
55 if (!out || out_cap == 0)
56 {
57 return 0;
58 }
59 if (!s_cfg.prefs.isKey(key))
60 {
61 size_t n = def ? strnlen(def, out_cap) : 0;
62 if (n > out_cap - 1)
63 {
64 n = out_cap - 1;
65 }
66 if (n)
67 {
68 memcpy(out, def, n);
69 }
70 out[n] = '\0';
71 return n;
72 }
73 return s_cfg.prefs.getString(key, out, out_cap);
74}
75
76bool pc_config_set_u32(const char *key, uint32_t val)
77{
78 return s_cfg.prefs.putUInt(key, val) == sizeof(uint32_t);
79}
80
81uint32_t pc_config_get_u32(const char *key, uint32_t def)
82{
83 return s_cfg.prefs.getUInt(key, def);
84}
85
86bool pc_config_set_blob(const char *key, const void *data, size_t len)
87{
88 if (!data)
89 {
90 return false;
91 }
92 return s_cfg.prefs.putBytes(key, data, len) == len;
93}
94
95size_t pc_config_get_blob(const char *key, void *out, size_t out_cap)
96{
97 if (!out || !s_cfg.prefs.isKey(key))
98 {
99 return 0;
100 }
101 return s_cfg.prefs.getBytes(key, out, out_cap);
102}
103
104bool pc_config_erase(const char *key)
105{
106 return s_cfg.prefs.remove(key);
107}
108
109bool pc_config_clear(void)
110{
111 return s_cfg.prefs.clear();
112}
113
114#else // host: in-memory backend (test double for NVS)
115
116namespace
117{
118struct Entry
119{
120 char key[PC_CONFIG_KEY_MAX];
121 uint8_t val[PC_CONFIG_VAL_MAX];
122 size_t len;
123 bool used;
124};
125// All host config-store state, owned by one instance (internal linkage): the in-memory
126// entry table (the NVS test double), so it is one named owner, unreachable cross-TU.
127struct ConfigStoreCtx
128{
129 Entry tbl[PC_CONFIG_MAX_ENTRIES];
130};
131ConfigStoreCtx s_cfg;
132
133bool key_ok(const char *key)
134{
135 return key && key[0] && strnlen(key, PC_CONFIG_KEY_MAX + 1) < PC_CONFIG_KEY_MAX;
136}
137
138// Returns a mutable entry (callers mutate it), so it takes the owner by non-const reference.
139Entry *find(ConfigStoreCtx &c, const char *key)
140{
141 for (int i = 0; i < PC_CONFIG_MAX_ENTRIES; i++)
142 {
143 if (c.tbl[i].used && strcmp(c.tbl[i].key, key) == 0)
144 {
145 return &c.tbl[i];
146 }
147 }
148 return nullptr;
149}
150
151Entry *find_or_alloc(ConfigStoreCtx &c, const char *key)
152{
153 Entry *e = find(c, key);
154 if (e)
155 {
156 return e;
157 }
158 for (int i = 0; i < PC_CONFIG_MAX_ENTRIES; i++)
159 {
160 if (!c.tbl[i].used)
161 {
162 c.tbl[i].used = true;
163 strncpy(c.tbl[i].key, key, PC_CONFIG_KEY_MAX - 1);
164 c.tbl[i].key[PC_CONFIG_KEY_MAX - 1] = '\0';
165 c.tbl[i].len = 0;
166 return &c.tbl[i];
167 }
168 }
169 return nullptr; // table full
170}
171
172bool store(ConfigStoreCtx &c, const char *key, const void *data, size_t len)
173{
174 if (!key_ok(key) || len > PC_CONFIG_VAL_MAX)
175 {
176 return false;
177 }
178 Entry *e = find_or_alloc(c, key);
179 if (!e)
180 {
181 return false;
182 }
183 memcpy(e->val, data, len);
184 e->len = len;
185 return true;
186}
187} // namespace
188
189bool pc_config_begin(const char *ns)
190{
191 (void)ns; // single in-memory namespace on host
192 return true;
193}
194
195bool pc_config_set_str(const char *key, const char *val)
196{
197 if (!val)
198 {
199 return false;
200 }
201 return store(s_cfg, key, val, strnlen(val, PC_CONFIG_VAL_MAX) + 1); // include the null terminator
202}
203
204size_t pc_config_get_str(const char *key, char *out, size_t out_cap, const char *def)
205{
206 if (!out || out_cap == 0)
207 {
208 return 0;
209 }
210 Entry *e = key_ok(key) ? find(s_cfg, key) : nullptr;
211 const char *src = e ? (const char *)e->val : (def ? def : "");
212 size_t n = strnlen(src, out_cap);
213 if (n > out_cap - 1)
214 {
215 n = out_cap - 1;
216 }
217 memcpy(out, src, n);
218 out[n] = '\0';
219 return n;
220}
221
222bool pc_config_set_u32(const char *key, uint32_t val)
223{
224 uint8_t b[4] = {(uint8_t)val, (uint8_t)(val >> 8), (uint8_t)(val >> 16), (uint8_t)(val >> 24)};
225 return store(s_cfg, key, b, sizeof(b));
226}
227
228uint32_t pc_config_get_u32(const char *key, uint32_t def)
229{
230 Entry *e = key_ok(key) ? find(s_cfg, key) : nullptr;
231 if (!e || e->len < 4)
232 {
233 return def;
234 }
235 return (uint32_t)e->val[0] | ((uint32_t)e->val[1] << 8) | ((uint32_t)e->val[2] << 16) | ((uint32_t)e->val[3] << 24);
236}
237
238bool pc_config_set_blob(const char *key, const void *data, size_t len)
239{
240 if (!data)
241 {
242 return false;
243 }
244 return store(s_cfg, key, data, len);
245}
246
247size_t pc_config_get_blob(const char *key, void *out, size_t out_cap)
248{
249 Entry *e = key_ok(key) ? find(s_cfg, key) : nullptr;
250 if (!e || !out)
251 {
252 return 0;
253 }
254 size_t n = e->len < out_cap ? e->len : out_cap;
255 memcpy(out, e->val, n);
256 return n;
257}
258
259bool pc_config_erase(const char *key)
260{
261 Entry *e = key_ok(key) ? find(s_cfg, key) : nullptr;
262 if (!e)
263 {
264 return false;
265 }
266 e->used = false;
267 e->len = 0;
268 return true;
269}
270
271bool pc_config_clear(void)
272{
273 for (int i = 0; i < PC_CONFIG_MAX_ENTRIES; i++)
274 {
275 s_cfg.tbl[i] = Entry{};
276 }
277 return true;
278}
279
280#endif // ARDUINO
281#endif // PC_ENABLE_CONFIG_STORE
Typed NVS configuration store (PC_ENABLE_CONFIG_STORE).
#define PC_CONFIG_KEY_MAX
Max key length incl. null (NVS caps keys at 15 chars).
#define PC_CONFIG_VAL_MAX
Max value bytes per entry in the host (test) config backend.
#define PC_CONFIG_MAX_ENTRIES
Max key/value entries in the host (test) config backend.