|
|
|
@ -43,6 +43,9 @@ |
|
|
|
|
|
|
|
#define FIRMWARE_VER_LEVEL 2 |
|
|
|
|
|
|
|
#define PATH_BLACKLIST_FILE "/path_bl" |
|
|
|
#define CHAN_BLACKLIST_FILE "/chan_bl" |
|
|
|
|
|
|
|
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
|
|
|
|
#define REQ_TYPE_KEEP_ALIVE 0x02 |
|
|
|
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03 |
|
|
|
@ -545,6 +548,293 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { |
|
|
|
return getRNG()->nextInt(0, 5*t + 1); |
|
|
|
} |
|
|
|
|
|
|
|
/* ----------------------- Blacklist helpers -------------------------------- */ |
|
|
|
|
|
|
|
// Returns true if any path entry in the packet matches a path-prefix blacklist entry.
|
|
|
|
// Comparison uses min(entry.len, hash_size) bytes, so shorter entries match any hash size.
|
|
|
|
bool MyMesh::isPathBlacklisted(const mesh::Packet* packet) const { |
|
|
|
uint8_t hash_size = packet->getPathHashSize(); |
|
|
|
uint8_t hash_count = packet->getPathHashCount(); |
|
|
|
const uint8_t* ptr = packet->path; |
|
|
|
for (uint8_t h = 0; h < hash_count; h++, ptr += hash_size) { |
|
|
|
for (int b = 0; b < MAX_BLACKLIST_ENTRIES; b++) { |
|
|
|
if (_path_blacklist[b].len == 0) continue; |
|
|
|
uint8_t cmp_len = (_path_blacklist[b].len < hash_size) ? _path_blacklist[b].len : hash_size; |
|
|
|
if (memcmp(ptr, _path_blacklist[b].prefix, cmp_len) == 0) return true; |
|
|
|
} |
|
|
|
} |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// Returns true if the channel hash at the start of a GRP_TXT/GRP_DATA payload matches
|
|
|
|
// a channel-hash blacklist entry (hex prefix or #channel_name with decrypt verification).
|
|
|
|
bool MyMesh::isChanBlacklisted(const mesh::Packet* packet) const { |
|
|
|
uint8_t pt = packet->getPayloadType(); |
|
|
|
if (pt != PAYLOAD_TYPE_GRP_TXT && pt != PAYLOAD_TYPE_GRP_DATA) return false; |
|
|
|
if (packet->payload_len < PATH_HASH_SIZE) return false; |
|
|
|
|
|
|
|
// Check hex prefix entries
|
|
|
|
for (int b = 0; b < MAX_BLACKLIST_ENTRIES; b++) { |
|
|
|
if (_chan_blacklist[b].len == 0) continue; |
|
|
|
uint8_t avail = (packet->payload_len < MAX_PATH_PREFIX_LEN) ? (uint8_t)packet->payload_len : MAX_PATH_PREFIX_LEN; |
|
|
|
uint8_t cmp_len = (_chan_blacklist[b].len < avail) ? _chan_blacklist[b].len : avail; |
|
|
|
if (memcmp(packet->payload, _chan_blacklist[b].prefix, cmp_len) == 0) return true; |
|
|
|
} |
|
|
|
|
|
|
|
// Check #channel_name entries with test decryption
|
|
|
|
if (_num_chan_name_filters > 0 && packet->payload_len > PATH_HASH_SIZE + CIPHER_MAC_SIZE) { |
|
|
|
uint8_t channel_hash = packet->payload[0]; |
|
|
|
const uint8_t* macAndData = &packet->payload[PATH_HASH_SIZE]; // MAC + encrypted data
|
|
|
|
int enc_len = packet->payload_len - PATH_HASH_SIZE; |
|
|
|
|
|
|
|
for (int i = 0; i < _num_chan_name_filters; i++) { |
|
|
|
// Quick hash check first
|
|
|
|
if (channel_hash != _chan_name_filters[i].hash[0]) continue; |
|
|
|
// Try to decrypt to verify this is actually the channel
|
|
|
|
uint8_t tmp[MAX_PACKET_PAYLOAD]; |
|
|
|
int len = mesh::Utils::MACThenDecrypt(_chan_name_filters[i].secret, tmp, macAndData, enc_len); |
|
|
|
if (len > 0) return true; // Successfully decrypted, confirmed channel match
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
bool MyMesh::addToBlacklist(BlacklistEntry* list, const uint8_t* prefix, uint8_t len) { |
|
|
|
if (len == 0 || len > MAX_PATH_PREFIX_LEN) return false; |
|
|
|
// if already present, consider success
|
|
|
|
for (int i = 0; i < MAX_BLACKLIST_ENTRIES; i++) { |
|
|
|
if (list[i].len == len && memcmp(list[i].prefix, prefix, len) == 0) return true; |
|
|
|
} |
|
|
|
// find an empty slot
|
|
|
|
for (int i = 0; i < MAX_BLACKLIST_ENTRIES; i++) { |
|
|
|
if (list[i].len == 0) { |
|
|
|
list[i].len = len; |
|
|
|
memcpy(list[i].prefix, prefix, len); |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
return false; // list is full
|
|
|
|
} |
|
|
|
|
|
|
|
bool MyMesh::removeFromBlacklist(BlacklistEntry* list, const uint8_t* prefix, uint8_t len) { |
|
|
|
for (int i = 0; i < MAX_BLACKLIST_ENTRIES; i++) { |
|
|
|
if (list[i].len == len && memcmp(list[i].prefix, prefix, len) == 0) { |
|
|
|
list[i].len = 0; // mark slot as empty
|
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
void MyMesh::formatBlacklist(const BlacklistEntry* list, char* reply) { |
|
|
|
char* dp = reply; |
|
|
|
int count = 0; |
|
|
|
for (int i = 0; i < MAX_BLACKLIST_ENTRIES; i++) { |
|
|
|
if (list[i].len == 0) continue; |
|
|
|
if (count > 0) *dp++ = '\n'; |
|
|
|
mesh::Utils::toHex(dp, list[i].prefix, list[i].len); |
|
|
|
dp += list[i].len * 2; |
|
|
|
count++; |
|
|
|
} |
|
|
|
if (count == 0) { |
|
|
|
strcpy(reply, "-none-"); |
|
|
|
} else { |
|
|
|
*dp = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void MyMesh::loadBlacklist(const char* fname, BlacklistEntry* list) { |
|
|
|
memset(list, 0, sizeof(BlacklistEntry) * MAX_BLACKLIST_ENTRIES); |
|
|
|
#if defined(RP2040_PLATFORM) |
|
|
|
File f = _fs->open(fname, "r"); |
|
|
|
#else |
|
|
|
File f = _fs->open(fname); |
|
|
|
#endif |
|
|
|
if (!f) return; |
|
|
|
int idx = 0; |
|
|
|
char line[MAX_PATH_PREFIX_LEN * 2 + 4]; |
|
|
|
int line_len = 0; |
|
|
|
while (f.available() && idx < MAX_BLACKLIST_ENTRIES) { |
|
|
|
int c = f.read(); |
|
|
|
if (c < 0) break; |
|
|
|
if (c == '\n' || c == '\r') { |
|
|
|
if (line_len >= 2 && (line_len % 2 == 0)) { |
|
|
|
line[line_len] = 0; |
|
|
|
uint8_t prefix[MAX_PATH_PREFIX_LEN]; |
|
|
|
int byte_len = line_len / 2; |
|
|
|
if (byte_len <= MAX_PATH_PREFIX_LEN && mesh::Utils::fromHex(prefix, byte_len, line)) { |
|
|
|
list[idx].len = (uint8_t)byte_len; |
|
|
|
memcpy(list[idx].prefix, prefix, byte_len); |
|
|
|
idx++; |
|
|
|
} |
|
|
|
} |
|
|
|
line_len = 0; |
|
|
|
} else if (line_len < (int)(sizeof(line) - 1)) { |
|
|
|
line[line_len++] = (char)c; |
|
|
|
} |
|
|
|
} |
|
|
|
// handle last line with no trailing newline
|
|
|
|
if (line_len >= 2 && (line_len % 2 == 0) && idx < MAX_BLACKLIST_ENTRIES) { |
|
|
|
line[line_len] = 0; |
|
|
|
uint8_t prefix[MAX_PATH_PREFIX_LEN]; |
|
|
|
int byte_len = line_len / 2; |
|
|
|
if (byte_len <= MAX_PATH_PREFIX_LEN && mesh::Utils::fromHex(prefix, byte_len, line)) { |
|
|
|
list[idx].len = (uint8_t)byte_len; |
|
|
|
memcpy(list[idx].prefix, prefix, byte_len); |
|
|
|
} |
|
|
|
} |
|
|
|
f.close(); |
|
|
|
} |
|
|
|
|
|
|
|
void MyMesh::saveBlacklist(const char* fname, const BlacklistEntry* list) { |
|
|
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) |
|
|
|
_fs->remove(fname); |
|
|
|
File f = _fs->open(fname, FILE_O_WRITE); |
|
|
|
#elif defined(RP2040_PLATFORM) |
|
|
|
File f = _fs->open(fname, "w"); |
|
|
|
#else |
|
|
|
File f = _fs->open(fname, "w", true); |
|
|
|
#endif |
|
|
|
if (!f) return; |
|
|
|
for (int i = 0; i < MAX_BLACKLIST_ENTRIES; i++) { |
|
|
|
if (list[i].len == 0) continue; |
|
|
|
char hex[MAX_PATH_PREFIX_LEN * 2 + 2]; |
|
|
|
mesh::Utils::toHex(hex, list[i].prefix, list[i].len); |
|
|
|
f.println(hex); |
|
|
|
} |
|
|
|
f.close(); |
|
|
|
} |
|
|
|
|
|
|
|
/* ------------------- Channel name filter helpers -------------------------- */ |
|
|
|
|
|
|
|
void MyMesh::deriveChanNameFilter(ChanNameFilter& entry, const char* name) { |
|
|
|
// Derive channel secret: first 16 bytes of sha256(name)
|
|
|
|
uint8_t full_hash[32]; |
|
|
|
mesh::Utils::sha256(full_hash, 32, (const uint8_t*)name, strlen(name)); |
|
|
|
memcpy(entry.secret, full_hash, CIPHER_KEY_SIZE); |
|
|
|
// Derive channel hash from the secret
|
|
|
|
mesh::Utils::sha256(entry.hash, sizeof(entry.hash), entry.secret, CIPHER_KEY_SIZE); |
|
|
|
StrHelper::strncpy(entry.name, name, sizeof(entry.name)); |
|
|
|
} |
|
|
|
|
|
|
|
bool MyMesh::addChanNameFilter(const char* name) { |
|
|
|
// Check for duplicate
|
|
|
|
for (int i = 0; i < _num_chan_name_filters; i++) { |
|
|
|
if (strcmp(_chan_name_filters[i].name, name) == 0) return true; // already exists
|
|
|
|
} |
|
|
|
if (_num_chan_name_filters >= MAX_CHAN_NAME_FILTERS) return false; // full
|
|
|
|
deriveChanNameFilter(_chan_name_filters[_num_chan_name_filters], name); |
|
|
|
_num_chan_name_filters++; |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
bool MyMesh::removeChanNameFilter(const char* name) { |
|
|
|
for (int i = 0; i < _num_chan_name_filters; i++) { |
|
|
|
if (strcmp(_chan_name_filters[i].name, name) == 0) { |
|
|
|
// shift remaining entries down
|
|
|
|
for (int j = i; j < _num_chan_name_filters - 1; j++) { |
|
|
|
_chan_name_filters[j] = _chan_name_filters[j + 1]; |
|
|
|
} |
|
|
|
_num_chan_name_filters--; |
|
|
|
memset(&_chan_name_filters[_num_chan_name_filters], 0, sizeof(ChanNameFilter)); |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
void MyMesh::loadChanBlacklist(const char* fname) { |
|
|
|
// Load both hex prefix entries and #channel_name entries from the same file
|
|
|
|
loadBlacklist(fname, _chan_blacklist); // loads hex entries into _chan_blacklist
|
|
|
|
|
|
|
|
// Now re-read the file to pick up #channel_name lines
|
|
|
|
_num_chan_name_filters = 0; |
|
|
|
memset(_chan_name_filters, 0, sizeof(_chan_name_filters)); |
|
|
|
#if defined(RP2040_PLATFORM) |
|
|
|
File f = _fs->open(fname, "r"); |
|
|
|
#else |
|
|
|
File f = _fs->open(fname); |
|
|
|
#endif |
|
|
|
if (!f) return; |
|
|
|
char line[40]; |
|
|
|
int line_len = 0; |
|
|
|
while (f.available() && _num_chan_name_filters < MAX_CHAN_NAME_FILTERS) { |
|
|
|
int c = f.read(); |
|
|
|
if (c < 0) break; |
|
|
|
if (c == '\n' || c == '\r') { |
|
|
|
if (line_len > 0 && line[0] == '#') { |
|
|
|
line[line_len] = 0; |
|
|
|
deriveChanNameFilter(_chan_name_filters[_num_chan_name_filters], line); |
|
|
|
_num_chan_name_filters++; |
|
|
|
} |
|
|
|
line_len = 0; |
|
|
|
} else if (line_len < (int)(sizeof(line) - 1)) { |
|
|
|
line[line_len++] = (char)c; |
|
|
|
} |
|
|
|
} |
|
|
|
// handle last line with no trailing newline
|
|
|
|
if (line_len > 0 && line[0] == '#' && _num_chan_name_filters < MAX_CHAN_NAME_FILTERS) { |
|
|
|
line[line_len] = 0; |
|
|
|
deriveChanNameFilter(_chan_name_filters[_num_chan_name_filters], line); |
|
|
|
_num_chan_name_filters++; |
|
|
|
} |
|
|
|
f.close(); |
|
|
|
} |
|
|
|
|
|
|
|
void MyMesh::saveChanBlacklist(const char* fname) { |
|
|
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) |
|
|
|
_fs->remove(fname); |
|
|
|
File f = _fs->open(fname, FILE_O_WRITE); |
|
|
|
#elif defined(RP2040_PLATFORM) |
|
|
|
File f = _fs->open(fname, "w"); |
|
|
|
#else |
|
|
|
File f = _fs->open(fname, "w", true); |
|
|
|
#endif |
|
|
|
if (!f) return; |
|
|
|
// Write hex prefix entries
|
|
|
|
for (int i = 0; i < MAX_BLACKLIST_ENTRIES; i++) { |
|
|
|
if (_chan_blacklist[i].len == 0) continue; |
|
|
|
char hex[MAX_PATH_PREFIX_LEN * 2 + 2]; |
|
|
|
mesh::Utils::toHex(hex, _chan_blacklist[i].prefix, _chan_blacklist[i].len); |
|
|
|
f.println(hex); |
|
|
|
} |
|
|
|
// Write #channel_name entries
|
|
|
|
for (int i = 0; i < _num_chan_name_filters; i++) { |
|
|
|
f.println(_chan_name_filters[i].name); |
|
|
|
} |
|
|
|
f.close(); |
|
|
|
} |
|
|
|
|
|
|
|
void MyMesh::formatChanBlacklist(char* reply) { |
|
|
|
char* dp = reply; |
|
|
|
char* end = reply + MAX_PACKET_PAYLOAD - 6; // leave room for null + safety
|
|
|
|
int count = 0; |
|
|
|
// Format hex prefix entries
|
|
|
|
for (int i = 0; i < MAX_BLACKLIST_ENTRIES; i++) { |
|
|
|
if (_chan_blacklist[i].len == 0) continue; |
|
|
|
if (dp + _chan_blacklist[i].len * 2 + 1 >= end) break; |
|
|
|
if (count > 0) *dp++ = '\n'; |
|
|
|
mesh::Utils::toHex(dp, _chan_blacklist[i].prefix, _chan_blacklist[i].len); |
|
|
|
dp += _chan_blacklist[i].len * 2; |
|
|
|
count++; |
|
|
|
} |
|
|
|
// Format #channel_name entries
|
|
|
|
for (int i = 0; i < _num_chan_name_filters; i++) { |
|
|
|
int len = strlen(_chan_name_filters[i].name); |
|
|
|
if (dp + len + 1 >= end) break; |
|
|
|
if (count > 0) *dp++ = '\n'; |
|
|
|
memcpy(dp, _chan_name_filters[i].name, len); |
|
|
|
dp += len; |
|
|
|
count++; |
|
|
|
} |
|
|
|
if (count == 0) { |
|
|
|
strcpy(reply, "-none-"); |
|
|
|
} else { |
|
|
|
*dp = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) { |
|
|
|
// just try to determine region for packet (apply later in allowPacketForward())
|
|
|
|
if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) { |
|
|
|
@ -558,6 +848,19 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) { |
|
|
|
} else { |
|
|
|
recv_pkt_region = NULL; |
|
|
|
} |
|
|
|
|
|
|
|
// Drop packets whose path contains a blacklisted node prefix
|
|
|
|
if (isPathBlacklisted(pkt)) { |
|
|
|
MESH_DEBUG_PRINTLN("filterRecvFloodPacket: path prefix blacklisted, dropping!"); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
// Drop group-channel packets whose channel hash is blacklisted
|
|
|
|
if (isChanBlacklisted(pkt)) { |
|
|
|
MESH_DEBUG_PRINTLN("filterRecvFloodPacket: channel hash blacklisted, dropping!"); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
// do normal processing
|
|
|
|
return false; |
|
|
|
} |
|
|
|
@ -864,6 +1167,11 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc |
|
|
|
_logging = false; |
|
|
|
region_load_active = false; |
|
|
|
|
|
|
|
memset(_path_blacklist, 0, sizeof(_path_blacklist)); |
|
|
|
memset(_chan_blacklist, 0, sizeof(_chan_blacklist)); |
|
|
|
memset(_chan_name_filters, 0, sizeof(_chan_name_filters)); |
|
|
|
_num_chan_name_filters = 0; |
|
|
|
|
|
|
|
#if MAX_NEIGHBOURS |
|
|
|
memset(neighbours, 0, sizeof(neighbours)); |
|
|
|
#endif |
|
|
|
@ -926,6 +1234,8 @@ void MyMesh::begin(FILESYSTEM *fs) { |
|
|
|
acl.load(_fs, self_id); |
|
|
|
// TODO: key_store.begin();
|
|
|
|
region_map.load(_fs); |
|
|
|
loadBlacklist(PATH_BLACKLIST_FILE, _path_blacklist); |
|
|
|
loadChanBlacklist(CHAN_BLACKLIST_FILE); |
|
|
|
|
|
|
|
// establish default-scope
|
|
|
|
{ |
|
|
|
@ -1251,6 +1561,93 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply |
|
|
|
sendNodeDiscoverReq(); |
|
|
|
strcpy(reply, "OK - Discover sent"); |
|
|
|
} |
|
|
|
} else if (memcmp(command, "blacklist ", 10) == 0) { |
|
|
|
// Commands:
|
|
|
|
// blacklist path list
|
|
|
|
// blacklist path add <hex>[,<hex>,...] (each hex = 2-8 even chars → 1-4 bytes)
|
|
|
|
// blacklist path rem <hex>[,<hex>,...]
|
|
|
|
// blacklist path clear
|
|
|
|
// blacklist chan list
|
|
|
|
// blacklist chan add <hex|#name>[,<hex|#name>,...]
|
|
|
|
// blacklist chan rem <hex|#name>[,<hex|#name>,...]
|
|
|
|
// blacklist chan clear
|
|
|
|
const char* parts[5]; |
|
|
|
int n = mesh::Utils::parseTextParts(command, parts, 5, ' '); |
|
|
|
|
|
|
|
BlacklistEntry* list = NULL; |
|
|
|
const char* list_file = NULL; |
|
|
|
bool is_chan = false; |
|
|
|
if (n >= 2 && strcmp(parts[1], "path") == 0) { |
|
|
|
list = _path_blacklist; list_file = PATH_BLACKLIST_FILE; |
|
|
|
} else if (n >= 2 && strcmp(parts[1], "chan") == 0) { |
|
|
|
list = _chan_blacklist; list_file = CHAN_BLACKLIST_FILE; |
|
|
|
is_chan = true; |
|
|
|
} |
|
|
|
|
|
|
|
if (list && n >= 3 && strcmp(parts[2], "list") == 0) { |
|
|
|
if (is_chan) { |
|
|
|
formatChanBlacklist(reply); |
|
|
|
} else { |
|
|
|
formatBlacklist(list, reply); |
|
|
|
} |
|
|
|
} else if (list && n >= 3 && strcmp(parts[2], "clear") == 0) { |
|
|
|
memset(list, 0, sizeof(BlacklistEntry) * MAX_BLACKLIST_ENTRIES); |
|
|
|
if (is_chan) { |
|
|
|
_num_chan_name_filters = 0; |
|
|
|
memset(_chan_name_filters, 0, sizeof(_chan_name_filters)); |
|
|
|
saveChanBlacklist(list_file); |
|
|
|
} else { |
|
|
|
saveBlacklist(list_file, list); |
|
|
|
} |
|
|
|
strcpy(reply, "OK"); |
|
|
|
} else if (list && n >= 4 && (strcmp(parts[2], "add") == 0 || strcmp(parts[2], "rem") == 0)) { |
|
|
|
bool is_add = (parts[2][0] == 'a'); |
|
|
|
// parts[3] may be a comma-separated list of hex entries or #channel_name entries
|
|
|
|
char tokens[MAX_PATH_PREFIX_LEN * 2 * MAX_BLACKLIST_ENTRIES + MAX_BLACKLIST_ENTRIES + 2]; |
|
|
|
strncpy(tokens, parts[3], sizeof(tokens) - 1); |
|
|
|
tokens[sizeof(tokens) - 1] = 0; |
|
|
|
|
|
|
|
const char* tok_parts[MAX_BLACKLIST_ENTRIES]; |
|
|
|
int tok_n = mesh::Utils::parseTextParts(tokens, tok_parts, MAX_BLACKLIST_ENTRIES, ','); |
|
|
|
|
|
|
|
bool any_ok = false, any_err = false; |
|
|
|
for (int t = 0; t < tok_n; t++) { |
|
|
|
if (is_chan && tok_parts[t][0] == '#') { |
|
|
|
// #channel_name entry
|
|
|
|
bool ok = is_add ? addChanNameFilter(tok_parts[t]) |
|
|
|
: removeChanNameFilter(tok_parts[t]); |
|
|
|
if (ok) any_ok = true; else any_err = true; |
|
|
|
} else { |
|
|
|
int hex_str_len = strlen(tok_parts[t]); |
|
|
|
if (hex_str_len < 2 || hex_str_len > MAX_PATH_PREFIX_LEN * 2 || (hex_str_len % 2) != 0) { |
|
|
|
any_err = true; continue; |
|
|
|
} |
|
|
|
uint8_t prefix[MAX_PATH_PREFIX_LEN]; |
|
|
|
int byte_len = hex_str_len / 2; |
|
|
|
if (!mesh::Utils::fromHex(prefix, byte_len, tok_parts[t])) { |
|
|
|
any_err = true; continue; |
|
|
|
} |
|
|
|
bool ok = is_add ? addToBlacklist(list, prefix, (uint8_t)byte_len) |
|
|
|
: removeFromBlacklist(list, prefix, (uint8_t)byte_len); |
|
|
|
if (ok) any_ok = true; else any_err = true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// auto-save on any successful mutation
|
|
|
|
if (any_ok) { |
|
|
|
if (is_chan) { |
|
|
|
saveChanBlacklist(list_file); |
|
|
|
} else { |
|
|
|
saveBlacklist(list_file, list); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (any_ok && !any_err) strcpy(reply, "OK"); |
|
|
|
else if (any_ok && any_err) strcpy(reply, "OK (partial)"); |
|
|
|
else strcpy(reply, is_add ? "Err - list full or bad input" : "Err - not found or bad input"); |
|
|
|
} else { |
|
|
|
strcpy(reply, "Err - usage: blacklist <path|chan> <list|add|rem|clear> [hex|#name[,...]]"); |
|
|
|
} |
|
|
|
} else{ |
|
|
|
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
|
|
|
|
} |
|
|
|
|