Files
MeshCore/test/fixtures/region_name_export.cpp
T

314 lines
12 KiB
C++

// Exercise the actual RegionMap declarations and method bodies, including the
// actual Repeater/Room getLocalScopes wrappers through minimal role adapters.
#include <algorithm>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <string>
#include <vector>
#include <helpers/RegionMap.h>
#include <helpers/RegionNameUtils.h>
#include <helpers/TxtDataHelpers.h>
#include "region_under_test.inc"
// Frozen original exporter from commit 3a3323c4d594dd766dea25caaad06bb5225a8ffa,
// RegionMap.cpp SHA256 0f3dd22e2c228061334bcf95416cf40ecdd01310f99a011785a0837e43334900.
// The adapter snapshots real public region state. The method below is unchanged
// and is used only for >=2-byte legacy output compatibility, never for safety.
namespace legacy {
class RegionMap {
uint16_t num_regions;
RegionEntry regions[MAX_REGION_ENTRIES];
RegionEntry wildcard;
public:
explicit RegionMap(::RegionMap& source)
: num_regions(source.getCount()), wildcard(source.getWildcard()) {
for (int i = 0; i < num_regions; ++i) regions[i] = *source.getByIdx(i);
}
int exportNamesTo(char *dest, int max_len, uint8_t mask, bool invert = false);
};
int RegionMap::exportNamesTo(char *dest, int max_len, uint8_t mask, bool invert) {
char *dp = dest;
// Check wildcard region
bool wildcard_matches = invert ? (wildcard.flags & mask) : !(wildcard.flags & mask);
if (wildcard_matches) {
*dp++ = '*';
*dp++ = ',';
}
for (int i = 0; i < num_regions; i++) {
auto region = &regions[i];
// Check if region matches the filter criteria
bool region_matches = invert ? (region->flags & mask) : !(region->flags & mask);
if (region_matches) {
int len = strlen(skip_hash(region->name));
if ((dp - dest) + len + 2 < max_len) { // only append if name will fit
memcpy(dp, skip_hash(region->name), len);
dp += len;
*dp++ = ',';
}
}
}
if (dp > dest) { dp--; } // don't include trailing comma
*dp = 0; // set null terminator
return dp - dest; // return length
}
} // namespace legacy
static void require(bool condition, const char* message) {
if (!condition) {
std::fprintf(stderr, "FAIL: %s\n", message);
std::exit(1);
}
}
static void populate(RegionMap& map, bool dense) {
if (dense) {
for (int i = 0; i < MAX_REGION_ENTRIES; ++i) {
char name[31];
std::snprintf(name, sizeof(name), "#Place%02d", i);
RegionEntry* entry = map.putRegion(name, 0);
require(entry != nullptr, "populate full real RegionMap");
entry->flags = 0;
}
return;
}
const char* names[] = {"#Alpine", "$Secret", "B", "#Direct", "Both",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ1234"};
const uint8_t flags[] = {0, 0, REGION_DENY_FLOOD, REGION_DENY_DIRECT,
REGION_DENY_FLOOD | REGION_DENY_DIRECT, 0};
for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); ++i) {
RegionEntry* entry = map.putRegion(names[i], 0);
require(entry != nullptr, "populate region names and filter policies");
entry->flags = flags[i];
}
}
static std::vector<std::string> eligible(RegionMap& map, uint8_t mask, bool invert) {
std::vector<std::string> names;
const auto matches = [=](uint8_t flags) {
return invert ? (flags & mask) != 0 : (flags & mask) == 0;
};
if (matches(map.getWildcard().flags)) names.emplace_back("*");
for (int i = 0; i < map.getCount(); ++i) {
const RegionEntry* entry = map.getByIdx(i);
if (matches(entry->flags)) names.emplace_back(RegionNameUtils::canonical(entry->name));
}
return names;
}
static std::string joined(const std::vector<std::string>& names) {
std::string result;
for (const auto& name : names) {
if (!result.empty()) result += ',';
result += name;
}
return result;
}
static void validate_output(const char* destination, int capacity, int count,
const std::vector<std::string>& names) {
require(count >= 0 && count < capacity, "returned length is inside destination capacity");
require(destination[count] == 0, "returned length points to null terminator");
require(std::strlen(destination) == static_cast<size_t>(count),
"returned length is exactly the produced string length");
const std::string actual(destination);
require(actual.empty() || (actual.front() != ',' && actual.back() != ','),
"no leading or trailing comma");
size_t allowed = 0;
for (size_t begin = 0; begin < actual.size();) {
const size_t end = actual.find(',', begin);
const std::string token = actual.substr(begin, end == std::string::npos ? end : end - begin);
require(!token.empty(), "no empty region token");
while (allowed < names.size() && names[allowed] != token) ++allowed;
require(allowed < names.size(), "only complete matching names in original order");
++allowed;
if (end == std::string::npos) break;
begin = end + 1;
}
if (joined(names).size() + 3 < static_cast<size_t>(capacity)) {
require(actual == joined(names), "ample destination includes every matching name");
}
}
static void guarded_export(RegionMap& map, int capacity, uint8_t mask, bool invert) {
constexpr size_t guard = 32;
constexpr unsigned char sentinel = 0xA5;
const size_t accessible = capacity > 0 ? static_cast<size_t>(capacity) : 0;
std::vector<unsigned char> storage(guard + accessible + guard, sentinel);
char* destination = reinterpret_cast<char*>(storage.data() + guard);
const int count = map.exportNamesTo(destination, capacity, mask, invert);
require(std::all_of(storage.begin(), storage.begin() + guard,
[](unsigned char c) { return c == sentinel; }),
"export preserved prefix guard");
require(std::all_of(storage.begin() + guard + accessible, storage.end(),
[](unsigned char c) { return c == sentinel; }),
"export wrote no byte beyond claimed destination capacity");
if (capacity <= 0) {
require(count == 0, "nonpositive destination returns zero without writing");
} else {
validate_output(destination, capacity, count, eligible(map, mask, invert));
if (capacity == 1) require(destination[0] == 0, "one-byte destination is empty");
if (capacity >= 2) {
legacy::RegionMap original(map);
std::vector<unsigned char> baseline(accessible, sentinel);
const int old_count = original.exportNamesTo(
reinterpret_cast<char*>(baseline.data()), capacity, mask, invert);
require(count == old_count
&& std::equal(baseline.begin(), baseline.end(), storage.begin() + guard),
"all >=2-byte results and destination bytes match frozen original exporter");
}
}
}
template<class Role>
static void guarded_wrapper(RegionMap& map, size_t capacity) {
constexpr size_t guard = 32;
constexpr unsigned char sentinel = 0xA5;
std::vector<unsigned char> storage(guard + capacity + guard, sentinel);
char* destination = reinterpret_cast<char*>(storage.data() + guard);
Role role(map);
role.getLocalScopes(destination, capacity);
require(std::all_of(storage.begin(), storage.begin() + guard,
[](unsigned char c) { return c == sentinel; }),
"role wrapper preserved prefix guard");
require(std::all_of(storage.begin() + guard + capacity, storage.end(),
[](unsigned char c) { return c == sentinel; }),
"role wrapper wrote no byte beyond claimed destination capacity");
if (capacity) {
const void* end = std::memchr(destination, 0, capacity);
require(end != nullptr, "role wrapper terminates inside supplied buffer");
validate_output(destination, static_cast<int>(capacity),
static_cast<const char*>(end) - destination,
eligible(map, REGION_DENY_FLOOD, false));
}
}
static void healthy() {
TransportKeyStore store;
RegionMap map(store);
populate(map, false);
for (int capacity : {96, 159, 160}) {
char destination[160];
const char* golden = "*,Alpine,$Secret,Direct,ABCDEFGHIJKLMNOPQRSTUVWXYZ1234";
const int count = map.exportNamesTo(destination, capacity, REGION_DENY_FLOOD);
require(std::strcmp(destination, golden) == 0 && count == static_cast<int>(std::strlen(golden)),
"healthy production-size output unchanged");
guarded_wrapper<repeater::MyMesh>(map, capacity);
guarded_wrapper<room::MyMesh>(map, capacity);
}
RegionMap full(store);
populate(full, true);
const char* golden96 = "*,Place00,Place01,Place02,Place03,Place04,Place05,Place06,Place07,Place08,Place09,Place10";
const char* golden159 = "*,Place00,Place01,Place02,Place03,Place04,Place05,Place06,Place07,Place08,Place09,Place10,Place11,Place12,Place13,Place14,Place15,Place16,Place17,Place18";
for (int capacity : {96, 159, 160}) {
char destination[160];
const char* golden = capacity == 96 ? golden96 : golden159;
const int count = full.exportNamesTo(destination, capacity, REGION_DENY_FLOOD);
require(std::strcmp(destination, golden) == 0 && count == static_cast<int>(std::strlen(golden)),
"full-map production-size truncation unchanged");
}
}
static void invalid() {
TransportKeyStore store;
RegionMap map(store);
populate(map, false);
for (int capacity : {INT_MIN, -4, -1, 0, 1, 2, 160, INT_MAX}) {
require(map.exportNamesTo(nullptr, capacity, REGION_DENY_FLOOD) == 0,
"null destination returns zero without access");
}
for (int capacity : {INT_MIN, -4, -1, 0}) {
guarded_export(map, capacity, REGION_DENY_FLOOD, false);
}
repeater::MyMesh repeater(map);
room::MyMesh room(map);
for (size_t capacity : {size_t(0), size_t(1), size_t(160)}) {
repeater.getLocalScopes(nullptr, capacity);
room.getLocalScopes(nullptr, capacity);
}
}
static void matrix() {
TransportKeyStore store;
size_t cases = 0;
for (int shape = 0; shape < 3; ++shape) {
RegionMap map(store);
if (shape) populate(map, shape == 2);
for (uint8_t wildcard_flags = 0; wildcard_flags < 4; ++wildcard_flags) {
map.getWildcard().flags = wildcard_flags;
for (uint8_t mask = 0; mask < 4; ++mask) {
for (bool invert : {false, true}) {
for (int capacity = -1; capacity <= 320; ++capacity) {
guarded_export(map, capacity, mask, invert);
++cases;
}
}
}
}
}
std::printf("PASS: %zu direct capacity/filter combinations\n", cases);
}
static void wrappers() {
TransportKeyStore store;
for (int shape = 0; shape < 3; ++shape) {
RegionMap map(store);
if (shape) populate(map, shape == 2);
for (uint8_t wildcard_flags = 0; wildcard_flags < 4; ++wildcard_flags) {
map.getWildcard().flags = wildcard_flags;
for (size_t capacity = 0; capacity <= 320; ++capacity) {
guarded_wrapper<repeater::MyMesh>(map, capacity);
guarded_wrapper<room::MyMesh>(map, capacity);
}
}
}
}
template<class Role>
static void one_byte() {
TransportKeyStore store;
RegionMap map(store);
Role role(map);
// An exact one-byte allocation makes an out-of-capacity intermediate comma
// store observable to ASan even if the eventual terminator is otherwise valid.
std::unique_ptr<char[]> destination(new char[1]);
destination[0] = '!';
role.getLocalScopes(destination.get(), 1);
require(destination[0] == 0, "one-byte role wrapper produces an empty string");
}
static void direct_one_byte() {
TransportKeyStore store;
RegionMap map(store);
std::unique_ptr<char[]> destination(new char[1]);
destination[0] = '!';
require(map.exportNamesTo(destination.get(), 1, REGION_DENY_FLOOD) == 0,
"one-byte direct exporter returns zero");
require(destination[0] == 0, "one-byte direct exporter produces an empty string");
}
int main(int argc, char** argv) {
require(argc == 2, "one scenario argument required");
const std::string scenario(argv[1]);
if (scenario == "healthy") healthy();
else if (scenario == "invalid") invalid();
else if (scenario == "matrix") matrix();
else if (scenario == "wrappers") wrappers();
else if (scenario == "direct-one-byte") direct_one_byte();
else if (scenario == "repeater-one-byte") one_byte<repeater::MyMesh>();
else if (scenario == "room-one-byte") one_byte<room::MyMesh>();
else require(false, "known scenario");
std::printf("PASS: %s\n", scenario.c_str());
return 0;
}