Files
mikecarper 9ce2a076eb Include and qualify Meshtext in RAK4631 Full Companion
Port Meshtext settings to verified atomic nRF52 storage and use the existing radio RNG and hardware entropy. Keep the ExtraFS boundary, GPS, contacts, channel count and 8 KiB stack unchanged; omit unused library data to fit the image. Require linked codec and Companion transport evidence and build the real RAK Full profile in CI. Add configuration and storage-failure regression coverage.
2026-10-12 22:39:00 -07:00

245 lines
9.2 KiB
C++

#pragma once
#include <helpers/IdentityStore.h>
#include <helpers/ContactInfo.h>
#include <helpers/ChannelDetails.h>
#include <helpers/PersistentStoreFormat.h>
#include "NodePrefs.h"
#if MESH_CONTACT_CACHE
#include <helpers/ContactSecretCache.h>
#endif
#include <helpers/CompanionReaderConfig.h>
#if defined(ESP32_PLATFORM)
namespace mesh { class ContactFileTransaction; }
#endif
#if COMPANION_FEATURE_READER
#include <helpers/bible/Reader.h>
#endif
class DataStoreHost {
public:
virtual bool onContactLoaded(const ContactInfo& contact) =0;
virtual bool getContactForSave(uint32_t idx, ContactInfo& contact) =0;
virtual ContactInfo* getContactForStore(uint32_t idx) =0;
virtual void onContactCacheFlushed() {}
virtual bool onChannelLoaded(uint8_t channel_idx, const ChannelDetails& ch) =0;
virtual bool getChannelForSave(uint8_t channel_idx, ChannelDetails& ch) =0;
};
class DataStore
#if MESH_CONTACT_CACHE
: public mesh::ContactPathBackend
#if MESH_CONTACT_SECRET_FLASH_CACHE
, public mesh::ContactSecretBackend
#endif
#endif
{
FILESYSTEM* _fs;
FILESYSTEM* _fsExtra;
// Keep the configured secondary even when normal I/O falls back to the
// primary filesystem. Boot recovery and explicit local repair must still
// be able to address the inactive on-chip ExtraFS.
FILESYSTEM* _configuredFsExtra;
mesh::RTCClock* _clock;
IdentityStore identity_store;
bool _identity_creation_blocked = false;
bool _prefs_load_incomplete = false;
#if defined(ESP32_PLATFORM)
const char* _prefs_recovery_source = nullptr;
const char* _channel_recovery_source = nullptr;
mesh::ContactFileTransaction* _contact_write = nullptr;
DataStoreHost* _contact_write_host = nullptr;
bool (*_contact_write_filter)(const ContactInfo&) = nullptr;
uint32_t _contact_write_revision = 0;
uint32_t _contact_write_active_revision = 0;
uint32_t _contact_write_index = 0;
bool _contact_write_verifying = false;
bool _contact_write_requested = false;
bool _contact_write_servicing = false;
void cancelContactWrite();
bool serviceContactWrite(DataStoreHost* host,
bool (*filter)(const ContactInfo&));
#endif
#if defined(ESP32_PLATFORM)
// Advert packets are reconstructable cache data. Keep accepted updates in
// RAM while their filesystem operations run on separate loop passes.
struct AdvertWriteState {
static constexpr uint8_t CAPACITY = 4;
struct Slot {
uint8_t key[8] = {};
uint8_t bytes[255] = {};
uint32_t revision = 0;
uint8_t key_len = 0;
uint8_t len = 0;
} slots[CAPACITY];
enum class Stage : uint8_t {
Idle, TargetProbe, BackupProbe, RestoreBackup, RemoveBackup,
TempProbe, RemoveTemp, OpenWrite, ConfigureWrite, Write, CloseWrite,
OpenVerify, ConfigureVerify, VerifySize, Verify, CloseVerify,
PublishTargetProbe, PublishBackupProbe, PublishRemoveBackup,
PublishBackup, Publish, PublishCleanup, CancelClose
} stage = Stage::Idle;
File file;
uint8_t scratch[64] = {};
uint8_t active_key[8] = {};
uint8_t active_key_len = 0;
char target[24] = {};
char backup[28] = {};
uint32_t revision = 0;
uint32_t active_revision = 0;
uint32_t retry_at = 0;
uint8_t active = CAPACITY;
uint8_t next = 0;
uint8_t offset = 0;
uint8_t failures = 0;
bool target_present = false;
bool backup_present = false;
bool temp_present = false;
bool cancelling = false;
bool synchronous_io = false;
~AdvertWriteState() { file.close(); }
void pause() {
file.close();
stage = Stage::Idle;
active = CAPACITY;
retry_at = 0;
}
void clear() {
pause();
for (auto& slot : slots) slot.len = 0;
failures = 0;
}
} _advert_write;
void invalidateAdvertWrite(const uint8_t key[], int key_len);
void retireAdvertWrite(const uint8_t key[], int key_len);
#endif
#if !defined(NRF52_PLATFORM)
bool _channel_load_incomplete = false;
#if !MESH_CONTACT_CACHE
bool _uncached_contact_load_incomplete = false;
#endif
#endif
#if MESH_CONTACT_CACHE
DataStoreHost* _cache_host = nullptr;
bool _cache_load_incomplete = false;
#if defined(ESP32_PLATFORM)
File _contact_path_reader;
#endif
bool readStoredPath(uint16_t source, uint8_t path[64]) override;
bool flushCachedPaths() override;
#if defined(ESP32_PLATFORM)
bool cancelCooperativeWrite() override;
#endif
#if MESH_CONTACT_SECRET_FLASH_CACHE
uint32_t _secret_retry_at = 0;
bool readSavedSecret(const uint8_t peer[32], const uint8_t identity[32],
uint8_t secret[32]) override;
bool saveSecret(const uint8_t peer[32], const uint8_t identity[32],
const uint8_t secret[32]) override;
uint16_t secretSlot(const uint8_t peer[32]) const;
#endif
#endif
#if defined(NRF52_PLATFORM)
mesh::storage::ContactSlotMap _contact_slots;
mesh::storage::DirtyPageSet _dirty_contact_pages;
mesh::storage::DirtyPageSet _unread_contact_pages;
bool _contact_load_incomplete = false;
bool _primary_storage_unavailable = false;
bool _secondary_authority_unknown = false;
bool _volatile_primary_fs = false;
uint32_t _contact_page_generations[mesh::storage::CONTACT_PAGE_COUNT];
bool _legacy_contacts_pending_cleanup;
bool _legacy_migration_ready;
uint16_t _legacy_contact_count;
bool prepareLegacyContactMigration();
bool loadContactPages(DataStoreHost* host, uint16_t minimum_slot,
uint32_t expected_page_mask);
bool writeContactPage(DataStoreHost* host, uint8_t page,
bool (*filter)(const ContactInfo& c));
bool truncateLegacyContacts(uint16_t remaining_contacts);
void resetContactPageState(bool clear_incomplete = false);
#if defined(EXTRAFS) && !defined(QSPIFLASH)
bool recoverInternalExtraFSOnBoot();
bool reinitializeInternalExtraFS(bool scan_physical_pages = false);
#endif
#endif
bool loadPrefsInt(const char *filename, CompanionNodePrefs& prefs,
double& node_lat, double& node_lon);
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
void checkAdvBlobFile();
#endif
public:
DataStore(FILESYSTEM& fs, mesh::RTCClock& clock);
~DataStore();
DataStore(FILESYSTEM& fs, FILESYSTEM& fsExtra, mesh::RTCClock& clock);
void begin();
bool formatFileSystem();
bool repairInternalExtraFS();
#if defined(NRF52_PLATFORM) && defined(EXTRAFS) && !defined(QSPIFLASH)
bool requestInternalExtraFSBootScan();
bool formatInternalExtraFSHealth(char* reply, size_t reply_size) const;
#endif
FILESYSTEM* getPrimaryFS() const { return _fs; }
FILESYSTEM* getSecondaryFS() const { return _fsExtra; }
#if defined(NRF52_PLATFORM)
void useVolatilePrimaryFS(FILESYSTEM& fs);
bool isVolatilePrimaryFS() const { return _volatile_primary_fs; }
// Meshtext settings belong to the primary store, alongside the identity.
// An absent record is size zero; unreadable or volatile storage is failure.
bool loadMeshtextSettings(uint8_t image[40], size_t& size);
bool saveMeshtextSettings(const uint8_t image[40]);
bool eraseMeshtextSettings();
#endif
void markPrimaryFSUnavailable();
void disableSecondaryFS(bool authority_unknown = true);
bool loadMainIdentity(mesh::LocalIdentity &identity);
bool canCreateMainIdentity() const;
bool saveMainIdentity(const mesh::LocalIdentity &identity);
bool loadPrefs(CompanionNodePrefs& prefs, double& node_lat,
double& node_lon);
bool savePrefs(const CompanionNodePrefs& prefs, double node_lat, double node_lon);
#if COMPANION_FEATURE_READER
bool loadReaderBookmark(mesh::bible::Position& pos);
bool saveReaderBookmark(mesh::bible::Position pos);
#endif
void loadContacts(DataStoreHost* host);
bool saveContacts(DataStoreHost* host, bool (*filter)(const ContactInfo& c) = NULL);
bool markContactDirty(const ContactInfo& contact);
bool releaseContact(const ContactInfo& contact);
bool restoreContactSlot(const ContactInfo& contact, uint16_t slot);
bool serviceContactWrites(DataStoreHost* host, bool (*filter)(const ContactInfo& c) = NULL);
bool flushContactWrites(DataStoreHost* host, bool (*filter)(const ContactInfo& c) = NULL);
bool hasPendingContactWrites() const;
bool hasIncompleteContactLoad() const;
void loadChannels(DataStoreHost* host);
bool saveChannels(DataStoreHost* host);
bool migrateToSecondaryFS();
uint8_t getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]);
bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len);
bool deleteBlobByKey(const uint8_t key[], int key_len);
#if defined(ESP32_PLATFORM)
bool queueAdvertByKey(const uint8_t key[], int key_len,
const uint8_t src_buf[], uint8_t len);
bool serviceAdvertWrites(uint32_t now);
bool flushAdvertWrites(uint32_t now = 0);
bool hasPendingAdvertWrites() const;
bool isAdvertWriteDue(uint32_t now) const;
bool consumeSynchronousAdvertIO();
#endif
File openRead(const char* filename);
File openRead(FILESYSTEM* fs, const char* filename);
File openDirectory(const char* path);
File openDirectory(FILESYSTEM* fs, const char* path);
bool removeFile(const char* filename);
bool removeFile(FILESYSTEM* fs, const char* filename);
uint32_t getStorageUsedKb() const;
uint32_t getStorageTotalKb() const;
private:
FILESYSTEM* _getContactsChannelsFS() const { if (_fsExtra) return _fsExtra; return _fs;};
};