Files

195 lines
8.0 KiB
C++

#include "IdentityStore.h"
#include <Mesh.h>
#include "ContactFileTransaction.h"
#include "PersistentStoreFormat.h"
#include <cassert>
#include <cstdio>
#include <string>
#include <vector>
class RegionMap;
namespace mesh { struct DataRouteState; }
class CommonCLI {
public:
mesh::DataRouteState* _data_route = nullptr;
bool adoptLegacyDataTxPath(const uint8_t* path, uint8_t path_len);
};
// Generated from the production source by the host test runner. Includes the
// real route state, save helper, and adoption method rather than copies of their
// behavior. The filesystem and packet boundary use the existing host fixtures.
#include "production_route.inc"
struct Fixture {
MemoryFS fs;
mesh::DataRouteState route;
CommonCLI cli;
Fixture() {
route.fs = &fs;
cli._data_route = &route;
}
};
static void verifySavedRoute(const Fixture& f) {
assert(f.route.persisted);
const auto& data = f.fs.files.at("/data_tx");
assert(data.size() == 108);
assert(!memcmp(data.data(), "DTX1", 4));
assert(data[4] == f.route.path_len && data[5] == f.route.region_mode);
assert(data[6] == 0 && data[7] == 0 && data[103] == 0);
assert(!memcmp(data.data() + 8, f.route.path, MAX_PATH_SIZE));
assert(!memcmp(data.data() + 72, f.route.region, sizeof(f.route.region)));
assert(mesh::storage::readLE32(data.data() + 104)
== mesh::storage::updateCRC32(0xffffffff, data.data(), 104));
assert(!f.fs.exists("/data_tx.tmp") && !f.fs.exists("/data_tx.bak"));
}
static void checkPersistedRouteWins() {
const uint8_t legacy[] = {0x12, 0x34, 0x56, 0x78};
for (const uint8_t explicit_len : {uint8_t(0), uint8_t(0x42),
uint8_t(OUT_PATH_UNKNOWN)}) {
Fixture f;
f.route.path_len = explicit_len;
if (explicit_len == 0x42) memcpy(f.route.path, "\xab\xcd\xef\x01", 4);
f.route.region_mode = mesh::DATA_REGION_NAMED;
strcpy(f.route.region, "shared-scope");
assert(mesh::dataRouteSave(f.route));
verifySavedRoute(f);
const auto saved_files = f.fs.files;
const mesh::DataRouteState previous = f.route;
// A configured shared path, including an explicit none, takes precedence
// over a legacy path. Legacy none must also leave that shared route intact.
assert(f.cli.adoptLegacyDataTxPath(legacy, 0x42));
assert(f.cli.adoptLegacyDataTxPath(nullptr, OUT_PATH_UNKNOWN));
assert(f.cli.adoptLegacyDataTxPath(nullptr, 0));
assert(f.route.path_len == previous.path_len);
assert(!memcmp(f.route.path, previous.path, sizeof(f.route.path)));
assert(f.route.region_mode == previous.region_mode);
assert(!memcmp(f.route.region, previous.region, sizeof(f.route.region)));
assert(f.route.healthy && f.route.persisted);
assert(f.fs.files == saved_files);
}
}
static void checkSuccessfulMigration() {
const uint8_t legacy[] = {0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc};
for (const uint8_t path_len : {uint8_t(0), uint8_t(2), uint8_t(0x42),
uint8_t(0x82)}) {
Fixture f;
memset(f.route.path, 0xa5, sizeof(f.route.path));
assert(f.cli.adoptLegacyDataTxPath(path_len ? legacy : nullptr, path_len));
assert(f.route.path_len == path_len && f.route.healthy);
const size_t bytes = (path_len & 63) * ((path_len >> 6) + 1);
assert(!memcmp(f.route.path, legacy, bytes));
for (size_t i = bytes; i < sizeof(f.route.path); ++i) assert(f.route.path[i] == 0);
assert(f.route.region_mode == mesh::DATA_REGION_AUTO && !f.route.region[0]);
verifySavedRoute(f);
}
}
static void checkIsolatedPublishFailureAndRetry() {
Fixture f;
f.route.path_len = 1;
memset(f.route.path, 0xa5, sizeof(f.route.path));
f.route.region_mode = mesh::DATA_REGION_NAMED;
strcpy(f.route.region, "shared-scope");
f.fs.files["/management"] = std::vector<uint8_t>(120, 0);
memcpy(f.fs.files["/management"].data(), "MGC1", 4);
const auto saved_files = f.fs.files;
const mesh::DataRouteState previous = f.route;
const uint8_t legacy[] = {0x12, 0x34, 0x56, 0x78};
f.fs.fail_rename_from = {"/data_tx.tmp"};
assert(!f.cli.adoptLegacyDataTxPath(legacy, 0x42));
assert(f.route.path_len == previous.path_len && !f.route.persisted);
assert(!memcmp(f.route.path, previous.path, sizeof(f.route.path)));
assert(f.route.region_mode == previous.region_mode);
assert(!memcmp(f.route.region, previous.region, sizeof(f.route.region)));
assert(f.route.healthy);
assert(f.fs.files == saved_files);
// The failed /data_tx publish is isolated: a later /management transaction
// can succeed. The production loader must therefore act on adoption failure
// before allowing a management checkpoint to discard the legacy route.
uint8_t management[120] = {};
memcpy(management, "MGC2", 4);
mesh::ContactFileTransaction writer(&f.fs, "/management");
assert(writer && writer.write(management, sizeof(management)) == sizeof(management));
assert(writer.commit());
assert(f.fs.files.at("/management")
== std::vector<uint8_t>(management, management + sizeof(management)));
assert(!f.fs.exists("/data_tx"));
assert(!f.fs.exists("/management.tmp") && !f.fs.exists("/management.bak"));
f.fs.fail_rename_from.clear();
assert(f.cli.adoptLegacyDataTxPath(legacy, 0x42));
verifySavedRoute(f);
assert(f.route.path_len == 0x42 && !memcmp(f.route.path, legacy, sizeof(legacy)));
}
static void checkUnavailableUnhealthyAndInvalidRoutes() {
const uint8_t legacy[MAX_PATH_SIZE] = {};
CommonCLI unavailable;
assert(!unavailable.adoptLegacyDataTxPath(legacy, 1));
for (const bool persisted : {false, true}) {
Fixture f;
f.route.healthy = false;
f.route.persisted = persisted;
assert(!f.cli.adoptLegacyDataTxPath(legacy, 1));
assert(f.fs.files.empty() && f.route.path_len == 0);
}
for (const uint8_t invalid : {uint8_t(0xc0), uint8_t(0x61), uint8_t(0x96)}) {
Fixture f;
assert(!f.cli.adoptLegacyDataTxPath(legacy, invalid));
assert(f.fs.files.empty() && f.route.path_len == 0 && !f.route.persisted);
}
Fixture missing_path;
assert(!missing_path.cli.adoptLegacyDataTxPath(nullptr, 1));
assert(missing_path.fs.files.empty() && !missing_path.route.persisted);
}
static void checkPathParsing() {
struct Case { const char* text; uint8_t encoded; const char* bytes; };
for (const auto& valid : {Case{"direct", 0, ""}, Case{" none ", OUT_PATH_UNKNOWN, ""},
Case{"clear", OUT_PATH_UNKNOWN, ""}, Case{"-", OUT_PATH_UNKNOWN, ""},
Case{"1:12ab", 2, "\x12\xab"}, Case{" 12, AB ", 2, "\x12\xab"},
Case{"2:12abcd34", 0x42, "\x12\xab\xcd\x34"},
Case{"12ab, cd34", 0x42, "\x12\xab\xcd\x34"},
Case{"3:123456abcdef", 0x82, "\x12\x34\x56\xab\xcd\xef"}}) {
std::vector<char> text(valid.text, valid.text + strlen(valid.text) + 1);
uint8_t path[MAX_PATH_SIZE], encoded = OUT_PATH_UNKNOWN;
const char* error = nullptr;
assert(mesh::parseDataPath(text.data(), path, encoded, error));
assert(encoded == valid.encoded && !error);
const size_t bytes = encoded == OUT_PATH_UNKNOWN ? 0 : (encoded & 63) * ((encoded >> 6) + 1);
assert(!memcmp(path, valid.bytes, bytes));
for (size_t i = bytes; i < sizeof(path); ++i) assert(path[i] == 0);
}
for (const char* malformed : {"", " ", ",12", "12,,ab", "12,", "12, ",
"12ab,cd34,", "12ab,cd34, ", "1:", "1:123", "2:12ab34",
"3:123456ab", "4:12345678", "12,abcd", "12;ab", "12,xx"}) {
std::vector<char> text(malformed, malformed + strlen(malformed) + 1);
uint8_t path[MAX_PATH_SIZE], encoded = OUT_PATH_UNKNOWN;
const char* error = nullptr;
if (mesh::parseDataPath(text.data(), path, encoded, error)) {
fprintf(stderr, "malformed data.tx path accepted: '%s'\n", malformed);
assert(false);
}
assert(error);
}
}
int main() {
checkPersistedRouteWins();
checkSuccessfulMigration();
checkIsolatedPublishFailureAndRetry();
checkUnavailableUnhealthyAndInvalidRoutes();
checkPathParsing();
puts("management route adoption checks passed");
}