Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion examples/simple_repeater/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -318,12 +318,59 @@ File MyMesh::openAppend(const char *fname) {
}

bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
if (_prefs.disable_fwd) return false;
if (_prefs.disable_fwd) {
return false;
}

if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false;
if (packet->isRouteFlood() && recv_pkt_region == NULL) {
MESH_DEBUG_PRINTLN("allowPacketForward: unknown transport code, or wildcard not allowed for FLOOD packet");
return false;
}

// Check if this packet type is allowed to be repeated
uint8_t pkt_type = packet->getPayloadType();
if (pkt_type < mesh::MAX_PACKET_TYPES && (_prefs.repeat_packet_types & (1 << pkt_type)) == 0) {
// Packet type is not allowed to repeat
MESH_DEBUG_PRINTLN("allowPacketForward: packet type %d not allowed to repeat", pkt_type);
return false;
}

// For advert packets, check sub-type filtering and max hops
if (pkt_type == PAYLOAD_TYPE_ADVERT) {
const int app_data_offset = PUB_KEY_SIZE + 4 + SIGNATURE_SIZE;

// Quick sanity check before parsing
if (packet->payload_len > app_data_offset) {
const uint8_t* app_data = &packet->payload[app_data_offset];
int app_data_len = packet->payload_len - app_data_offset;

// Only parse if there's actual data
if (app_data_len > 0) {
// Quick check: read advert type from flags byte (avoid full parse if possible)
uint8_t flags = app_data[0];
uint8_t adv_type = flags & 0x0F;

// Check if this advert sub-type is allowed
if (adv_type < mesh::MAX_ADVERT_TYPES && (_prefs.repeat_advert_types & (1 << adv_type)) == 0) {
MESH_DEBUG_PRINTLN("allowPacketForward: %s advert type not allowed to repeat",
mesh::getAdvertTypeName(adv_type));
return false;
}

// Check max hops for flood-mode advert packets (0 = no limit)
// path_len is only meaningful for flood packets, not direct-mode packets
// CHAT adverts are excluded from hop count filtering
if (packet->isRouteFlood() && adv_type != ADV_TYPE_CHAT && _prefs.advert_max_hops > 0 &&
packet->path_len > _prefs.advert_max_hops ) {
MESH_DEBUG_PRINTLN("allowPacketForward: advert exceeded max hops (%d > %d)",
packet->path_len, _prefs.advert_max_hops);
return false;
}
}
}
}

return true;
}

Expand Down Expand Up @@ -723,6 +770,11 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;

_prefs.adc_multiplier = 0.0f; // 0.0f means use default board multiplier

// Packet filtering defaults - repeat everything by default
_prefs.repeat_packet_types = 0xFFFF; // Repeat all packet types by default
_prefs.repeat_advert_types = 0xFFFF; // Repeat all advert types by default
_prefs.advert_max_hops = 0; // No hop limit by default
}

void MyMesh::begin(FILESYSTEM *fs) {
Expand Down
1 change: 1 addition & 0 deletions examples/simple_repeater/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <helpers/ArduinoHelpers.h>
#include <helpers/ClientACL.h>
#include <helpers/CommonCLI.h>
#include <helpers/PacketTypeNames.h>
#include <helpers/IdentityStore.h>
#include <helpers/SimpleMeshTables.h>
#include <helpers/StaticPoolPacketManager.h>
Expand Down
252 changes: 244 additions & 8 deletions src/helpers/CommonCLI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "CommonCLI.h"
#include "TxtDataHelpers.h"
#include "AdvertDataHelpers.h"
#include "PacketTypeNames.h"
#include <RTClib.h>

// Believe it or not, this std C function is busted on some platforms!
Expand Down Expand Up @@ -72,7 +73,10 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161
file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
// 170
file.read((uint8_t *)&_prefs->repeat_packet_types, sizeof(_prefs->repeat_packet_types)); // 170
file.read((uint8_t *)&_prefs->repeat_advert_types, sizeof(_prefs->repeat_advert_types)); // 172
file.read((uint8_t *)&_prefs->advert_max_hops, sizeof(_prefs->advert_max_hops)); // 173
// 174

// sanitise bad pref values
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
Expand All @@ -99,7 +103,27 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
_prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1);
_prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2);

// Validate packet repeat bitmask - ensure no bits set beyond valid packet types
uint16_t valid_mask = (1 << mesh::MAX_PACKET_TYPES) - 1;
_prefs->repeat_packet_types &= valid_mask;

// Validate advert repeat bitmask - ensure no bits set beyond valid advert types
uint8_t valid_adv_mask = (1 << mesh::MAX_ADVERT_TYPES) - 1;
_prefs->repeat_advert_types &= valid_adv_mask;

// Validate advert max hops (0 = no limit, otherwise 1-255)
// No validation needed since uint8_t naturally constrains to 0-255

file.close();

