From f573bd134657f8648c2d0380bd38e81340142cf6 Mon Sep 17 00:00:00 2001 From: Kemal Hadimli Date: Fri, 12 Jun 2026 09:49:53 +0100 Subject: [PATCH] filter channel: remove a single channel by key Add `filter channel remove <#name|psk|public>` to drop one channel instead of clearing all. Matched by the resolved key, so any equivalent spec works. Decode logic shared between add and remove via decodeChannelKey(). Co-Authored-By: Claude Opus 4.8 --- docs/cli_commands.md | 3 ++- examples/simple_repeater/MyMesh.cpp | 36 +++++++++++++++++++++++++---- examples/simple_repeater/MyMesh.h | 1 + 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 243d081f5..f4b1eb91b 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -1142,13 +1142,14 @@ Repeater only. Lets a repeater decrypt channels it holds the key for, inspect th - `filter channel <#name>` - `filter channel ` - `filter channel public` +- `filter channel remove <#name|psk|public>` - `filter channel clear` **Parameters:** - `#name`: A hashtag channel, given by its name (e.g. `#selftest`). The key is derived the same way MeshCore clients derive it — the first 16 bytes of `SHA256("#name")` — so you don't need to paste a key. Only works for hashtag channels (named channels like `public` use a random key, not a name-derived one). - `psk`: Channel pre-shared key, as either Base64 (the form MeshCore clients share, e.g. `izOH6cXN6mrJ5e26oRXNcg==`) or raw hex (`62be0e1267c401381f4ea6f44217d7a9`). Either way it must decode to 16 or 32 bytes. The literal `public` is a shortcut for the well-known public channel key. -**Note:** Adding a channel only enables decryption/inspection of that channel; messages still forward normally unless they match a blocked keyword or sender. `filter channel clear` removes all channel keys (the repeater stops decrypting and forwards everything blind again). +**Note:** Adding a channel only enables decryption/inspection of that channel; messages still forward normally unless they match a blocked keyword or sender. `filter channel remove ` drops a single channel — give it the same `#name`/PSK/`public` you added it with (it's matched by the resulting key, so any equivalent form works). `filter channel clear` removes all channel keys (the repeater stops decrypting and forwards everything blind again). --- diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 2fc2df1b1..8d4d80468 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -677,10 +677,8 @@ void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) { } #ifdef WITH_CHANNEL_FILTER -bool MyMesh::addFilterChannel(const char *psk) { - if (num_filter_channels >= MAX_FILTER_CHANNELS) return false; - - auto ch = &filter_channels[num_filter_channels]; +// Resolve a channel spec (#name | hex | base64 | "public") into a GroupChannel. +static bool decodeChannelKey(const char *psk, mesh::GroupChannel *ch) { memset(ch->secret, 0, sizeof(ch->secret)); int len; @@ -701,10 +699,29 @@ bool MyMesh::addFilterChannel(const char *psk) { if (len != 16 && len != 32) return false; mesh::Utils::sha256(ch->hash, sizeof(ch->hash), ch->secret, len); + return true; +} + +bool MyMesh::addFilterChannel(const char *psk) { + if (num_filter_channels >= MAX_FILTER_CHANNELS) return false; + if (!decodeChannelKey(psk, &filter_channels[num_filter_channels])) return false; num_filter_channels++; return true; } +bool MyMesh::removeFilterChannel(const char *psk) { + mesh::GroupChannel ch; + if (!decodeChannelKey(psk, &ch)) return false; + for (int i = 0; i < num_filter_channels; i++) { + if (memcmp(filter_channels[i].secret, ch.secret, sizeof(ch.secret)) == 0) { + for (int j = i; j < num_filter_channels - 1; j++) filter_channels[j] = filter_channels[j + 1]; + num_filter_channels--; + return true; + } + } + return false; // no channel with that key +} + int MyMesh::searchChannelsByHash(const uint8_t *hash, mesh::GroupChannel channels[], int max_matches) { int n = 0; for (int i = 0; i < num_filter_channels && n < max_matches; i++) { @@ -877,6 +894,15 @@ void MyMesh::handleFilterCommand(char *command, char *reply) { num_filter_channels = 0; saveChannelFilter(); strcpy(reply, "OK - channels cleared"); + } else if (memcmp(val, "remove ", 7) == 0) { + char *spec = val + 7; + while (*spec == ' ') spec++; + if (removeFilterChannel(spec)) { + saveChannelFilter(); + sprintf(reply, "OK - %d channel(s)", num_filter_channels); + } else { + strcpy(reply, "Err - channel not found"); + } } else if (addFilterChannel(val)) { saveChannelFilter(); sprintf(reply, "OK - %d channel(s)", num_filter_channels); @@ -918,7 +944,7 @@ void MyMesh::handleFilterCommand(char *command, char *reply) { strcpy(reply, "OK - filter reset"); return; } - strcpy(reply, "Err - usage: filter [list|stats [reset]|channel <#name|b64|hex|public|clear>|block |sender |clear|reset]"); + strcpy(reply, "Err - usage: filter [list|stats [reset]|channel <#name|b64|hex|public|remove |clear>|block |sender |clear|reset]"); } #endif // WITH_CHANNEL_FILTER diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index a62592259..0c3289ba0 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -155,6 +155,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #ifdef WITH_CHANNEL_FILTER bool addFilterChannel(const char* psk); + bool removeFilterChannel(const char* psk); void loadChannelFilter(); void saveChannelFilter(); void handleFilterCommand(char* command, char* reply);