Preserve durable region policy on failed replacement saves

This commit is contained in:
mikecarper committed 2026-10-16 11:56:00 -07:00
1 parent 4b81a9d5e0
commit df0e7c3f89
7 files changed
+661 -15

No files matched your search

+1
View File
@@ -184,6 +184,7 @@ jobs:
- name: Verify parser, delivery, storage, permissions, clock and radio stability
run: |
python3 -B test/test_protocol_parser_bounds.py -v
python3 -B test/test_region_map_transactions.py -v
python3 -B test/test_cli_error_bounds.py -v
python3 -B test/test_common_cli_argument_bounds.py -v
python3 -B test/test_flood_retry_bucket_cli.py -v
+35 -15
View File
@@ -1,8 +1,13 @@
#include "RegionMap.h"
#include <helpers/RegionNameUtils.h>
#include <helpers/TxtDataHelpers.h>
#if defined(NRF52_PLATFORM)
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
#include <helpers/AtomicFileWriter.h>
#elif defined(ESP32_PLATFORM) || defined(RP2040_PLATFORM)
#include <helpers/ContactFileTransaction.h>
#endif
#if defined(ESP32_PLATFORM)
#include <helpers/FilePresence.h>
#endif
#include <SHA256.h>
@@ -62,25 +67,35 @@ static const char* skip_hash(const char* name) {
return RegionNameUtils::canonical(name);
}
#if !defined(NRF52_PLATFORM)
#if !defined(NRF52_PLATFORM) && !defined(STM32_PLATFORM) && !defined(ESP32_PLATFORM) && !defined(RP2040_PLATFORM)
static File openWrite(FILESYSTEM* _fs, const char* filename) {
#if defined(STM32_PLATFORM)
_fs->remove(filename);
return _fs->open(filename, FILE_O_WRITE);
#elif defined(RP2040_PLATFORM)
return _fs->open(filename, "w");
#else
return _fs->open(filename, "w", true);
#endif
return _fs->open(filename, "w", true);
}
#endif
static bool regionPathSupported(FILESYSTEM* fs, const char* path) {
if (fs == nullptr || path == nullptr || path[0] == 0) return false;
#if defined(ESP32_PLATFORM) || defined(RP2040_PLATFORM)
// Transaction siblings have 48 bytes, including the suffix and terminator.
if (strlen(path) > 43) return false;
#endif
return true;
}
bool RegionMap::load(FILESYSTEM* _fs, const char* path) {
if (_fs->exists(path ? path : "/regions2")) {
const char* target_path = path ? path : "/regions2";
if (!regionPathSupported(_fs, target_path)) return false;
#if defined(ESP32_PLATFORM)
if (!mesh::ContactFileTransaction::recover(
_fs, target_path, mesh::filePresence<FILESYSTEM>)) return false;
#elif defined(RP2040_PLATFORM)
if (!mesh::ContactFileTransaction::recover(_fs, target_path)) return false;
#endif
if (_fs->exists(target_path)) {
#if defined(RP2040_PLATFORM)
File file = _fs->open(path ? path : "/regions2", "r");
File file = _fs->open(target_path, "r");
#else
File file = _fs->open(path ? path : "/regions2");
File file = _fs->open(target_path);
#endif
if (file) {
@@ -191,8 +206,13 @@ bool RegionMap::load(FILESYSTEM* _fs, const char* path) {
bool RegionMap::save(FILESYSTEM* _fs, const char* path) {
const char* target_path = path ? path : "/regions2";
#if defined(NRF52_PLATFORM)
if (!regionPathSupported(_fs, target_path)) return false;
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
mesh::AtomicFileWriter file(_fs, target_path);
#elif defined(ESP32_PLATFORM)
mesh::ContactFileTransaction file(_fs, target_path, mesh::filePresence<FILESYSTEM>);
#elif defined(RP2040_PLATFORM)
mesh::ContactFileTransaction file(_fs, target_path);
#else
File file = openWrite(_fs, target_path);
#endif
@@ -218,7 +238,7 @@ bool RegionMap::save(FILESYSTEM* _fs, const char* path) {
if (!success) break; // write failed
}
}
#if defined(NRF52_PLATFORM)
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) || defined(ESP32_PLATFORM) || defined(RP2040_PLATFORM)
success = file.commit(success);
#else
file.close();
+56
View File
@@ -0,0 +1,56 @@
// Exact pre-fix openWrite()/save() bodies from RegionMap.cpp. Keep the old
// destructive writer as an executable negative control for the durable-policy
// assertions, including STM32's removal before opening the replacement.
// Source RegionMap.cpp SHA256 before the fix:
// c6a1ec995dcd072462410476e04756c3ede95c28964244b76b84dafb6b1c963f
#if !defined(NRF52_PLATFORM)
static File openWrite(FILESYSTEM* _fs, const char* filename) {
#if defined(STM32_PLATFORM)
_fs->remove(filename);
return _fs->open(filename, FILE_O_WRITE);
#elif defined(RP2040_PLATFORM)
return _fs->open(filename, "w");
#else
return _fs->open(filename, "w", true);
#endif
}
#endif
bool RegionMap::save(FILESYSTEM* _fs, const char* path) {
const char* target_path = path ? path : "/regions2";
#if defined(NRF52_PLATFORM)
mesh::AtomicFileWriter file(_fs, target_path);
#else
File file = openWrite(_fs, target_path);
#endif
if (file) {
uint8_t pad[128];
memset(pad, 0, sizeof(pad));
bool success = file.write(pad, 3) == 3; // reserved header
success = success && file.write((uint8_t *) &default_id, sizeof(default_id)) == sizeof(default_id);
success = success && file.write((uint8_t *) &home_id, sizeof(home_id)) == sizeof(home_id);
success = success && file.write((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags);
success = success && file.write((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id);
if (success) {
for (int i = 0; i < num_regions; i++) {
auto r = &regions[i];
success = file.write((uint8_t *) &r->id, sizeof(r->id)) == sizeof(r->id);
success = success && file.write((uint8_t *) &r->parent, sizeof(r->parent)) == sizeof(r->parent);
success = success && file.write((uint8_t *) r->name, sizeof(r->name)) == sizeof(r->name);
success = success && file.write((uint8_t *) &r->flags, sizeof(r->flags)) == sizeof(r->flags);
success = success && file.write(pad, sizeof(pad)) == sizeof(pad);
if (!success) break; // write failed
}
}
#if defined(NRF52_PLATFORM)
success = file.commit(success);
#else
file.close();
#endif
return success;
}
return false; // failed
}
@@ -0,0 +1,120 @@
#pragma once
// Native adapter for the public File/FILESYSTEM operations used by RegionMap
// and the real AtomicFileWriter/ContactFileTransaction implementations.
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <map>
#include <set>
#include <string>
#include <vector>
class MemoryFS;
class File {
MemoryFS* fs_ = nullptr;
std::string path_;
size_t cursor_ = 0;
public:
File() = default;
explicit File(MemoryFS& fs) : fs_(&fs) {}
File(MemoryFS* fs, const char* path) : fs_(fs), path_(path) {}
bool open(const char* path, uint8_t mode);
operator bool() const { return fs_ != nullptr; }
size_t size() const;
bool isDirectory() const { return false; }
int read(uint8_t* data, size_t size);
int read() { uint8_t value; return read(&value, 1) == 1 ? value : -1; }
size_t write(const uint8_t* data, size_t size);
bool setBufferSize(size_t size) { return size != 0; }
void flush() {}
void close() { fs_ = nullptr; }
};
class MemoryFS {
public:
std::map<std::string, std::vector<uint8_t>> files;
std::set<std::string> failed_rename_sources;
std::set<std::string> metadata_errors;
bool fail_write_open = false;
bool fail_read_open = false;
int fail_write_after = -1;
size_t capacity = std::numeric_limits<size_t>::max();
unsigned write_calls = 0, open_writes = 0, remove_calls = 0, rename_calls = 0;
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) || defined(RP2040_PLATFORM)
bool rename_replaces = true;
#else
bool rename_replaces = false;
#endif
size_t used() const {
size_t total = 0;
for (const auto& item : files) total += item.second.size();
return total;
}
bool exists(const char* path) const { return files.count(path) != 0; }
bool remove(const char* path) {
++remove_calls;
return files.erase(path) != 0;
}
bool rename(const char* from, const char* to) {
++rename_calls;
if (failed_rename_sources.count(from) || !exists(from)
|| (!rename_replaces && exists(to))) return false;
files[to] = files.at(from);
files.erase(from);
return true;
}
File open(const char* path, const char* mode = "r", bool = false) {
if (*mode == 'w') {
++open_writes;
if (fail_write_open) return {};
files[path].clear();
} else if (fail_read_open) {
return {};
}
if (!exists(path)) return {};
return File(this, path);
}
File open(const char* path, uint8_t mode) {
return open(path, mode == 1 ? "w" : "r");
}
};
inline MemoryFS* metadata_filesystem = nullptr;
enum { FILE_O_READ = 0, FILE_O_WRITE = 1 };
inline bool File::open(const char* path, uint8_t mode) {
if (!fs_) return false;
*this = fs_->open(path, mode);
return fs_ != nullptr;
}
inline size_t File::size() const {
return fs_ ? fs_->files.at(path_).size() : 0;
}
inline int File::read(uint8_t* data, size_t size) {
if (!fs_) return -1;
const auto& bytes = fs_->files.at(path_);
size = std::min(size, bytes.size() - cursor_);
if (size != 0) std::memcpy(data, bytes.data() + cursor_, size);
cursor_ += size;
return static_cast<int>(size);
}
inline size_t File::write(const uint8_t* data, size_t size) {
if (!fs_) return 0;
++fs_->write_calls;
auto& bytes = fs_->files.at(path_);
if (fs_->fail_write_after >= 0) {
const size_t limit = static_cast<size_t>(fs_->fail_write_after);
size = bytes.size() >= limit ? 0 : std::min(size, limit - bytes.size());
}
const size_t used = fs_->used();
size = used >= fs_->capacity ? 0 : std::min(size, fs_->capacity - used);
if (size != 0) bytes.insert(bytes.end(), data, data + size);
return size;
}
#define FILESYSTEM MemoryFS
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <cerrno>
#include <cstring>
struct stat {};
inline int stat(const char* path, struct stat*) {
if (!metadata_filesystem || std::strncmp(path, "/spiffs", 7) != 0) {
errno = EIO;
return -1;
}
const std::string relative(path + 7);
if (metadata_filesystem->metadata_errors.count(relative)) {
errno = EIO;
return -1;
}
if (!metadata_filesystem->files.count(relative)) {
errno = ENOENT;
return -1;
}
return 0;
}
+317
View File
@@ -0,0 +1,317 @@
// Compile the actual RegionMap declarations and selected implementation bodies
// with the real platform transaction helper, not a second serializer.
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <helpers/RegionMap.h>
#include <helpers/RegionNameUtils.h>
#include <helpers/TxtDataHelpers.h>
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
#define ATOMIC_FILE_WRITER_IMPLEMENTATION
#include <helpers/AtomicFileWriter.h>
#else
#include <contact_transaction_under_test.h>
#if defined(ESP32_PLATFORM)
#include <helpers/FilePresence.h>
#endif
#endif
#include "region_under_test.inc"
static void require(bool condition, const char* message) {
if (!condition) {
std::fprintf(stderr, "FAIL: %s\n", message);
std::exit(1);
}
}
static void select(MemoryFS& fs) { metadata_filesystem = &fs; }
static bool has_policy(RegionMap& regions, bool candidate) {
if (regions.getCount() != (candidate ? 4 : 3)) return false;
const auto* root = regions.findById(11);
const auto* private_region = regions.findById(27);
const auto* local = regions.findById(35);
const auto* added = regions.findById(50);
if (!root || !private_region || !local) return false;
if (std::strcmp(root->name, "#Mountain") || root->parent != 0
|| root->flags != (candidate ? 0 : REGION_DENY_FLOOD | REGION_DENY_DIRECT)) return false;
if (std::strcmp(private_region->name, "$Private") || private_region->parent != 11
|| private_region->flags != REGION_DENY_DIRECT) return false;
if (std::strcmp(local->name, "Local") || local->parent != 0
|| local->flags != REGION_DENY_FLOOD) return false;
if (candidate && (!added || std::strcmp(added->name, "Candidate")
|| added->parent != 27 || added->flags != 0)) return false;
if (!candidate && added) return false;
if (!regions.getDefaultRegion() || !regions.getHomeRegion()) return false;
return regions.getWildcard().flags == (candidate ? 0 : REGION_DENY_FLOOD | REGION_DENY_DIRECT)
&& regions.getDefaultRegion()->id == (candidate ? 35 : 11)
&& regions.getHomeRegion()->id == (candidate ? 11 : 27);
}
struct Scenario {
TransportKeyStore keys;
MemoryFS fs;
RegionMap original{keys};
RegionMap candidate{keys};
std::vector<uint8_t> baseline;
std::vector<uint8_t> replacement;
std::string path;
explicit Scenario(const char* target = "/regions2") : path(target) {
select(fs);
RegionEntry* root = original.putRegion("#Mountain", 0, 11);
RegionEntry* private_region = original.putRegion("$Private", 11, 27);
RegionEntry* local = original.putRegion("Local", 0, 35);
require(root && private_region && local, "create baseline region hierarchy");
root->flags = REGION_DENY_FLOOD | REGION_DENY_DIRECT;
private_region->flags = REGION_DENY_DIRECT;
local->flags = REGION_DENY_FLOOD;
original.getWildcard().flags = REGION_DENY_FLOOD | REGION_DENY_DIRECT;
original.setDefaultRegion(root);
original.setHomeRegion(private_region);
require(original.save(&fs, path.c_str()), "write baseline regions");
baseline = fs.files.at(path);
require(baseline.size() == 10 + 3 * 164, "legacy region image size unchanged");
require(candidate.load(&fs, path.c_str()), "load candidate from durable baseline");
candidate.findById(11)->flags = 0;
candidate.getWildcard().flags = 0;
candidate.setDefaultRegion(candidate.findById(35));
candidate.setHomeRegion(candidate.findById(11));
RegionEntry* added = candidate.putRegion("Candidate", 27, 50);
require(added != nullptr, "create candidate region");
added->flags = 0;
MemoryFS healthy;
select(healthy);
require(candidate.save(&healthy, path.c_str()), "write candidate reference");
replacement = healthy.files.at(path);
select(fs);
}
void reboot(MemoryFS& storage, bool expected_candidate = false) {
select(storage);
RegionMap restarted(keys);
const bool loaded = restarted.load(&storage, path.c_str());
require(loaded && has_policy(restarted, expected_candidate),
expected_candidate ? "durable candidate region/default/home/wildcard policy survived"
: "durable old region/default/home/wildcard policy survived");
// The next auto-assigned ID is also durable state, not merely recomputed
// policy. Explicit IDs above leave 36 or 51 as the next allocation.
const RegionEntry* next = restarted.putRegion("AfterReboot", 0);
require(next && next->id == (expected_candidate ? 51 : 36),
"durable next region ID survived");
require(storage.files.at(path) == (expected_candidate ? replacement : baseline),
"exact durable public image retained");
}
};
static void healthy_save_and_reboot() {
Scenario test;
test.reboot(test.fs);
require(test.original.save(&test.fs), "repeated healthy region save");
test.reboot(test.fs);
require(test.candidate.save(&test.fs), "publish changed region policy");
test.reboot(test.fs, true);
require(!test.fs.exists("/regions2.tmp") && !test.fs.exists("/regions2.bak"),
"successful save cleaned transaction siblings");
}
static void invalid_inputs_do_not_mutate() {
Scenario test;
const auto before = test.fs.files;
const unsigned opens = test.fs.open_writes;
const unsigned removes = test.fs.remove_calls;
const unsigned renames = test.fs.rename_calls;
require(!test.candidate.save(nullptr), "null filesystem save refused");
require(!test.candidate.load(nullptr), "null filesystem load refused");
require(!test.candidate.save(&test.fs, ""), "empty target save refused");
require(!test.candidate.load(&test.fs, ""), "empty target load refused");
require(test.fs.files == before && test.fs.open_writes == opens
&& test.fs.remove_calls == removes && test.fs.rename_calls == renames,
"invalid input left namespace untouched");
require(has_policy(test.candidate, true), "invalid input retained caller's RAM policy");
test.reboot(test.fs);
}
static void refusal_and_short_writes() {
Scenario test;
{
MemoryFS storage = test.fs;
select(storage);
storage.fail_write_open = true;
require(!test.candidate.save(&storage), "replacement open refusal reported");
storage.fail_write_open = false;
test.reboot(storage);
}
// Exhaust every byte offset. This includes every reserved/header field,
// every region field/padding boundary, and clean record-boundary prefixes.
for (size_t boundary = 0; boundary < test.replacement.size(); ++boundary) {
MemoryFS storage = test.fs;
select(storage);
storage.fail_write_after = static_cast<int>(boundary);
require(!test.candidate.save(&storage), "ordinary short write reported");
storage.fail_write_after = -1;
test.reboot(storage);
require(!storage.exists("/regions2.tmp"), "short write cleaned staging file");
require(has_policy(test.candidate, true), "save failure retained caller's RAM policy");
}
// A full filesystem is independent of the File::write short-write hook:
// committed bytes consume capacity before each staging write is attempted.
for (size_t available = 0; available < test.replacement.size(); ++available) {
MemoryFS storage = test.fs;
select(storage);
storage.capacity = storage.used() + available;
require(!test.candidate.save(&storage), "full-storage write failure reported");
storage.capacity = std::numeric_limits<size_t>::max();
test.reboot(storage);
}
std::printf("PASS: %zu short-write offsets and %zu full-storage capacities\n",
test.replacement.size(), test.replacement.size());
}
static void staged_readback_and_publication_failure() {
Scenario test;
{
MemoryFS storage = test.fs;
select(storage);
storage.fail_read_open = true;
require(!test.candidate.save(&storage), "staged readback refusal reported");
storage.fail_read_open = false;
test.reboot(storage);
}
{
MemoryFS storage = test.fs;
select(storage);
storage.failed_rename_sources.insert("/regions2.tmp");
require(!test.candidate.save(&storage), "known unchanged publication reported");
storage.failed_rename_sources.clear();
test.reboot(storage);
}
#if defined(ESP32_PLATFORM) || defined(RP2040_PLATFORM)
{
MemoryFS storage = test.fs;
select(storage);
storage.failed_rename_sources.insert("/regions2");
require(!test.candidate.save(&storage), "backup move refusal reported");
storage.failed_rename_sources.clear();
test.reboot(storage);
}
{
MemoryFS storage = test.fs;
select(storage);
storage.failed_rename_sources.insert("/regions2.tmp");
storage.failed_rename_sources.insert("/regions2.bak");
require(!test.candidate.save(&storage), "failed publication and rollback reported");
require(!storage.exists("/regions2") && storage.files.at("/regions2.bak") == test.baseline,
"rollback failure retained old recovery image");
storage.failed_rename_sources.clear();
test.reboot(storage);
}
{
MemoryFS storage = test.fs;
select(storage);
require(storage.rename("/regions2", "/regions2.bak"), "simulate reset in backup gap");
storage.files["/regions2.tmp"] = test.replacement;
test.reboot(storage);
require(!storage.exists("/regions2.bak"), "fresh load recovered backup gap");
}
#endif
}
static void custom_path_isolation() {
Scenario test("/regions-custom");
test.fs.files["/regions2"] = {0x12, 0x34};
test.fs.files["/regions2.tmp"] = {0x56};
test.fs.files["/regions2.bak"] = {0x78};
const auto unrelated = test.fs.files;
test.fs.fail_write_after = 9;
require(!test.candidate.save(&test.fs, test.path.c_str()), "custom path short write reported");
test.fs.fail_write_after = -1;
test.reboot(test.fs);
for (const char* name : {"/regions2", "/regions2.tmp", "/regions2.bak"}) {
require(test.fs.files.at(name) == unrelated.at(name), "custom path left default artifacts alone");
}
#if defined(ESP32_PLATFORM) || defined(RP2040_PLATFORM)
require(test.fs.rename(test.path.c_str(), (test.path + ".bak").c_str()),
"simulate custom backup gap");
test.fs.files[test.path + ".tmp"] = test.replacement;
test.reboot(test.fs);
for (const char* name : {"/regions2", "/regions2.tmp", "/regions2.bak"}) {
require(test.fs.files.at(name) == unrelated.at(name), "custom recovery left default artifacts alone");
}
// ContactFileTransaction has 48 bytes for each sibling pathname, including
// its suffix and terminator. Reject overlong paths before any recovery or
// staging can address a truncated name belonging to another file.
const std::string limit_path = "/" + std::string(42, 'x');
Scenario limit(limit_path.c_str());
require(limit.candidate.save(&limit.fs, limit.path.c_str()), "43-byte custom target fits");
limit.reboot(limit.fs, true);
for (size_t length : {size_t(44), size_t(80), size_t(128)}) {
const std::string path = "/" + std::string(length - 1, 'y');
MemoryFS storage;
select(storage);
storage.files[path] = test.baseline;
storage.files[path + ".tmp"] = test.replacement;
storage.files[path + ".bak"] = test.baseline;
storage.files[(path + ".tmp").substr(0, 47)] = {0x66};
storage.files[(path + ".bak").substr(0, 47)] = {0x77};
const auto before = storage.files;
require(!test.candidate.save(&storage, path.c_str()), "overlong custom save refused");
RegionMap restarted(test.keys);
require(!restarted.load(&storage, path.c_str()), "overlong custom load refused");
require(storage.files == before && storage.open_writes == 0
&& storage.remove_calls == 0 && storage.rename_calls == 0,
"overlong custom path left all complete and truncated artifacts untouched");
}
#endif
}
#if defined(ESP32_PLATFORM)
static void esp_metadata_errors_fail_closed() {
Scenario test;
for (const char* failed : {"/regions2", "/regions2.bak", "/regions2.tmp"}) {
MemoryFS storage = test.fs;
select(storage);
storage.files["/regions2.tmp"] = {0x61};
const auto before = storage.files;
storage.metadata_errors.insert(failed);
require(!test.candidate.save(&storage), "ESP metadata error refused save");
require(storage.files == before && storage.open_writes == test.fs.open_writes
&& storage.remove_calls == test.fs.remove_calls
&& storage.rename_calls == test.fs.rename_calls,
"ESP metadata error caused no namespace mutation");
storage.metadata_errors.clear();
test.reboot(storage);
}
for (const char* failed : {"/regions2", "/regions2.bak"}) {
MemoryFS storage = test.fs;
select(storage);
require(storage.rename("/regions2", "/regions2.bak"), "create metadata recovery gap");
storage.files["/regions2.tmp"] = test.replacement;
storage.metadata_errors.insert(failed);
const auto before = storage.files;
RegionMap restarted(test.keys);
require(!restarted.load(&storage), "ESP metadata error refused recovery load");
require(storage.files == before, "ESP metadata error retained backup and candidate bytes");
storage.metadata_errors.clear();
test.reboot(storage);
}
}
#endif
int main(int argc, char** argv) {
if (argc == 2 && std::strcmp(argv[1], "short-write") == 0) {
refusal_and_short_writes();
return 0;
}
healthy_save_and_reboot();
invalid_inputs_do_not_mutate();
refusal_and_short_writes();
staged_readback_and_publication_failure();
custom_path_isolation();
#if defined(ESP32_PLATFORM)
esp_metadata_errors_fail_closed();
#endif
std::puts("PASS: actual RegionMap durable transaction and reboot policy");
}
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env python3
"""Run actual RegionMap load/save with real platform transaction helpers."""
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest
from cpp_source import body
ROOT = Path(__file__).resolve().parents[1]
FIXTURE = ROOT / "test/fixtures/region_map_transactions"
def region_implementation(legacy=False):
source = (ROOT / "src/helpers/RegionMap.cpp").read_text(encoding="ascii")
# Retain the exact source helper block between name canonicalization and
# load(), including its platform guards, rather than reproducing recovery
# or path validation in the harness.
helpers_start = source.index("static const char* skip_hash(")
helpers_end = source.index("bool RegionMap::load(", helpers_start)
helpers = source[helpers_start:helpers_end]
if legacy and "static File openWrite(" in helpers:
helpers = helpers.replace(body(helpers, "static File openWrite("), "")
signatures = (
"RegionMap::RegionMap(", "bool RegionMap::is_name_char(",
"bool RegionMap::load(", "RegionEntry* RegionMap::putRegion(",
"RegionEntry* RegionMap::findByName(", "RegionEntry* RegionMap::findById(",
"RegionEntry* RegionMap::getHomeRegion(", "void RegionMap::setHomeRegion(",
"RegionEntry* RegionMap::getDefaultRegion(", "void RegionMap::setDefaultRegion(",
"bool RegionMap::wouldCreateCycle(",
)
generated = helpers + "\n" + "\n".join(body(source, signature) for signature in signatures)
generated += "\n" + (FIXTURE / "legacy_writer.inc").read_text(encoding="ascii") if legacy else (
"\n" + body(source, "bool RegionMap::save("))
text_helpers = (ROOT / "src/helpers/TxtDataHelpers.cpp").read_text(encoding="ascii")
return generated + "\n" + body(text_helpers, "void StrHelper::strncpy(")
class RegionMapTransactionTest(unittest.TestCase):
def run_platform(self, platform, legacy=False):
compiler = shutil.which("g++") or shutil.which("clang++")
self.assertIsNotNone(compiler, "a native C++ compiler is required")
with tempfile.TemporaryDirectory(prefix="meshcore-region-transactions-") as temporary:
work = Path(temporary)
(work / "region_under_test.inc").write_text(region_implementation(legacy), encoding="ascii")
# Relocate the unchanged real transaction header so its quoted
# platform IdentityStore include resolves to the native adapter.
# PersistentStoreFormat remains the real production header.
(work / "contact_transaction_under_test.h").write_text(
(ROOT / "src/helpers/ContactFileTransaction.h").read_text(encoding="ascii"),
encoding="ascii")
(work / "IdentityStore.h").write_text(
'#include <helpers/IdentityStore.h>\n', encoding="ascii")
(work / "PersistentStoreFormat.h").write_text(
'#include <helpers/PersistentStoreFormat.h>\n', encoding="ascii")
executable = work / "test"
command = [
compiler, "-std=c++17", "-O1", "-g", "-Wall", "-Wextra", "-Werror",
"-Wno-unused-parameter", "-Wno-sign-compare", "-Wno-unused-function",
"-fsanitize=address,undefined", "-fno-sanitize-recover=all", "-fno-pie", "-no-pie",
"-D" + platform + "=1", "-I", str(work), "-I", str(FIXTURE / "mocks"),
"-I", str(ROOT / "test/mocks"), "-I", str(ROOT / "src"),
str(FIXTURE / "test.cpp"), "-o", str(executable),
]
compiled = subprocess.run(command, capture_output=True, text=True, timeout=60)
self.assertEqual(compiled.returncode, 0, compiled.stdout + compiled.stderr)
environment = os.environ.copy()
environment["ASAN_OPTIONS"] = "detect_leaks=1:abort_on_error=1"
environment["UBSAN_OPTIONS"] = "halt_on_error=1:print_stacktrace=1"
arguments = [str(executable)] + (["short-write"] if legacy else [])
result = subprocess.run(arguments, capture_output=True, text=True,
timeout=60, env=environment)
if legacy:
self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn("FAIL: durable old region/default/home/wildcard policy survived", result.stderr)
self.assertNotIn("AddressSanitizer", result.stderr)
self.assertNotIn("runtime error:", result.stderr)
else:
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn("PASS: actual RegionMap durable transaction and reboot policy", result.stdout)
self.assertIn("666 short-write offsets and 666 full-storage capacities", result.stdout)
def test_nrf52_durable_transactions(self):
self.run_platform("NRF52_PLATFORM")
def test_stm32_durable_transactions(self):
self.run_platform("STM32_PLATFORM")
def test_esp32_durable_transactions(self):
self.run_platform("ESP32_PLATFORM")
def test_rp2040_durable_transactions(self):
self.run_platform("RP2040_PLATFORM")
def test_old_esp32_truncation_is_detected(self):
self.run_platform("ESP32_PLATFORM", legacy=True)
def test_old_rp2040_truncation_is_detected(self):
self.run_platform("RP2040_PLATFORM", legacy=True)
def test_old_stm32_removal_is_detected(self):
self.run_platform("STM32_PLATFORM", legacy=True)
if __name__ == "__main__":
unittest.main()