// Migrate legacy disable_fwd to new granular filtering system
if (_prefs->disable_fwd && _prefs->repeat_packet_types == 0xFFFF) {
// Old firmware had disable_fwd=true but new filtering not configured yet
// Migrate: clear all bits to block all packet types (bit=1 means allow)
_prefs->repeat_packet_types = 0x0000; // Block all packet types
savePrefs(); // Persist migration immediately
}
// Note: disable_fwd now acts as a global on/off switch that is independent of the bitmask
}
}

Expand Down Expand Up @@ -155,7 +179,10 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161
file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
// 170
file.write((uint8_t *)&_prefs->repeat_packet_types, sizeof(_prefs->repeat_packet_types)); // 170
file.write((uint8_t *)&_prefs->repeat_advert_types, sizeof(_prefs->repeat_advert_types)); // 172
file.write((uint8_t *)&_prefs->advert_max_hops, sizeof(_prefs->advert_max_hops)); // 173
// 174

file.close();
}
Expand Down Expand Up @@ -282,8 +309,93 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
sprintf(reply, "> %s", tmp);
} else if (memcmp(config, "name", 4) == 0) {
sprintf(reply, "> %s", _prefs->node_name);
} else if (memcmp(config, "repeat", 6) == 0) {
sprintf(reply, "> %s", _prefs->disable_fwd ? "off" : "on");
} else if (memcmp(config, "repeat advert.max_hops", 22) == 0) {
// Show max hops for advert packets
if (_prefs->advert_max_hops == 0) {
sprintf(reply, "> unlimited");
} else {
sprintf(reply, "> %d (excluding companion adverts)", _prefs->advert_max_hops);
}
} else if (memcmp(config, "repeat advert", 13) == 0 && (config[13] == 0 || config[13] == ' ')) {
// Show which advert sub-types are allowed to repeat
reply[0] = '>';
reply[1] = ' ';
int pos = 2;
bool has_any = false;

for (int i = 0; i < mesh::MAX_ADVERT_TYPES; i++) {
if ((_prefs->repeat_advert_types & (1 << i)) != 0) {
const char* name = mesh::getAdvertTypeName(i);
int len = strlen(name);

if (pos + (has_any ? 1 : 0) + len + 1 >= 160) {
strcpy(&reply[pos], "...");
pos += 3;
break;
}

if (has_any) {
reply[pos++] = ',';
}
strcpy(&reply[pos], name);
pos += len;
has_any = true;
}
}

if (!has_any) {
strcpy(&reply[2], "all filtered");
} else {
reply[pos] = 0;
}
} else if (memcmp(config, "repeat", 6) == 0 && (config[6] == 0 || config[6] == ' ')) {
// Show global repeat state and packet filter configuration
reply[0] = '>';
reply[1] = ' ';
int pos = 2;

// Show global state first
if (_prefs->disable_fwd) {
strcpy(&reply[pos], "OFF");
pos += 3;
} else {
strcpy(&reply[pos], "ON");
pos += 2;
}

// Add separator
strcpy(&reply[pos], " (allowed: ");
pos += 11;

// Show configured packet types
bool has_any = false;
for (int i = 0; i < mesh::MAX_PACKET_TYPES; i++) {
if ((_prefs->repeat_packet_types & (1 << i)) != 0) { // If bit set, it's allowed
const char* name = mesh::getPacketTypeName(i);
int len = strlen(name);

if (pos + (has_any ? 1 : 0) + len + 2 >= 160) {
strcpy(&reply[pos], "...");
pos += 3;
break;
}

if (has_any) {
reply[pos++] = ',';
}
strcpy(&reply[pos], name);
pos += len;
has_any = true;
}
}

if (!has_any) {
strcpy(&reply[pos], "none");
pos += 4;
}

reply[pos++] = ')';
reply[pos] = 0;
} else if (memcmp(config, "lat", 3) == 0) {
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lat));
} else if (memcmp(config, "lon", 3) == 0) {
Expand Down Expand Up @@ -413,10 +525,6 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name));
savePrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "repeat ", 7) == 0) {
_prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0;
savePrefs();
strcpy(reply, _prefs->disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON");
} else if (memcmp(config, "radio ", 6) == 0) {
strcpy(tmp, &config[6]);
const char *parts[4];
Expand Down Expand Up @@ -550,6 +658,134 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
_prefs->adc_multiplier = 0.0f;
strcpy(reply, "Error: unsupported by this board");
};
} else if (memcmp(config, "repeat on", 9) == 0) {
// Enable global repeat switch (bitmask settings are preserved)
_prefs->disable_fwd = false;
savePrefs();
strcpy(reply, "OK - repeating enabled");
} else if (memcmp(config, "repeat off", 10) == 0) {
// Disable global repeat switch (bitmask settings are preserved)
_prefs->disable_fwd = true;
savePrefs();
strcpy(reply, "OK - repeating disabled");
} else if (memcmp(config, "repeat ", 7) == 0) {
const char* rest = &config[7];

// Check for advert sub-type commands: "repeat advert.sensor on/off" or "repeat advert.max_hops <value>"
if (memcmp(rest, "advert.", 7) == 0) {
const char* adv_type_and_state = &rest[7];

// Special handling for max_hops
if (memcmp(adv_type_and_state, "max_hops ", 9) == 0) {
const char* value_str = &adv_type_and_state[9];
int value = atoi(value_str);
if (value < 0 || value > 255) {
strcpy(reply, "Error: max_hops must be 0-255 (0 = unlimited)");
} else {
_prefs->advert_max_hops = (uint8_t)value;
savePrefs();
if (value == 0) {
strcpy(reply, "OK - advert max hops set to unlimited");
} else {
sprintf(reply, "OK - advert max hops set to %d (excluding companion adverts)", value);
}
}
}
// Handle advert sub-type on/off commands
else {
// Find space separating type from on/off
const char* space_pos = strchr(adv_type_and_state, ' ');
if (space_pos != NULL) {
int type_len = space_pos - adv_type_and_state;
char adv_type_name[16];
if (type_len < 16) {
memcpy(adv_type_name, adv_type_and_state, type_len);
adv_type_name[type_len] = 0;

const char* state = space_pos + 1;
int adv_type_idx = mesh::findAdvertTypeByName(adv_type_name);

if (adv_type_idx >= 0) {
if (memcmp(state, "on", 2) == 0) {
_prefs->repeat_advert_types |= (1 << adv_type_idx);

// Automatically enable advert packet type if it's off
if ((_prefs->repeat_packet_types & (1 << PAYLOAD_TYPE_ADVERT)) == 0) {
_prefs->repeat_packet_types |= (1 << PAYLOAD_TYPE_ADVERT);
}

savePrefs();
sprintf(reply, "OK - %s adverts will be repeated", adv_type_name);
} else if (memcmp(state, "off", 3) == 0) {
_prefs->repeat_advert_types &= ~(1 << adv_type_idx);
savePrefs();
sprintf(reply, "OK - %s adverts will not be repeated", adv_type_name);
} else {
strcpy(reply, "Error: use 'on' or 'off'");
}
} else {
strcpy(reply, "Error: unknown advert type (use: none,chat,repeater,room,sensor)");
}
} else {
strcpy(reply, "Error: advert type name too long");
}
} else {
strcpy(reply, "Error: format is 'set repeat advert.<type> on/off'");
}
}
}
// Check for packet type commands: "repeat advert on/off", "repeat grp.txt on/off"
else {
// Find space separating type from on/off
const char* space_pos = strchr(rest, ' ');
if (space_pos != NULL) {
int type_len = space_pos - rest;
char type_name[16];
if (type_len < 16) {
memcpy(type_name, rest, type_len);
type_name[type_len] = 0;

const char* state = space_pos + 1;
int type_idx = mesh::findPacketTypeByName(type_name);

if (type_idx >= 0) {
if (memcmp(state, "on", 2) == 0) {
_prefs->repeat_packet_types |= (1 << type_idx); // Set bit = repeat ON

// Special handling for advert: enable all advert sub-types
if (type_idx == PAYLOAD_TYPE_ADVERT) {
_prefs->repeat_advert_types = (1 << mesh::MAX_ADVERT_TYPES) - 1; // All bits set
}

savePrefs();
sprintf(reply, "OK - %s packets will be repeated", type_name);
} else if (memcmp(state, "off", 3) == 0) {
_prefs->repeat_packet_types &= ~(1 << type_idx); // Clear bit = repeat OFF

// Special handling for advert: clear all advert sub-types
if (type_idx == PAYLOAD_TYPE_ADVERT) {
_prefs->repeat_advert_types = 0x00; // All bits clear
}

savePrefs();
if (type_idx == PAYLOAD_TYPE_ADVERT) {
sprintf(reply, "OK - %s packets will not be repeated (use 'set repeat advert.<type> on' for exceptions)", type_name);
} else {
sprintf(reply, "OK - %s packets will not be repeated", type_name);
}
} else {
strcpy(reply, "Error: use 'on' or 'off'");
}
} else {
strcpy(reply, "Error: unknown type (use: req,resp,txt,ack,advert,grp.txt,grp.data,anon,path,trace,multi,control,raw)");
}
} else {
strcpy(reply, "Error: packet type name too long");
}
} else {
strcpy(reply, "Error: format is 'set repeat <type> on/off'");
}
}
} else {
sprintf(reply, "unknown config: %s", config);
}
Expand Down
3 changes: 3 additions & 0 deletions src/helpers/CommonCLI.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ struct NodePrefs { // persisted to file
uint8_t advert_loc_policy;
uint32_t discovery_mod_timestamp;
float adc_multiplier;
uint16_t repeat_packet_types; // Bitmask: bit=1 means ALLOW repeat (0-15)
uint8_t repeat_advert_types; // Bitmask: bit=1 means ALLOW advert type through
uint8_t advert_max_hops; // Max hops for advert packets (0 = no limit)
};

class CommonCLICallbacks {
Expand Down
Loading