CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
NvsEditModule.cpp
Go to the documentation of this file.
1
5
9#include "cdc_ui/ViewStack.h"
10#include "cdc_views/ListView.h"
13#include "cdc_views/InfoView.h"
14#include "cdc_views/ToastView.h"
15#include "cdc_log.h"
16#include "esp_attr.h"
17#include "nvs_flash.h"
18#include "nvs.h"
19#include <cstring>
20#include <cstdio>
21
22using namespace cdc::ui;
23using namespace cdc::core;
24
25static const char* TAG = "NvsEdit";
26
27namespace cdc::mod_nvsedit {
28
30static IView* getNvsEditorView();
31
33static void rebuildKeyListView();
34
36
37static constexpr uint8_t MAX_NAMESPACES = 32;
38static constexpr uint8_t MAX_KEYS = 48;
39static constexpr size_t MAX_VALUE_DISPLAY = 256;
40
42
43static char s_namespaces[MAX_NAMESPACES][16] = {};
44static uint8_t s_namespaceCount = 0;
45static char s_selectedNamespace[16] = {};
46
47static char s_keys[MAX_KEYS][16] = {};
48static nvs_type_t s_keyTypes[MAX_KEYS] = {};
49static uint8_t s_keyCount = 0;
50static char s_selectedKey[16] = {};
51
53static ListView* s_namespaceListView = nullptr;
54static ListView* s_keyListView = nullptr;
55static InfoView* s_valueView = nullptr;
58
63static bool deleteEnabled() {
64 return FEATURE_NVS_EDIT != 0;
65}
66
70static void showDeleteDisabled() {
71 showToastError("Delete disabled");
72}
73
79static const char* nvsTypeToString(nvs_type_t type) {
80 switch (type) {
81 case NVS_TYPE_U8: return "u8";
82 case NVS_TYPE_I8: return "i8";
83 case NVS_TYPE_U16: return "u16";
84 case NVS_TYPE_I16: return "i16";
85 case NVS_TYPE_U32: return "u32";
86 case NVS_TYPE_I32: return "i32";
87 case NVS_TYPE_U64: return "u64";
88 case NVS_TYPE_I64: return "i64";
89 case NVS_TYPE_STR: return "str";
90 case NVS_TYPE_BLOB: return "blob";
91 default: return "?";
92 }
93}
94
98static void loadNamespaces() {
100 memset(s_namespaces, 0, sizeof(s_namespaces));
101
102 nvs_iterator_t it = nullptr;
103 esp_err_t err = nvs_entry_find(NVS_DEFAULT_PART_NAME, nullptr, NVS_TYPE_ANY, &it);
104
105 char lastNs[16] = {};
106 while (err == ESP_OK && s_namespaceCount < MAX_NAMESPACES) {
107 nvs_entry_info_t info;
108 nvs_entry_info(it, &info);
109
110 // Add namespace if not already in list
111 if (strcmp(info.namespace_name, lastNs) != 0) {
112 strncpy(s_namespaces[s_namespaceCount], info.namespace_name, 15);
113 s_namespaces[s_namespaceCount][15] = '\0';
114 strncpy(lastNs, info.namespace_name, 15);
116 }
117
118 err = nvs_entry_next(&it);
119 }
120
121 if (it) nvs_release_iterator(it);
122 LOG_I(TAG, "Found %d namespaces", s_namespaceCount);
123}
124
129static void loadKeys(const char* ns) {
130 s_keyCount = 0;
131 memset(s_keys, 0, sizeof(s_keys));
132
133 nvs_iterator_t it = nullptr;
134 esp_err_t err = nvs_entry_find(NVS_DEFAULT_PART_NAME, ns, NVS_TYPE_ANY, &it);
135
136 while (err == ESP_OK && s_keyCount < MAX_KEYS) {
137 nvs_entry_info_t info;
138 nvs_entry_info(it, &info);
139
140 strncpy(s_keys[s_keyCount], info.key, 15);
141 s_keys[s_keyCount][15] = '\0';
142 s_keyTypes[s_keyCount] = info.type;
143 s_keyCount++;
144
145 err = nvs_entry_next(&it);
146 }
147
148 if (it) nvs_release_iterator(it);
149 LOG_I(TAG, "Found %d keys in '%s'", s_keyCount, ns);
150}
151
158static bool deleteKey(const char* ns, const char* key) {
159 nvs_handle_t handle;
160 if (nvs_open(ns, NVS_READWRITE, &handle) != ESP_OK) {
161 return false;
162 }
163
164 esp_err_t err = nvs_erase_key(handle, key);
165 if (err == ESP_OK) {
166 nvs_commit(handle);
167 }
168 nvs_close(handle);
169
170 LOG_I(TAG, "Deleted key '%s' from '%s': %s", key, ns, esp_err_to_name(err));
171 return err == ESP_OK;
172}
173
179static bool deleteNamespace(const char* ns) {
180 nvs_handle_t handle;
181 if (nvs_open(ns, NVS_READWRITE, &handle) != ESP_OK) {
182 return false;
183 }
184
185 esp_err_t err = nvs_erase_all(handle);
186 if (err == ESP_OK) {
187 nvs_commit(handle);
188 }
189 nvs_close(handle);
190
191 LOG_I(TAG, "Deleted namespace '%s': %s", ns, esp_err_to_name(err));
192 return err == ESP_OK;
193}
194
203static void formatValue(const char* ns, const char* key, nvs_type_t type, char* buf, size_t bufLen) {
204 nvs_handle_t handle;
205 if (nvs_open(ns, NVS_READONLY, &handle) != ESP_OK) {
206 snprintf(buf, bufLen, "(error opening)");
207 return;
208 }
209
210 esp_err_t err = ESP_FAIL;
211
212 switch (type) {
213 case NVS_TYPE_U8: {
214 uint8_t val;
215 err = nvs_get_u8(handle, key, &val);
216 if (err == ESP_OK) snprintf(buf, bufLen, "%u (0x%02X)", val, val);
217 break;
218 }
219 case NVS_TYPE_I8: {
220 int8_t val;
221 err = nvs_get_i8(handle, key, &val);
222 if (err == ESP_OK) snprintf(buf, bufLen, "%d", val);
223 break;
224 }
225 case NVS_TYPE_U16: {
226 uint16_t val;
227 err = nvs_get_u16(handle, key, &val);
228 if (err == ESP_OK) snprintf(buf, bufLen, "%u (0x%04X)", val, val);
229 break;
230 }
231 case NVS_TYPE_I16: {
232 int16_t val;
233 err = nvs_get_i16(handle, key, &val);
234 if (err == ESP_OK) snprintf(buf, bufLen, "%d", val);
235 break;
236 }
237 case NVS_TYPE_U32: {
238 uint32_t val;
239 err = nvs_get_u32(handle, key, &val);
240 if (err == ESP_OK) snprintf(buf, bufLen, "%lu (0x%08lX)", (unsigned long)val, (unsigned long)val);
241 break;
242 }
243 case NVS_TYPE_I32: {
244 int32_t val;
245 err = nvs_get_i32(handle, key, &val);
246 if (err == ESP_OK) snprintf(buf, bufLen, "%ld", (long)val);
247 break;
248 }
249 case NVS_TYPE_U64: {
250 uint64_t val;
251 err = nvs_get_u64(handle, key, &val);
252 if (err == ESP_OK) snprintf(buf, bufLen, "%llu", (unsigned long long)val);
253 break;
254 }
255 case NVS_TYPE_I64: {
256 int64_t val;
257 err = nvs_get_i64(handle, key, &val);
258 if (err == ESP_OK) snprintf(buf, bufLen, "%lld", (long long)val);
259 break;
260 }
261 case NVS_TYPE_STR: {
262 size_t len = 0;
263 err = nvs_get_str(handle, key, nullptr, &len);
264 if (err == ESP_OK && len > 0 && len < bufLen) {
265 err = nvs_get_str(handle, key, buf, &len);
266 } else if (err == ESP_OK) {
267 snprintf(buf, bufLen, "(str len=%zu)", len);
268 }
269 break;
270 }
271 case NVS_TYPE_BLOB: {
272 size_t len = 0;
273 err = nvs_get_blob(handle, key, nullptr, &len);
274 if (err == ESP_OK) {
275 if (len <= 32) {
276 uint8_t data[32];
277 err = nvs_get_blob(handle, key, data, &len);
278 if (err == ESP_OK) {
279 char* p = buf;
280 for (size_t i = 0; i < len && (p - buf) < (int)(bufLen - 4); i++) {
281 p += snprintf(p, bufLen - (p - buf), "%02X ", data[i]);
282 }
283 }
284 } else {
285 snprintf(buf, bufLen, "(blob %zu bytes)", len);
286 }
287 }
288 break;
289 }
290 default:
291 snprintf(buf, bufLen, "(unknown type)");
292 break;
293 }
294
295 if (err != ESP_OK) {
296 snprintf(buf, bufLen, "(read error)");
297 }
298
299 nvs_close(handle);
300}
301
305static void onDeleteNamespace() {
306 if (!deleteEnabled()) {
309 return;
310 }
311 if (s_selectedNamespace[0] == '\0') return;
312
314 showToastInfo("Deleted");
316 // Update list
317 for (uint8_t i = 0; i < s_namespaceCount; i++) {
318 s_namespaceItems[i] = {s_namespaces[i], 0, false, nullptr};
319 }
321 s_namespaceListView->init("NVS Namespaces", s_namespaceItems, s_namespaceCount);
322 }
323 } else {
324 showToastError("Delete failed");
325 }
327}
328
332static void onDeleteKey() {
333 if (!deleteEnabled()) {
336 return;
337 }
338 if (s_selectedNamespace[0] == '\0' || s_selectedKey[0] == '\0') return;
339
341 showToastInfo("Deleted");
344 // Go back if no more keys
345 if (s_keyCount == 0) {
347 }
348 } else {
349 showToastError("Delete failed");
350 }
352}
353
355 {"Delete NS", onDeleteNamespace}
356};
357
359 {"Delete Key", onDeleteKey}
360};
361
363static void showKeyListView(const char* ns);
365static void showValueView(const char* ns, const char* key, nvs_type_t type);
366
372static void onNamespaceSelect(uint16_t index, void* userData) {
373 (void)userData;
374 if (index >= s_namespaceCount) return;
375
376 strncpy(s_selectedNamespace, s_namespaces[index], sizeof(s_selectedNamespace) - 1);
378}
379
385static void onNamespaceMenu(uint16_t index, void* userData) {
386 (void)userData;
387 if (index < s_namespaceCount) {
388 strncpy(s_selectedNamespace, s_namespaces[index], sizeof(s_selectedNamespace) - 1);
389 if (!deleteEnabled()) {
391 return;
392 }
393 showContextMenu("Namespace", s_nsContextItems, 1);
394 }
395}
396
402
403 if (!s_namespaceListView) {
407 }
408
409 for (uint8_t i = 0; i < s_namespaceCount; i++) {
410 s_namespaceItems[i] = {s_namespaces[i], 0, false, nullptr};
411 }
412
413 s_namespaceListView->init("NVS Namespaces", s_namespaceItems, s_namespaceCount);
415}
416
418EXT_RAM_BSS_ATTR static char s_keyLabels[MAX_KEYS][24];
419
425static void onKeySelect(uint16_t index, void* userData) {
426 (void)userData;
427 if (index >= s_keyCount) return;
428
429 strncpy(s_selectedKey, s_keys[index], sizeof(s_selectedKey) - 1);
431}
432
438static void onKeyMenu(uint16_t index, void* userData) {
439 (void)userData;
440 if (index < s_keyCount) {
441 strncpy(s_selectedKey, s_keys[index], sizeof(s_selectedKey) - 1);
442 if (!deleteEnabled()) {
444 return;
445 }
447 }
448}
449
453static void rebuildKeyListView() {
454 for (uint8_t i = 0; i < s_keyCount; i++) {
455 snprintf(s_keyLabels[i], sizeof(s_keyLabels[i]), "%s [%s]",
457 s_keyItems[i] = {s_keyLabels[i], 0, false, nullptr};
458 }
459 if (s_keyListView) {
461 }
462}
463
468static void showKeyListView(const char* ns) {
469 loadKeys(ns);
470
471 if (!s_keyListView) {
472 s_keyListView = new ListView();
473 s_keyListView->setOnSelect(onKeySelect);
474 s_keyListView->setOnMenu(onKeyMenu);
475 }
476
479}
480
482static char s_valueTitle[32];
483
490static void showValueView(const char* ns, const char* key, nvs_type_t type) {
491 formatValue(ns, key, type, s_valueBuffer, sizeof(s_valueBuffer));
492
493 snprintf(s_valueTitle, sizeof(s_valueTitle), "%s [%s]", key, nvsTypeToString(type));
494
495 if (!s_valueView) {
496 s_valueView = new InfoView();
497 }
498
501}
502
507static void onNvsEditorConfirm(void* userData) {
508 (void)userData;
510 if (!deleteEnabled()) {
511 showToastInfo("Read-only mode");
512 }
513}
514
520 const char* msg = deleteEnabled()
521 ? "NVS Editor is privileged. Deletes are irreversible. Continue?"
522 : "NVS Browser is read-only. Continue?";
524 return nullptr; // We pushed view ourselves
525}
526
533 LOG_I(TAG, "NVS Editor module initialized");
534 return true;
535}
536
543uint8_t NvsEditModule::getMenuItems(ModuleMenuItem* items, uint8_t maxItems) {
544 if (maxItems < 1) return 0;
545
546 items[0].label = deleteEnabled() ? "NVS Editor" : "NVS Browser";
547 items[0].priority = 50;
548 items[0].getView = getNvsEditorView;
549 items[0].isVisible = nullptr;
550 items[0].moduleName = getName();
552 items[0].onSelect = nullptr;
553
554 return 1;
555}
556
559
560} // namespace cdc::mod_nvsedit
561
565extern "C" void mod_nvsedit_register() {
567 auto& module = cdc::mod_nvsedit::s_module;
568 module.init();
569 });
570}
static const char * TAG
void mod_nvsedit_register()
Registers NVS editor initializer with module registry.
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_I(tag, fmt,...)
Definition cdc_log.h:147
const char * getName() const override
Returns the module name supplied to the constructor.
Definition ModuleBase.h:32
ServiceState state_
Definition ModuleBase.h:67
static ModuleRegistry & instance()
Returns the singleton module registry instance.
void registerInitializer(ModuleInitFunc initFunc)
Registers a deferred module initializer callback.
bool init() override
Initializes NVS editor module state.
uint8_t getMenuItems(cdc::core::ModuleMenuItem *items, uint8_t maxItems) override
Exposes NVS editor entry in the tools menu.
static ViewStack & instance()
Returns singleton view-stack instance.
Definition ViewStack.cpp:34
void push(IView *view, void *context=nullptr)
#define FEATURE_NVS_EDIT
static ContextMenuItem s_nsContextItems[]
static ListView * s_namespaceListView
Lazily created views and backing list item arrays.
static char s_valueBuffer[MAX_VALUE_DISPLAY]
static constexpr uint8_t MAX_KEYS
static void formatValue(const char *ns, const char *key, nvs_type_t type, char *buf, size_t bufLen)
Formats one NVS value into a readable preview string.
static void loadKeys(const char *ns)
Loads keys and types for one namespace.
static bool deleteKey(const char *ns, const char *key)
Deletes a single key from a namespace.
static uint8_t s_namespaceCount
static void onKeySelect(uint16_t index, void *userData)
Handles key selection and opens detailed value view.
static char s_selectedNamespace[16]
static ListItem s_namespaceItems[MAX_NAMESPACES]
static constexpr uint8_t MAX_NAMESPACES
Upper bounds for list sizes and value preview buffers.
static const char * nvsTypeToString(nvs_type_t type)
Converts NVS value type enum to a short display string.
static void loadNamespaces()
Loads unique namespace names from the default NVS partition.
static void onDeleteNamespace()
Deletes the currently selected namespace via context menu action.
static void onDeleteKey()
Deletes the currently selected key via context menu action.
static char s_keys[MAX_KEYS][16]
static void rebuildKeyListView()
Rebuilds key list labels into s_keyLabels and refreshes the key list view.
static void onNamespaceMenu(uint16_t index, void *userData)
Opens namespace context menu for selected item.
static constexpr size_t MAX_VALUE_DISPLAY
static void showNamespaceListView()
Creates and shows the namespace list view.
static ListItem s_keyItems[MAX_KEYS]
static ContextMenuItem s_keyContextItems[]
static bool deleteEnabled()
Indicates whether destructive delete actions are enabled.
static void showDeleteDisabled()
Shows a toast describing that delete actions are disabled.
static InfoView * s_valueView
static bool deleteNamespace(const char *ns)
Deletes all keys in a namespace.
static void onKeyMenu(uint16_t index, void *userData)
Opens key context menu for the selected key.
static nvs_type_t s_keyTypes[MAX_KEYS]
static char s_namespaces[MAX_NAMESPACES][16]
Runtime state for namespace/key browsing context.
static NvsEditModule s_module
Static module instance used by registration callback.
static char s_selectedKey[16]
static void onNamespaceSelect(uint16_t index, void *userData)
Handles namespace list selection.
static char s_keyLabels[MAX_KEYS][24]
Persistent key label storage used by list items.
static ListView * s_keyListView
static void showValueView(const char *ns, const char *key, nvs_type_t type)
Shows detailed value view for a selected key.
static char s_valueTitle[32]
static uint8_t s_keyCount
static IView * getNvsEditorView()
Returns the NVS editor entry view callback target.
static void onNvsEditorConfirm(void *userData)
Confirm callback that opens namespace browser after warning dialog.
static void showKeyListView(const char *ns)
Shows key list view for the selected namespace.
Centralized key-code constants for cdc_views.
Definition IModule.h:8
void showConfirm(const char *message, ConfirmView::ConfirmCallback onConfirm, ConfirmView::CancelCallback onCancel=nullptr, ConfirmView::Icon icon=ConfirmView::Icon::QUESTION, void *userData=nullptr)
Shows a shared modal confirmation dialog instance.
void hideContextMenu()
Hides the active context menu modal.
ContextMenuView * showContextMenu(const char *title, const ContextMenuItem *items, uint8_t count)
Shows the shared context menu instance as modal.
void showToastInfo(const char *message, uint16_t durationMs=1500)
Shows an informational toast message.
void showToastError(const char *message, uint16_t durationMs=1500)
Shows an error toast message.
Menu item registered by a module.
Definition IModule.h:29
ui::IView *(* getView)()
Definition IModule.h:32
MenuLocation location
Definition IModule.h:35
const char * moduleName
Definition IModule.h:34