CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
TropicStorage.cpp
Go to the documentation of this file.
3#include "cdc_core/Hash.h"
4#include "cdc_core/Raii.h"
5#include "cdc_log.h"
6#include "nvs_flash.h"
7#include <cstring>
8
9static const char* TAG = "TR01_STORE";
10
11namespace cdc::core {
12
13static constexpr uint8_t CACHE_VERSION = 1;
14static constexpr const char* NVS_NAMESPACE = "tr01_meta";
15static constexpr const char* NVS_KEY_HEADER = "hdr";
16
21TropicStorage& TropicStorage::instance() {
22 static TropicStorage inst;
23 return inst;
24}
25
31 if (state_ != ServiceState::UNINITIALIZED) {
32 return state_ == ServiceState::INITIALIZED || state_ == ServiceState::STARTED;
33 }
34
35 header_.version = CACHE_VERSION;
36 header_.chunkSlots = CHUNK_SLOTS;
37 header_.entrySize = sizeof(CacheEntry);
38 header_.mapSignature = computeMapSignature();
39
40 cacheValid_ = loadHeader();
41
42 if (!cacheValid_) {
43 if (saveHeader()) {
44 cacheValid_ = true;
45 } else {
46 LOG_W(TAG, "Cache header missing or stale - rebuild required");
47 }
48 }
49
51 return true;
52}
53
59 if (state_ == ServiceState::UNINITIALIZED) {
60 if (!init()) return false;
61 }
62 state_ = ServiceState::STARTED;
63 return true;
64}
65
72
80bool TropicStorage::forEachSlot(uint8_t moduleId, SlotCallback cb, void* ctx) {
81 return forEachSlot(moduleId, 0, 0xFFFF, cb, ctx);
82}
83
93bool TropicStorage::forEachSlot(uint8_t moduleId, uint16_t fromSlot, uint16_t toSlot,
94 SlotCallback cb, void* ctx) {
95 if (!cb) return false;
96 if (fromSlot > toSlot) return false;
97 auto& slotMap = TropicSlotMap::instance();
98 TropicSlotMap::SlotRange range = {};
99 if (!slotMap.getRangeByModuleId(moduleId, TropicSlotMap::SlotType::RMEM, &range)) {
100 return false;
101 }
102 if (fromSlot == 0 || toSlot == 0xFFFF) {
103 fromSlot = range.start;
104 toSlot = range.end;
105 }
106 if (fromSlot < range.start) fromSlot = range.start;
107 if (toSlot > range.end) toSlot = range.end;
108
109 uint16_t startChunk = fromSlot / CHUNK_SLOTS;
110 uint16_t endChunk = toSlot / CHUNK_SLOTS;
111
112 CacheEntry entries[CHUNK_SLOTS] = {};
113 for (uint16_t chunk = startChunk; chunk <= endChunk; chunk++) {
114 if (!loadChunk(chunk, entries)) {
115 return false;
116 }
117 uint16_t slotBase = chunk * CHUNK_SLOTS;
118 for (uint16_t i = 0; i < CHUNK_SLOTS; i++) {
119 uint16_t slot = static_cast<uint16_t>(slotBase + i);
120 if (slot < fromSlot || slot > toSlot) continue;
121 const CacheEntry& entry = entries[i];
122 if (!isEntryUsed(entry)) continue;
123 if (!isEntryAllowed(slot, entry.moduleId)) continue;
124 if (entry.moduleId != moduleId) continue;
125 cb(slot, entry, ctx);
126 }
127 }
128 return true;
129}
130
139bool TropicStorage::getSlot(uint8_t moduleId, uint16_t index, SlotCallback cb, void* ctx) {
140 if (!cb) return false;
141
142 uint16_t start = 0;
143 uint16_t end = 0;
144
145 TropicSlotMap::SlotRange range = {};
146 if (!TropicSlotMap::instance().getRangeByModuleId(
148 return false;
149 }
150 start = range.start;
151 end = range.end;
152
153 uint32_t slot = static_cast<uint32_t>(start) + index;
154 if (slot > end) return false;
155
156 CacheEntry entry = {};
157 if (!getEntry(static_cast<uint16_t>(slot), &entry)) return false;
158 if (!isEntryUsed(entry)) return false;
159 if (!isEntryAllowed(static_cast<uint16_t>(slot), entry.moduleId)) return false;
160 if (entry.moduleId != moduleId) return false;
161
162 cb(static_cast<uint16_t>(slot), entry, ctx);
163 return true;
164}
165
174bool TropicStorage::writeSlot(uint8_t moduleId, uint16_t slot, const char* name, uint8_t flags) {
175 if (!isEntryAllowed(slot, moduleId)) {
176 return false;
177 }
178
179 CacheEntry entry = {};
180 entry.moduleId = moduleId;
181 entry.flags = static_cast<uint8_t>(flags | FLAG_USED);
182 if (name) {
183 strncpy(entry.name, name, sizeof(entry.name) - 1);
184 entry.name[sizeof(entry.name) - 1] = '\0';
185 }
186
187 if (!saveHeader()) {
188 return false;
189 }
190
191 return setEntry(slot, entry);
192}
193
200bool TropicStorage::eraseSlot(uint8_t moduleId, uint16_t slot) {
201 if (!isEntryAllowed(slot, moduleId)) {
202 return false;
203 }
204
205 CacheEntry entry = {};
206 return setEntry(slot, entry);
207}
208
214 return rebuildVerbose(nullptr, nullptr);
215}
216
224 if (!secureElement_) {
225 LOG_E(TAG, "No secure element set");
226 return false;
227 }
228 if (!secureElement_->isSessionActive()) {
229 if (!secureElement_->sessionStart()) {
230 if (logFn) logFn(0xFFFF, "session start failed", ctx);
231 return false;
232 }
233 }
234
235 CacheEntry chunk[CHUNK_SLOTS] = {};
236 uint16_t totalChunks =
237 static_cast<uint16_t>((TropicSlotMap::instance().rmemMax() + 1u) / CHUNK_SLOTS);
238
239 for (uint16_t chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
240 memset(chunk, 0, sizeof(chunk));
241 uint16_t slotBase = chunkIndex * CHUNK_SLOTS;
242 for (uint16_t i = 0; i < CHUNK_SLOTS; i++) {
243 uint16_t slot = static_cast<uint16_t>(slotBase + i);
244 if (slot == 0) continue;
245
246 cdc::hal::ISecureElement::RMemHeader header = {};
247 uint16_t payloadLen = 0;
248 auto res = secureElement_->rmemReadWithHeader(slot, &header, nullptr, 0, &payloadLen);
249 if (res == cdc::hal::SeResult::OK) {
250 if (!isEntryAllowed(slot, header.moduleId)) {
251 if (logFn) logFn(slot, "mismatched module", ctx);
252 continue;
253 }
254
255 CacheEntry& entry = chunk[i];
256 entry.moduleId = header.moduleId;
257 entry.flags = static_cast<uint8_t>(header.flags | FLAG_USED);
258 strncpy(entry.name, header.name, sizeof(entry.name) - 1);
259 entry.name[sizeof(entry.name) - 1] = '\0';
260 if (logFn) logFn(slot, entry.name, ctx);
261 continue;
262 }
263
264 uint8_t temp[4];
265 uint16_t readLen = 0;
266 auto rawRes = secureElement_->rmemRead(slot, temp, sizeof(temp), &readLen);
267 if (rawRes == cdc::hal::SeResult::OK && readLen > 0) {
268 if (logFn) logFn(slot, "invalid header", ctx);
269 } else if (rawRes != cdc::hal::SeResult::SLOT_EMPTY) {
270 if (logFn) logFn(slot, "read failed", ctx);
271 }
272 }
273 if (!saveChunk(chunkIndex, chunk)) {
274 if (logFn) logFn(slotBase, "nvs write failed", ctx);
275 return false;
276 }
277 }
278
279 cacheValid_ = saveHeader();
280 return cacheValid_;
281}
282
288 if (!secureElement_) {
289 LOG_E(TAG, "No secure element set");
290 return false;
291 }
292
293 CacheEntry entries[CHUNK_SLOTS] = {};
294 uint16_t totalChunks =
295 static_cast<uint16_t>((TropicSlotMap::instance().rmemMax() + 1u) / CHUNK_SLOTS);
296
297 for (uint16_t chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
298 if (!loadChunk(chunkIndex, entries)) {
299 return false;
300 }
301 uint16_t slotBase = chunkIndex * CHUNK_SLOTS;
302 bool changed = false;
303 for (uint16_t i = 0; i < CHUNK_SLOTS; i++) {
304 uint16_t slot = static_cast<uint16_t>(slotBase + i);
305 CacheEntry& entry = entries[i];
306 if (!isEntryUsed(entry)) continue;
307 if (isEntryAllowed(slot, entry.moduleId)) continue;
308
309 LOG_W(TAG, "Cleanup: slot %u has mismatched module %u", slot, entry.moduleId);
310 secureElement_->rmemErase(slot);
311 memset(&entry, 0, sizeof(entry));
312 changed = true;
313 }
314 if (changed) {
315 if (!saveChunk(chunkIndex, entries)) {
316 return false;
317 }
318 }
319 }
320
321 return rebuild();
322}
323
328bool TropicStorage::loadHeader() {
329 NvsScope nvs(NVS_NAMESPACE, NVS_READONLY);
330 if (!nvs) return false;
331 size_t len = sizeof(CacheHeader);
332 CacheHeader stored = {};
333 esp_err_t err = nvs_get_blob(nvs, NVS_KEY_HEADER, &stored, &len);
334 if (err != ESP_OK || len != sizeof(CacheHeader)) {
335 return false;
336 }
337
338 if (stored.version != CACHE_VERSION || stored.chunkSlots != CHUNK_SLOTS ||
339 stored.entrySize != sizeof(CacheEntry)) {
340 return false;
341 }
342 if (stored.mapSignature != computeMapSignature()) {
343 return false;
344 }
345 header_ = stored;
346 return true;
347}
348
353bool TropicStorage::saveHeader() {
354 NvsScope nvs(NVS_NAMESPACE, NVS_READWRITE);
355 if (!nvs) return false;
356 esp_err_t err = nvs_set_blob(nvs, NVS_KEY_HEADER, &header_, sizeof(header_));
357 if (err == ESP_OK) err = nvs.commit();
358 return err == ESP_OK;
359}
360
367bool TropicStorage::loadChunk(uint16_t chunkIndex, CacheEntry* entries) {
368 if (!entries) return false;
369 memset(entries, 0, sizeof(CacheEntry) * CHUNK_SLOTS);
370
371 NvsScope nvs(NVS_NAMESPACE, NVS_READONLY);
372 if (!nvs) return true;
373 char key[12];
374 snprintf(key, sizeof(key), "c%u", chunkIndex);
375 size_t len = sizeof(CacheEntry) * CHUNK_SLOTS;
376 esp_err_t err = nvs_get_blob(nvs, key, entries, &len);
377
378 if (err != ESP_OK || len != sizeof(CacheEntry) * CHUNK_SLOTS) {
379 memset(entries, 0, sizeof(CacheEntry) * CHUNK_SLOTS);
380 return true;
381 }
382 return true;
383}
384
391bool TropicStorage::saveChunk(uint16_t chunkIndex, const CacheEntry* entries) {
392 if (!entries) return false;
393 NvsScope nvs(NVS_NAMESPACE, NVS_READWRITE);
394 if (!nvs) return false;
395 char key[12];
396 snprintf(key, sizeof(key), "c%u", chunkIndex);
397 esp_err_t err = nvs_set_blob(nvs, key, entries, sizeof(CacheEntry) * CHUNK_SLOTS);
398 if (err == ESP_OK) err = nvs.commit();
399 return err == ESP_OK;
400}
401
406uint32_t TropicStorage::computeMapSignature() const {
407 // FNV-1a 32-bit over the underlying slot-map signature.
409 cdc::core::hash::fnv1a_mix_u32(hash, TropicSlotMap::instance().computeMapSignature());
410 return hash;
411}
412
419bool TropicStorage::setEntry(uint16_t slot, const CacheEntry& entry) {
420 uint16_t chunkIndex = slot / CHUNK_SLOTS;
421 uint16_t offset = slot % CHUNK_SLOTS;
422 CacheEntry entries[CHUNK_SLOTS] = {};
423 if (!loadChunk(chunkIndex, entries)) return false;
424 entries[offset] = entry;
425 return saveChunk(chunkIndex, entries);
426}
427
434bool TropicStorage::getEntry(uint16_t slot, CacheEntry* entry) {
435 if (!entry) return false;
436 uint16_t chunkIndex = slot / CHUNK_SLOTS;
437 uint16_t offset = slot % CHUNK_SLOTS;
438 CacheEntry entries[CHUNK_SLOTS] = {};
439 if (!loadChunk(chunkIndex, entries)) return false;
440 *entry = entries[offset];
441 return true;
442}
443
449bool TropicStorage::isEntryUsed(const CacheEntry& entry) const {
450 return (entry.flags & FLAG_USED) != 0;
451}
452
459bool TropicStorage::isEntryAllowed(uint16_t slot, uint8_t moduleId) const {
461}
462
463} // namespace cdc::core
static const char * TAG
Centralized non-cryptographic hash utilities.
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
uint8_t flags
uint8_t moduleId
Shared RAII wrappers for firmware resources.
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
Definition cdc_log.h:146
#define LOG_E(tag, fmt,...)
Definition cdc_log.h:145
RAII wrapper for an NVS handle.
Definition Raii.h:106
static TropicSlotMap & instance()
Returns singleton Tropic slot-map instance.
uint16_t rmemMax() const
Returns maximum RMEM slot index.
bool isRmemAllowedForModuleId(uint16_t slot, uint8_t moduleId) const
Checks whether RMEM slot is allowed for given module id.
static constexpr uint16_t CHUNK_SLOTS
bool rebuild()
Rebuilds cache contents from secure-element R-MEM without verbose logging.
void stop() override
Stops cache service.
bool writeSlot(uint8_t moduleId, uint16_t slot, const char *name, uint8_t flags)
Writes or updates cached metadata entry for one slot.
bool eraseSlot(uint8_t moduleId, uint16_t slot)
Clears cached metadata entry for one slot.
static TropicStorage & instance()
Returns singleton instance of TROPIC metadata cache manager.
bool forEachSlot(uint8_t moduleId, SlotCallback cb, void *ctx)
Iterates all cached slots for one module across its allowed range.
void(*)(uint16_t slot, const CacheEntry &entry, void *ctx) SlotCallback
bool getSlot(uint8_t moduleId, uint16_t index, SlotCallback cb, void *ctx)
Resolves one module-relative index to slot entry and invokes callback.
bool start() override
Starts cache service, initializing first if required.
bool init() override
Initializes cache metadata and validates persisted cache header.
void(*)(uint16_t slot, const char *message, void *ctx) RebuildLogFn
static constexpr uint8_t FLAG_USED
bool rebuildVerbose(RebuildLogFn logFn, void *ctx)
Rebuilds cache contents from secure-element R-MEM with optional logging callback.
bool cleanup()
Removes cache entries and chip records that violate slot/module mapping.
static constexpr uint32_t FNV1A_32_OFFSET_BASIS
FNV-1a 32-bit constants (Fowler/Noll/Vo).
Definition Hash.h:20
void fnv1a_mix_u32(uint32_t &hash, uint32_t value)
Mixes a 32-bit word into a running FNV-1a 32-bit hash.
Definition Hash.h:41
static constexpr const char * NVS_NAMESPACE
static constexpr uint8_t CACHE_VERSION
static constexpr const char * NVS_KEY_HEADER
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]