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.
This commit is contained in:
1 parent
7e5eabe81c
commit
65820d0dd2
13 files changed
+515
-9
No files matched your search
@@ -371,7 +371,7 @@ static void cleanupAtomicTempFiles(FILESYSTEM* fs) {
|
||||
"/_main.id.tmp", "/new_prefs.tmp", "/channels2.tmp",
|
||||
"/contacts3.tmp", "/contacts4.mig.tmp", "/adv_blobs.tmp",
|
||||
"/.extrafs.mig.tmp", "/extrafs.badpages.tmp",
|
||||
"/extrafs.badpages.bak.tmp"};
|
||||
"/extrafs.badpages.bak.tmp", "/mesh_mt.tmp"};
|
||||
for (size_t i = 0; i < sizeof(fixed_temp_paths) / sizeof(fixed_temp_paths[0]); i++) {
|
||||
if (fs->exists(fixed_temp_paths[i])) fs->remove(fixed_temp_paths[i]);
|
||||
}
|
||||
@@ -573,6 +573,56 @@ void DataStore::useVolatilePrimaryFS(FILESYSTEM& fs) {
|
||||
_contact_load_incomplete = false;
|
||||
_prefs_load_incomplete = false;
|
||||
}
|
||||
|
||||
bool DataStore::loadMeshtextSettings(uint8_t image[40], size_t& size) {
|
||||
size = 0;
|
||||
if (image == nullptr || _fs == nullptr || _primary_storage_unavailable
|
||||
|| _volatile_primary_fs) return false;
|
||||
bool present = false;
|
||||
uint32_t stored_size = 0;
|
||||
if (!contactPathPresence(_fs, "/mesh_mt", present, &stored_size)) return false;
|
||||
if (!present) return true;
|
||||
// This backend has never stored the legacy ESP32-only NVS formats. Do not
|
||||
// accept a truncated image, directory or uncommitted .tmp as fresh defaults.
|
||||
if (stored_size != 40) return false;
|
||||
for (uint8_t attempt = 0; attempt < 3; ++attempt) {
|
||||
File file = openRead(_fs, "/mesh_mt");
|
||||
if (!file) continue;
|
||||
uint8_t loaded[40];
|
||||
const bool ok = !file.isDirectory() && file.size() == sizeof(loaded)
|
||||
&& file.read(loaded, sizeof(loaded)) == sizeof(loaded);
|
||||
file.close();
|
||||
if (ok) {
|
||||
memcpy(image, loaded, sizeof(loaded));
|
||||
size = sizeof(loaded);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DataStore::saveMeshtextSettings(const uint8_t image[40]) {
|
||||
if (image == nullptr || _fs == nullptr || _primary_storage_unavailable
|
||||
|| _volatile_primary_fs) return false;
|
||||
bool present = false;
|
||||
if (!contactPathPresence(_fs, "/mesh_mt", present)) return false;
|
||||
if (!contactPathPresence(_fs, "/mesh_mt.tmp", present)) return false;
|
||||
if (present && !_fs->remove("/mesh_mt.tmp")) return false;
|
||||
mesh::AtomicFileWriter file(_fs, "/mesh_mt");
|
||||
return file && file.write(image, 40) == 40 && file.commit();
|
||||
}
|
||||
|
||||
bool DataStore::eraseMeshtextSettings() {
|
||||
if (_fs == nullptr || _primary_storage_unavailable || _volatile_primary_fs) return false;
|
||||
bool present = false;
|
||||
// A staging file must never become a recovery source after explicit erase.
|
||||
for (const char* path : {"/mesh_mt.tmp", "/mesh_mt"}) {
|
||||
if (!contactPathPresence(_fs, path, present)) return false;
|
||||
if (present && !_fs->remove(path)) return false;
|
||||
if (!contactPathPresence(_fs, path, present) || present) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void DataStore::disableSecondaryFS(bool authority_unknown) {
|
||||
|
||||
@@ -188,6 +188,11 @@ public:
|
||||
#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);
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#include "MyMesh.h"
|
||||
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT
|
||||
#if defined(NRF52_PLATFORM)
|
||||
#include <helpers/NRF52Crypto.h>
|
||||
#else
|
||||
#include <Preferences.h>
|
||||
#include <esp_system.h>
|
||||
#endif
|
||||
#include <helpers/CLICommandUtils.h>
|
||||
#include <helpers/UTF8Helpers.h>
|
||||
|
||||
@@ -98,15 +102,31 @@ void MyMesh::beginMeshtext() {
|
||||
refreshMeshtextIdentity();
|
||||
// Independent random boot seed, then monotonic IDs. Never reuse a packet ID
|
||||
// within this boot (including failed admission) with the same CTR sender.
|
||||
#if defined(NRF52_PLATFORM)
|
||||
getRNG()->random(reinterpret_cast<uint8_t*>(&_mt_packet_id), sizeof(_mt_packet_id));
|
||||
#if defined(USE_CC310_HW_CRYPTO)
|
||||
mesh::mixCC310Random(reinterpret_cast<uint8_t*>(&_mt_packet_id), sizeof(_mt_packet_id));
|
||||
#endif
|
||||
#else
|
||||
_mt_packet_id = esp_random();
|
||||
#endif
|
||||
if (!_mt_packet_id) _mt_packet_id = 1;
|
||||
uint8_t image[SettingsSize] = {}, expected[SettingsSize] = {};
|
||||
#if defined(NRF52_PLATFORM)
|
||||
size_t size = 0;
|
||||
if (!_store->loadMeshtextSettings(image, size)) { _mt_settings_valid = false; return; }
|
||||
#else
|
||||
Preferences prefs;
|
||||
if (!prefs.begin("mesh-mt", false)) { _mt_settings_valid = false; return; }
|
||||
const size_t size = prefs.getBytesLength("settings");
|
||||
#endif
|
||||
if (size) {
|
||||
uint8_t image[SettingsSize] = {}, expected[SettingsSize] = {};
|
||||
#if defined(NRF52_PLATFORM)
|
||||
_mt_settings_valid = size == SettingsSize;
|
||||
#else
|
||||
_mt_settings_valid = (size == SettingsSize || size == LegacySettingsSize)
|
||||
&& prefs.getBytes("settings", image, size) == size;
|
||||
#endif
|
||||
if (_mt_settings_valid) {
|
||||
const auto region = static_cast<mesh::mt::Region>(image[4]);
|
||||
const bool legacy = size == LegacySettingsSize;
|
||||
@@ -138,7 +158,9 @@ void MyMesh::beginMeshtext() {
|
||||
}
|
||||
}
|
||||
}
|
||||
#if !defined(NRF52_PLATFORM)
|
||||
prefs.end();
|
||||
#endif
|
||||
refreshMeshtextProfile();
|
||||
}
|
||||
|
||||
@@ -149,8 +171,12 @@ bool MyMesh::saveMeshtextSettings(mesh::mt::Region region, uint32_t slot, uint8_
|
||||
if (node_override == UINT32_MAX) node_override = _mt_node_override;
|
||||
if (region > mesh::mt::Region::ANZ || slot > 1024 || hops > 7 || !validShortName(short_name)
|
||||
|| !validNodeOverride(node_override) || hasForeignRadioFrame()) return false;
|
||||
uint8_t image[SettingsSize], verify[SettingsSize];
|
||||
uint8_t image[SettingsSize];
|
||||
makeSettings(image, region, slot, hops, short_name, node_override);
|
||||
#if defined(NRF52_PLATFORM)
|
||||
const bool ok = _store->saveMeshtextSettings(image);
|
||||
#else
|
||||
uint8_t verify[SettingsSize];
|
||||
Preferences prefs;
|
||||
if (!prefs.begin("mesh-mt", false)) return false;
|
||||
// NVS commits this one blob atomically; never split region and slot into
|
||||
@@ -159,8 +185,9 @@ bool MyMesh::saveMeshtextSettings(mesh::mt::Region region, uint32_t slot, uint8_
|
||||
&& prefs.getBytes("settings", verify, sizeof(verify)) == sizeof(verify)
|
||||
&& memcmp(image, verify, sizeof(image)) == 0;
|
||||
prefs.end();
|
||||
#endif
|
||||
if (!ok) {
|
||||
// A failed read-back cannot prove whether NVS committed. Suspend the
|
||||
// A failed read-back cannot prove whether storage committed. Suspend the
|
||||
// backend rather than silently use one network now and another at boot.
|
||||
_mt_settings_valid = false;
|
||||
_mt_profile_dirty = true;
|
||||
@@ -188,6 +215,9 @@ bool MyMesh::eraseMeshtextSettings() {
|
||||
_mt_seen.clear();
|
||||
_radio_profiles.clearAutoSecondary();
|
||||
_radio_profiles.clearSecondaryProtocol();
|
||||
#if defined(NRF52_PLATFORM)
|
||||
const bool ok = _store->eraseMeshtextSettings();
|
||||
#else
|
||||
Preferences prefs;
|
||||
if (!prefs.begin("mesh-mt", false)) {
|
||||
_mt_status = "Meshtext settings erase failed";
|
||||
@@ -195,6 +225,7 @@ bool MyMesh::eraseMeshtextSettings() {
|
||||
}
|
||||
const bool ok = !prefs.getBytesLength("settings") || prefs.remove("settings");
|
||||
prefs.end();
|
||||
#endif
|
||||
if (ok) {
|
||||
_mt_region = mesh::mt::Region::Unset; _mt_slot = 0; _mt_hop_limit = 7;
|
||||
_mt_short_name[0] = 0; _mt_node_override = 0; _mt_node = _mt_default_node;
|
||||
|
||||
Reference in new issue
Block a user