From 7f1265bfe857092de229a1d9126c05ffb29eec96 Mon Sep 17 00:00:00 2001 From: Kemal Hadimli Date: Tue, 9 Jun 2026 15:29:58 +0100 Subject: [PATCH] Guard channel content filter behind WITH_CHANNEL_FILTER Feature is now fully compiled out unless built with -D WITH_CHANNEL_FILTER. Co-Authored-By: Claude Opus 4.8 --- docs/cli_commands.md | 4 +++- examples/simple_repeater/MyMesh.cpp | 13 ++++++++++++- examples/simple_repeater/MyMesh.h | 9 +++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 071e5db58..0be4fb25f 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -118,6 +118,8 @@ This document provides an overview of CLI commands that can be sent to MeshCore ## Channel Content Filter (Repeater Only) +> **Compile-time feature.** These commands only exist when the firmware is built with `-D WITH_CHANNEL_FILTER`. Without that build flag the feature is fully compiled out (no code, no storage, no CLI commands) and the repeater behaves exactly as stock. + Lets a repeater decrypt channels it holds the key for, inspect the plaintext, and refuse to retransmit messages that match a blocked keyword or sender name. With nothing configured, behaviour is identical to a stock repeater. **How it works:** the repeater only decrypts channels you explicitly load a key for (see `filter channel`). For those channels it reads the sender name and message text; any message matching a blocked keyword (text, case-insensitive substring) or blocked sender (case-insensitive substring of the sender name) is dropped instead of forwarded. All other channels — and direct messages — are never decrypted and forward exactly as before. @@ -131,7 +133,7 @@ Lets a repeater decrypt channels it holds the key for, inspect the plaintext, an **Config storage:** persisted to `/channel_filter` on the node's filesystem. -**Build flags:** `MAX_FILTER_CHANNELS` (default `4`), `MAX_FILTER_TERMS` (default `8`, applies to keywords and senders independently). +**Build flags:** `WITH_CHANNEL_FILTER` (required, enables the feature), `MAX_FILTER_CHANNELS` (default `4`), `MAX_FILTER_TERMS` (default `8`, applies to keywords and senders independently). --- diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index d562437c3..f0d7e6be7 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1,8 +1,10 @@ #include "MyMesh.h" -#include "UnicodeFold.h" #include #include +#ifdef WITH_CHANNEL_FILTER +#include "UnicodeFold.h" + /* --------------------- public-channel content filter ------------------ */ // The well-known MeshCore public channel PSK ("izOH6cXN6mrJ5e26oRXNcg==") @@ -35,6 +37,7 @@ static int decodeBase64(const char* in, uint8_t* out, int max_out) { } return n; } +#endif // WITH_CHANNEL_FILTER /* ------------------------------ Config -------------------------------- */ @@ -663,6 +666,7 @@ void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) { } } +#ifdef WITH_CHANNEL_FILTER bool MyMesh::addFilterChannel(const char *psk_b64) { if (num_filter_channels >= MAX_FILTER_CHANNELS) return false; @@ -865,6 +869,7 @@ void MyMesh::handleFilterCommand(char *command, char *reply) { } strcpy(reply, "Err - usage: filter [list|stats [reset]|channel |block |sender |clear|reset]"); } +#endif // WITH_CHANNEL_FILTER static bool isShare(const mesh::Packet *packet) { if (packet->hasTransportCodes()) { @@ -1101,10 +1106,12 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc { last_millis = 0; uptime_millis = 0; +#ifdef WITH_CHANNEL_FILTER num_filter_channels = 0; num_block_keywords = 0; num_block_senders = 0; n_filtered = 0; +#endif next_local_advert = next_flood_advert = 0; dirty_contacts_expiry = 0; set_radio_at = revert_radio_at = 0; @@ -1173,7 +1180,9 @@ void MyMesh::begin(FILESYSTEM *fs) { // load persisted prefs _cli.loadPrefs(_fs); acl.load(_fs, self_id); +#ifdef WITH_CHANNEL_FILTER loadChannelFilter(); +#endif // TODO: key_store.begin(); region_map.load(_fs); @@ -1501,8 +1510,10 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply sendNodeDiscoverReq(); strcpy(reply, "OK - Discover sent"); } +#ifdef WITH_CHANNEL_FILTER } else if (memcmp(command, "filter", 6) == 0 && (command[6] == 0 || command[6] == ' ')) { handleFilterCommand(command, reply); +#endif } else{ _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 1dd8f8e60..12d3357d8 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -79,6 +79,8 @@ struct NeighbourInfo { #define FIRMWARE_ROLE "repeater" #define PACKET_LOG_FILE "/packet_log" + +#ifdef WITH_CHANNEL_FILTER #define CHANNEL_FILTER_FILE "/channel_filter" #ifndef MAX_FILTER_CHANNELS @@ -89,6 +91,7 @@ struct NeighbourInfo { #endif #define FILTER_TERM_LEN 24 #define FILTER_PSK_B64_LEN 48 +#endif class MyMesh : public mesh::Mesh, public CommonCLICallbacks { FILESYSTEM* _fs; @@ -124,6 +127,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { uint8_t pending_cr; int matching_peer_indexes[MAX_CLIENTS]; +#ifdef WITH_CHANNEL_FILTER mesh::GroupChannel filter_channels[MAX_FILTER_CHANNELS]; char filter_channel_psk[MAX_FILTER_CHANNELS][FILTER_PSK_B64_LEN]; uint8_t num_filter_channels; @@ -132,6 +136,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { char block_senders[MAX_FILTER_TERMS][FILTER_TERM_LEN]; uint8_t num_block_senders; uint32_t n_filtered; +#endif #if defined(WITH_RS232_BRIDGE) RS232Bridge bridge; #elif defined(WITH_ESPNOW_BRIDGE) @@ -149,10 +154,12 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { File openAppend(const char* fname); bool isLooped(const mesh::Packet* packet, const uint8_t max_counters[]); +#ifdef WITH_CHANNEL_FILTER bool addFilterChannel(const char* psk_b64); void loadChannelFilter(); void saveChannelFilter(); void handleFilterCommand(char* command, char* reply); +#endif protected: float getAirtimeBudgetFactor() const override { @@ -191,8 +198,10 @@ protected: void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override; int searchPeersByHash(const uint8_t* hash) override; +#ifdef WITH_CHANNEL_FILTER int searchChannelsByHash(const uint8_t* hash, mesh::GroupChannel channels[], int max_matches) override; void onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mesh::GroupChannel& channel, uint8_t* data, size_t len) override; +#endif void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override; void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len); void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override;