* new CLI commands: region, region load, region save, region get, region allow

This commit is contained in:
Scott Powell
2025-11-03 22:53:14 +11:00
parent f797744f7c
commit ecd30f4d36
5 changed files with 219 additions and 22 deletions

View File

@@ -610,7 +610,7 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t
MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng,
mesh::RTCClock &rtc, mesh::MeshTables &tables)
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
_cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store)
_cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store)
#if defined(WITH_RS232_BRIDGE)
, bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc)
#endif
@@ -624,6 +624,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
dirty_contacts_expiry = 0;
set_radio_at = revert_radio_at = 0;
_logging = false;
region_load_active = false;
#if MAX_NEIGHBOURS
memset(neighbours, 0, sizeof(neighbours));
@@ -846,9 +847,46 @@ void MyMesh::clearStats() {
((SimpleMeshTables *)getTables())->resetStats();
}
static bool is_name_char(char c) {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= 'z') || c == '-' || c == '.' || c == '_' || c == '#';
}
void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) {
while (*command == ' ')
command++; // skip leading spaces
if (region_load_active) {
if (*command == 0) { // empty line, signal to terminate 'load' operation
region_map = temp_map; // copy over the temp instance as new current map
region_load_active = false;
sprintf(reply, "OK - loaded %d regions", region_map.getCount());
} else {
char *np = command;
while (*np == ' ') np++; // skip indent
int indent = np - command;
char *ep = np;
while (is_name_char(*ep)) ep++;
if (*ep) { *ep++ = 0; } // set null terminator for end of name
while (*ep && *ep != 'F') ep++; // look for (optional flags)
if (indent > 0 && indent < 8) {
auto parent = load_stack[indent - 1];
if (parent) {
auto old = region_map.findByName(np);
auto nw = temp_map.putRegion(np, parent->id, old ? old->id : 0); // carry-over the current ID (if name already exists)
if (nw) {
nw->flags = old ? old->flags : (*ep == 'F' ? REGION_ALLOW_FLOOD : 0); // carry-over flags from curr
load_stack[indent] = nw; // keep pointers to parent regions, to resolve parent_id's
}
}
}
reply[0] = 0;
}
return;
}
while (*command == ' ') command++; // skip leading spaces
if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI)
memcpy(reply, command, 3); // reflect the prefix back
@@ -890,6 +928,45 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
Serial.printf("\n");
}
reply[0] = 0;
} else if (memcmp(command, "region", 6) == 0) {
reply[0] = 0;
const char* parts[4];
int n = mesh::Utils::parseTextParts(command, parts, 4, ' ');
if (n == 1 && sender_timestamp == 0) {
region_map.exportTo(Serial);
} else if (n >= 2 && strcmp(parts[1], "load") == 0) {
temp_map.resetFrom(region_map); // rebuild regions in a temp instance
memset(load_stack, 0, sizeof(load_stack));
load_stack[0] = &temp_map.getWildcard();
region_load_active = true;
} else if (n >= 2 && strcmp(parts[1], "save") == 0) {
bool success = region_map.save(_fs);
strcpy(reply, success ? "OK" : "Err - save failed");
} else if (n >= 3 && strcmp(parts[1], "allow") == 0) {
auto region = n >= 4 ? region_map.findByNamePrefix(parts[3]) : &region_map.getWildcard();
if (region) {
strcpy(reply, "OK");
if (strcmp(parts[2], "F") == 0) {
region->flags = REGION_ALLOW_FLOOD;
} else if (strcmp(parts[2], "0") == 0) {
region->flags = 0;
} else {
sprintf(reply, "Err - invalid flag: %s", parts[2]);
}
} else {
strcpy(reply, "Err - unknown region");
}
} else if (n >= 3 && strcmp(parts[1], "get") == 0) {
auto region = region_map.findByNamePrefix(parts[2]);
if (region) {
sprintf(reply, " %s %s", region->name, region->flags == REGION_ALLOW_FLOOD ? "F" : "");
} else {
strcpy(reply, "Err - unknown region");
}
} else {
strcpy(reply, "Err - ??");
}
} else{
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
}

View File

@@ -89,7 +89,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
uint8_t reply_data[MAX_PACKET_PAYLOAD];
ClientACL acl;
TransportKeyStore key_store;
RegionMap region_map;
RegionMap region_map, temp_map;
RegionEntry* load_stack[8];
bool region_load_active;
unsigned long dirty_contacts_expiry;
#if MAX_NEIGHBOURS
NeighbourInfo neighbours[MAX_NEIGHBOURS];

View File

