DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
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 DETWS_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 detws_config_begin(const char *ns)
35{
36 if (s_cfg.open)
37 s_cfg.prefs.end();
38 s_cfg.open = s_cfg.prefs.begin(ns, false); // read-write
39 return s_cfg.open;
40}
41
42bool detws_config_set_str(const char *key, const char *val)
43{
44 if (!val)
45 return false;
46 return s_cfg.prefs.putString(key, val) == strnlen(val, DETWS_CONFIG_VAL_MAX + 1);
47}
48
49size_t detws_config_get_str(const char *key, char *out, size_t out_cap, const char *def)
50{
51 if (!out || out_cap == 0)
52 return 0;
53 if (!s_cfg.prefs.isKey(key))
54 {
55 size_t n = def ? strnlen(def, out_cap) : 0;
56 if (n > out_cap - 1)
57 n = out_cap - 1;
58 if (n)
59 memcpy(out, def, n);
60 out[n] = '\0';
61 return n;
62 }
63 return s_cfg.prefs.getString(key, out, out_cap);
64}
65
66bool detws_config_set_u32(const char *key, uint32_t val)
67{
68 return s_cfg.prefs.putUInt(key, val) == sizeof(uint32_t);
69}
70
71uint32_t detws_config_get_u32(const char *key, uint32_t def)
72{
73 return s_cfg.prefs.getUInt(key, def);
74}
75
76bool detws_config_set_blob(const char *key, const void *data, size_t len)
77{
78 if (!data)
79 return false;
80 return s_cfg.prefs.putBytes(key, data, len) == len;
81}
82
83size_t detws_config_get_blob(const char *key, void *out, size_t out_cap)
84{
85 if (!out || !s_cfg.prefs.isKey(key))
86 return 0;
87 return s_cfg.prefs.getBytes(key, out, out_cap);
88}
89
90bool detws_config_erase(const char *key)
91{
92 return s_cfg.prefs.remove(key);
93}
94
95bool detws_config_clear(void)
96{
97 return s_cfg.prefs.clear();
98}
99
100#else // host: in-memory backend (test double for NVS)
101
102namespace
103{
104struct Entry
105{
106 char key[DETWS_CONFIG_KEY_MAX];
107 uint8_t val[DETWS_CONFIG_VAL_MAX];
108 size_t len;
109 bool used;
110};
111// All host config-store state, owned by one instance (internal linkage): the in-memory
112// entry table (the NVS test double), so it is one named owner, unreachable cross-TU.
113struct ConfigStoreCtx
114{
115 Entry tbl[DETWS_CONFIG_MAX_ENTRIES];
116};
117ConfigStoreCtx s_cfg;
118
119bool key_ok(const char *key)
120{
121 return key && key[0] && strnlen(key, DETWS_CONFIG_KEY_MAX + 1) < DETWS_CONFIG_KEY_MAX;
122}
123
124// Returns a mutable entry (callers mutate it), so it takes the owner by non-const reference.
125Entry *find(ConfigStoreCtx &c, const char *key)
126{
127 for (int i = 0; i < DETWS_CONFIG_MAX_ENTRIES; i++)
128 if (c.tbl[i].used && strcmp(c.tbl[i].key, key) == 0)
129 return &c.tbl[i];
130 return nullptr;
131}
132
133Entry *find_or_alloc(ConfigStoreCtx &c, const char *key)
134{
135 Entry *e = find(c, key);
136 if (e)
137 return e;
138 for (int i = 0; i < DETWS_CONFIG_MAX_ENTRIES; i++)
139 if (!c.tbl[i].used)
140 {
141 c.tbl[i].used = true;
142 strncpy(c.tbl[i].key, key, DETWS_CONFIG_KEY_MAX - 1);
143 c.tbl[i].key[DETWS_CONFIG_KEY_MAX - 1] = '\0';
144 c.tbl[i].len = 0;
145 return &c.tbl[i];
146 }
147 return nullptr; // table full
148}
149
150bool store(ConfigStoreCtx &c, const char *key, const void *data, size_t len)
151{
152 if (!key_ok(key) || len > DETWS_CONFIG_VAL_MAX)
153 return false;
154 Entry *e = find_or_alloc(c, key);
155 if (!e)
156 return false;
157 memcpy(e->val, data, len);
158 e->len = len;
159 return true;
160}
161} // namespace
162
163bool detws_config_begin(const char *ns)
164{
165 (void)ns; // single in-memory namespace on host
166 return true;
167}
168
169bool detws_config_set_str(const char *key, const char *val)
170{
171 if (!val)
172 return false;
173 return store(s_cfg, key, val, strnlen(val, DETWS_CONFIG_VAL_MAX) + 1); // include the null terminator
174}
175
176size_t detws_config_get_str(const char *key, char *out, size_t out_cap, const char *def)
177{
178 if (!out || out_cap == 0)
179 return 0;
180 Entry *e = key_ok(key) ? find(s_cfg, key) : nullptr;
181 const char *src = e ? (const char *)e->val : (def ? def : "");
182 size_t n = strnlen(src, out_cap);
183 if (n > out_cap - 1)
184 n = out_cap - 1;
185 memcpy(out, src, n);
186 out[n] = '\0';
187 return n;
188}
189
190bool detws_config_set_u32(const char *key, uint32_t val)
191{
192 uint8_t b[4] = {(uint8_t)val, (uint8_t)(val >> 8), (uint8_t)(val >> 16), (uint8_t)(val >> 24)};
193 return store(s_cfg, key, b, sizeof(b));
194}
195
196uint32_t detws_config_get_u32(const char *key, uint32_t def)
197{
198 Entry *e = key_ok(key) ? find(s_cfg, key) : nullptr;
199 if (!e || e->len < 4)
200 return def;
201 return (uint32_t)e->val[0] | ((uint32_t)e->val[1] << 8) | ((uint32_t)e->val[2] << 16) | ((uint32_t)e->val[3] << 24);
202}
203
204bool detws_config_set_blob(const char *key, const void *data, size_t len)
205{
206 if (!data)
207 return false;
208 return store(s_cfg, key, data, len);
209}
210
211size_t detws_config_get_blob(const char *key, void *out, size_t out_cap)
212{
213 Entry *e = key_ok(key) ? find(s_cfg, key) : nullptr;
214 if (!e || !out)
215 return 0;
216 size_t n = e->len < out_cap ? e->len : out_cap;
217 memcpy(out, e->val, n);
218 return n;
219}
220
221bool detws_config_erase(const char *key)
222{
223 Entry *e = key_ok(key) ? find(s_cfg, key) : nullptr;
224 if (!e)
225 return false;
226 e->used = false;
227 e->len = 0;
228 return true;
229}
230
231bool detws_config_clear(void)
232{
233 for (int i = 0; i < DETWS_CONFIG_MAX_ENTRIES; i++)
234 s_cfg.tbl[i] = Entry{};
235 return true;
236}
237
238#endif // ARDUINO
239#endif // DETWS_ENABLE_CONFIG_STORE
#define DETWS_CONFIG_KEY_MAX
Max key length incl. null (NVS caps keys at 15 chars).
#define DETWS_CONFIG_MAX_ENTRIES
Max key/value entries in the host (test) config backend.
#define DETWS_CONFIG_VAL_MAX
Max value bytes per entry in the host (test) config backend.
Typed NVS configuration store (DETWS_ENABLE_CONFIG_STORE).