diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 33e32a68a..09f1207b8 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -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; } @@ -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) { diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 343aa44f5..50bc048d9 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 78e1b5e0b..f69ab9b2f 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -2,6 +2,7 @@ #include "CommonCLI.h" #include "TxtDataHelpers.h" #include "AdvertDataHelpers.h" +#include "PacketTypeNames.h" #include // Believe it or not, this std C function is busted on some platforms! @@ -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); @@ -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 } } @@ -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(); } @@ -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) { @@ -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]; @@ -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 " + 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. 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. 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 on/off'"); + } + } } else { sprintf(reply, "unknown config: %s", config); } diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 642a4cce3..05c1c0fab 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -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 { diff --git a/src/helpers/PacketTypeNames.h b/src/helpers/PacketTypeNames.h new file mode 100644 index 000000000..801d995f6 --- /dev/null +++ b/src/helpers/PacketTypeNames.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include + +namespace mesh { + +constexpr int MAX_PACKET_TYPES = 16; +constexpr int MAX_ADVERT_TYPES = 5; + +// Packet type name strings - stored in flash memory +// These correspond to PAYLOAD_TYPE_* values 0x00 through 0x0F +static const char* const PACKET_TYPE_NAMES[MAX_PACKET_TYPES] = { + "req", // 0x00 PAYLOAD_TYPE_REQ + "resp", // 0x01 PAYLOAD_TYPE_RESPONSE + "txt", // 0x02 PAYLOAD_TYPE_TXT_MSG + "ack", // 0x03 PAYLOAD_TYPE_ACK + "advert", // 0x04 PAYLOAD_TYPE_ADVERT + "grp.txt", // 0x05 PAYLOAD_TYPE_GRP_TXT + "grp.data", // 0x06 PAYLOAD_TYPE_GRP_DATA + "anon", // 0x07 PAYLOAD_TYPE_ANON_REQ + "path", // 0x08 PAYLOAD_TYPE_PATH + "trace", // 0x09 PAYLOAD_TYPE_TRACE + "multi", // 0x0A PAYLOAD_TYPE_MULTIPART + "control", // 0x0B PAYLOAD_TYPE_CONTROL + "12", // 0x0C (reserved) + "13", // 0x0D (reserved) + "14", // 0x0E (reserved) + "raw" // 0x0F PAYLOAD_TYPE_RAW_CUSTOM +}; + +// Advert type name strings - stored in flash memory +// These correspond to ADV_TYPE_* values 0 through 4 +static const char* const ADVERT_TYPE_NAMES[MAX_ADVERT_TYPES] = { + "none", // 0 ADV_TYPE_NONE + "chat", // 1 ADV_TYPE_CHAT (companion radios) + "repeater", // 2 ADV_TYPE_REPEATER + "room", // 3 ADV_TYPE_ROOM (room servers) + "sensor" // 4 ADV_TYPE_SENSOR +}; + +// Helper function to safely get packet type name +inline const char* getPacketTypeName(uint8_t type) { + if (type >= MAX_PACKET_TYPES) return "unknown"; + return PACKET_TYPE_NAMES[type]; +} + +// Helper function to find packet type index by name +inline int findPacketTypeByName(const char* name) { + for (int i = 0; i < MAX_PACKET_TYPES; i++) { + if (strcmp(name, PACKET_TYPE_NAMES[i]) == 0) { + return i; + } + } + return -1; +} + +// Helper function to safely get advert type name +inline const char* getAdvertTypeName(uint8_t type) { + if (type >= MAX_ADVERT_TYPES) return "unknown"; + return ADVERT_TYPE_NAMES[type]; +} + +// Helper function to find advert type index by name +inline int findAdvertTypeByName(const char* name) { + for (int i = 0; i < MAX_ADVERT_TYPES; i++) { + if (strcmp(name, ADVERT_TYPE_NAMES[i]) == 0) { + return i; + } + } + return -1; +} + +}