@@ -91,14 +91,16 @@ void loop() {
if (c != '\n') {
command[len++] = c;
command[len] = 0;
Serial.print(c);
}
Serial.print(c);
if (c == '\r') break;
}
if (len == sizeof(command)-1) { // command buffer full
command[sizeof(command)-1] = '\r';
}
if (len > 0 && command[len - 1] == '\r') { // received complete line
Serial.print('\n');
command[len - 1] = 0; // replace newline with C string null terminator
char reply[160];
the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!

View File

@@ -2,25 +2,106 @@
#include <helpers/TxtDataHelpers.h>
#include <SHA256.h>
void RegionMap::load(FILESYSTEM* _fs) {
// TODO
}
void RegionMap::save(FILESYSTEM* _fs) {
// TODO
RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) {
next_id = 1; num_regions = 0;
wildcard.id = wildcard.parent = 0;
wildcard.flags = REGION_ALLOW_FLOOD; // default behaviour, allow flood
strcpy(wildcard.name, "(*)");
}
RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id) {
static File openWrite(FILESYSTEM* _fs, const char* filename) {
#if defined(NRF52_PLATFORM) || 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
}
bool RegionMap::load(FILESYSTEM* _fs) {
if (_fs->exists("/regions2")) {
#if defined(RP2040_PLATFORM)
File file = _fs->open("/regions2", "r");
#else
File file = _fs->open("/regions2");
#endif
if (file) {
uint8_t pad[128];
num_regions = 0; next_id = 1;
bool success = file.read(pad, 7) == 7; // reserved header
success = success && file.read((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags);
success = success && file.read((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id);
if (success) {
while (num_regions < MAX_REGION_ENTRIES) {
auto r = &regions[num_regions];
success = file.read((uint8_t *) &r->id, sizeof(r->id)) == sizeof(r->id);
success = success && file.read((uint8_t *) &r->parent, sizeof(r->parent)) == sizeof(r->parent);
success = success && file.read((uint8_t *) r->name, sizeof(r->name)) == sizeof(r->name);
success = success && file.read((uint8_t *) &r->flags, sizeof(r->flags)) == sizeof(r->flags);
success = success && file.read(pad, sizeof(pad)) == sizeof(pad);
if (!success) break; // EOF
if (r->id >= next_id) { // make sure next_id is valid
next_id = r->id + 1;
}
num_regions++;
}
}
file.close();
return true;
}
}
return false; // failed
}
bool RegionMap::save(FILESYSTEM* _fs) {
File file = openWrite(_fs, "/regions2");
if (file) {
uint8_t pad[128];
memset(pad, 0, sizeof(pad));
bool success = file.write(pad, 7) == 7; // reserved header
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
}
}
file.close();
return true;
}
return false; // failed
}
RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id, uint16_t id) {
auto region = findByName(name);
if (region) {
if (region->id == parent_id) return NULL; // ERROR: invalid parent!
region->parent = parent_id; // re-parent / move this region in the hierarchy
} else {
if (num_regions >= MAX_REGION_ENTRIES) return NULL; // full!
if (id == 0 && num_regions >= MAX_REGION_ENTRIES) return NULL; // full!
region = &regions[num_regions++]; // alloc new RegionEntry
region->flags = 0;
region->id = next_id++;
region->id = id == 0 ? next_id++ : id;
StrHelper::strncpy(region->name, name, sizeof(region->name));
region->parent = parent_id;
}
@@ -58,6 +139,14 @@ RegionEntry* RegionMap::findByName(const char* name) {
return NULL; // not found
}
RegionEntry* RegionMap::findByNamePrefix(const char* prefix) {
for (int i = 0; i < num_regions; i++) {
auto region = &regions[i];
if (memcmp(prefix, region->name, strlen(prefix)) == 0) return region;
}
return NULL; // not found
}
RegionEntry* RegionMap::findById(uint16_t id) {
if (id == 0) return &wildcard; // special root Region
@@ -94,3 +183,26 @@ bool RegionMap::clear() {
num_regions = 0;
return true; // success
}
void RegionMap::printChildRegions(int indent, const RegionEntry* parent, Stream& out) const {
for (int i = 0; i < indent; i++) {
out.print(' ');
}
if (parent->flags & REGION_ALLOW_FLOOD) {
out.printf("%s F\n", parent->name);
} else {
out.printf("%s\n", parent->name);
}
for (int i = 0; i < num_regions; i++) {
auto r = &regions[i];
if (r->parent == parent->id) {
printChildRegions(indent + 1, r, out);
}
}
}
void RegionMap::exportTo(Stream& out) const {
printChildRegions(0, &wildcard, out); // recursive
}

View File

@@ -24,20 +24,24 @@ class RegionMap {
RegionEntry regions[MAX_REGION_ENTRIES];
RegionEntry wildcard;
public:
RegionMap(TransportKeyStore& store) : _store(&store) {
next_id = 1; num_regions = 0;
wildcard.id = wildcard.parent = 0;
wildcard.flags = REGION_ALLOW_FLOOD; // default behaviour, allow flood
}
void load(FILESYSTEM* _fs);
void save(FILESYSTEM* _fs);
void printChildRegions(int indent, const RegionEntry* parent, Stream& out) const;
RegionEntry* putRegion(const char* name, uint16_t parent_id);
public:
RegionMap(TransportKeyStore& store);
bool load(FILESYSTEM* _fs);
bool save(FILESYSTEM* _fs);
RegionEntry* putRegion(const char* name, uint16_t parent_id, uint16_t id = 0);
RegionEntry* findMatch(mesh::Packet* packet, uint8_t mask);
RegionEntry& getWildcard() { return wildcard; }
RegionEntry* findByName(const char* name);
RegionEntry* findByNamePrefix(const char* prefix);
RegionEntry* findById(uint16_t id);
bool removeRegion(const RegionEntry& region);
bool clear();
void resetFrom(const RegionMap& src) { num_regions = 0; next_id = src.next_id; }
int getCount() const { return num_regions; }
void exportTo(Stream& out) const;
};