* Mesh.cpp: optimisation to not retransmit packets handled by this node

* simple_repeater: now supports a remote CLI
This commit is contained in:
Scott Powell
2025-01-22 12:11:43 +11:00
parent 6d10fab4f3
commit 22e87836db
5 changed files with 133 additions and 42 deletions

View File

@@ -13,6 +13,7 @@
#include <helpers/StaticPoolPacketManager.h> #include <helpers/StaticPoolPacketManager.h>
#include <helpers/SimpleMeshTables.h> #include <helpers/SimpleMeshTables.h>
#include <helpers/IdentityStore.h> #include <helpers/IdentityStore.h>
#include <RTClib.h>
/* ------------------------------ Config -------------------------------- */ /* ------------------------------ Config -------------------------------- */
@@ -262,39 +263,81 @@ protected:
} }
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override { void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override {
if (type == PAYLOAD_TYPE_REQ) { // request (from a Known admin client!) int i = matching_peer_indexes[sender_idx];
int i = matching_peer_indexes[sender_idx]; if (i < 0 || i >= num_clients) { // get from our known_clients table (sender SHOULD already be known in this context)
MESH_DEBUG_PRINTLN("onPeerDataRecv: invalid peer idx: %d", i);
return;
}
auto client = &known_clients[i];
if (type == PAYLOAD_TYPE_REQ) { // request (from a Known admin client!)
uint32_t timestamp;
memcpy(&timestamp, data, 4);
if (i >= 0 && i < num_clients) { // get from our known_clients table (sender SHOULD already be known in this context) if (timestamp > client->last_timestamp) { // prevent replay attacks
auto client = &known_clients[i]; int reply_len = handleRequest(client, &data[4], len - 4);
if (reply_len == 0) return; // invalid command
uint32_t timestamp; client->last_timestamp = timestamp;
memcpy(&timestamp, data, 4);
if (timestamp > client->last_timestamp) { // prevent replay attacks if (packet->isRouteFlood()) {
int reply_len = handleRequest(client, &data[4], len - 4); // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
if (reply_len == 0) return; // invalid command mesh::Packet* path = createPathReturn(client->id, secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
client->last_timestamp = timestamp; if (path) sendFlood(path);
} else {
if (packet->isRouteFlood()) { mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len);
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response if (reply) {
mesh::Packet* path = createPathReturn(client->id, secret, packet->path, packet->path_len, if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); sendDirect(reply, client->out_path, client->out_path_len);
if (path) sendFlood(path); } else {
} else { sendFlood(reply);
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); }
if (reply) { }
if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT }
sendDirect(reply, client->out_path, client->out_path_len); }
} else { } else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { // a CLI command
sendFlood(reply); uint32_t timestamp;
} memcpy(&timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong)
uint flags = data[4]; // message attempt number, and other flags
if (flags == 0 && timestamp > client->last_timestamp) { // prevent replay attacks
client->last_timestamp = timestamp;
// len can be > original length, but 'text' will be padded with zeroes
data[len] = 0; // need to make a C string again, with null terminator
uint32_t ack_hash; // calc truncated hash of the message timestamp + text + sender pub_key, to prove to sender that we got it
mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, 5 + strlen((char *)&data[5]), client->id.pub_key, PUB_KEY_SIZE);
mesh::Packet* ack = createAck(ack_hash);
if (ack) {
if (client->out_path_len < 0) {
sendFlood(ack);
} else {
sendDirect(ack, client->out_path, client->out_path_len);
}
}
uint8_t temp[166];
handleCommand(timestamp, (const char *) &data[5], (char *) &temp[5]);
int text_len = strlen((char *) &temp[5]);
if (text_len > 0) {
uint32_t timestamp = getRTCClock()->getCurrentTime();
memcpy(temp, &timestamp, 4); // mostly an extra blob to help make packet_hash unique
temp[4] = 0;
// calc expected ACK reply
//mesh::Utils::sha256((uint8_t *)&expected_ack_crc, 4, temp, 5 + text_len, self_id.pub_key, PUB_KEY_SIZE);
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len);
if (reply) {
if (client->out_path_len < 0) {
sendFlood(reply);
} else {
sendDirect(reply, client->out_path, client->out_path_len);
} }
} }
} }
} else {
MESH_DEBUG_PRINTLN("onPeerDataRecv: invalid peer idx: %d", i);
} }
} }
} }
@@ -359,6 +402,31 @@ public:
MESH_DEBUG_PRINTLN("ERROR: unable to create advertisement packet!"); MESH_DEBUG_PRINTLN("ERROR: unable to create advertisement packet!");
} }
} }
void handleCommand(uint32_t sender_timestamp, const char* command, char reply[]) {
while (*command == ' ') command++; // skip leading spaces
if (memcmp(command, "reboot", 6) == 0) {
board.reboot(); // doesn't return
} else if (memcmp(command, "advert", 6) == 0) {
sendSelfAdvertisement();
strcpy(reply, "OK - Advert sent");
} else if (memcmp(command, "clock sync", 10) == 0) {
uint32_t curr = getRTCClock()->getCurrentTime();
if (sender_timestamp > curr) {
getRTCClock()->setCurrentTime(sender_timestamp);
strcpy(reply, "OK - clock set");
} else {
strcpy(reply, "ERR: clock cannot go backwards");
}
} else if (memcmp(command, "clock", 5) == 0) {
uint32_t now = getRTCClock()->getCurrentTime();
DateTime dt = DateTime(now);
sprintf(reply, "%02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
} else {
sprintf(reply, "Unknown: %s (commands: reboot, advert, clock)", command);
}
}
}; };
#if defined(NRF52_PLATFORM) #if defined(NRF52_PLATFORM)
@@ -456,13 +524,10 @@ void loop() {
if (len > 0 && command[len - 1] == '\r') { // received complete line if (len > 0 && command[len - 1] == '\r') { // received complete line
command[len - 1] = 0; // replace newline with C string null terminator command[len - 1] = 0; // replace newline with C string null terminator
if (strcmp(command, "reboot") == 0) { char reply[160];
board.reboot(); // doesn't return the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
} else if (strcmp(command, "advert") == 0) { if (reply[0]) {
the_mesh.sendSelfAdvertisement(); Serial.print(" -> "); Serial.println(reply);
} else {
Serial.print(" ERROR: unknown command: "); Serial.println(command);
Serial.println(" (commands: reboot, advert)");
} }
command[0] = 0; // reset command buffer command[0] = 0; // reset command buffer

View File

@@ -220,20 +220,24 @@ protected:
} }
void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override { void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override {
processAck((uint8_t *)&ack_crc); if (processAck((uint8_t *)&ack_crc)) {
packet->markDoNotRetransmit(); // ACK was for this node, so don't retransmit
}
} }
void processAck(const uint8_t *data) { bool processAck(const uint8_t *data) {
if (memcmp(data, &expected_ack_crc, 4) == 0) { // got an ACK from recipient if (memcmp(data, &expected_ack_crc, 4) == 0) { // got an ACK from recipient
Serial.printf(" Got ACK! (round trip: %d millis)\n", _ms->getMillis() - last_msg_sent); Serial.printf(" Got ACK! (round trip: %d millis)\n", _ms->getMillis() - last_msg_sent);
// NOTE: the same ACK can be received multiple times! // NOTE: the same ACK can be received multiple times!
expected_ack_crc = 0; // reset our expected hash, now that we have received ACK expected_ack_crc = 0; // reset our expected hash, now that we have received ACK
txt_send_timeout = 0; txt_send_timeout = 0;
} else { return true;
uint32_t crc;
memcpy(&crc, data, 4);
MESH_DEBUG_PRINTLN(" unknown ACK received: %08X (expected: %08X)", crc, expected_ack_crc);
} }
uint32_t crc;
memcpy(&crc, data, 4);
MESH_DEBUG_PRINTLN(" unknown ACK received: %08X (expected: %08X)", crc, expected_ack_crc);
return false;
} }
public: public:

View File

@@ -70,6 +70,9 @@ build_flags =
; -D MESH_PACKET_LOGGING=1 ; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1 ; -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/simple_repeater/main.cpp> build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/simple_repeater/main.cpp>
lib_deps =
${Heltec_lora32_v3.lib_deps}
adafruit/RTClib @ ^2.1.3
[env:Heltec_v3_chat_alice] [env:Heltec_v3_chat_alice]
extends = Heltec_lora32_v3 extends = Heltec_lora32_v3
@@ -126,6 +129,9 @@ build_flags =
-D ADMIN_PASSWORD="\"password\"" -D ADMIN_PASSWORD="\"password\""
; -D MESH_PACKET_LOGGING=1 ; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1 ; -D MESH_DEBUG=1
lib_deps =
${Xiao_esp32_C3.lib_deps}
adafruit/RTClib @ ^2.1.3
[env:Xiao_C3_Repeater_sx1268] [env:Xiao_C3_Repeater_sx1268]
extends = Xiao_esp32_C3 extends = Xiao_esp32_C3
@@ -141,6 +147,9 @@ build_flags =
-D ADMIN_PASSWORD="\"password\"" -D ADMIN_PASSWORD="\"password\""
; -D MESH_PACKET_LOGGING=1 ; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1 ; -D MESH_DEBUG=1
lib_deps =
${Xiao_esp32_C3.lib_deps}
adafruit/RTClib @ ^2.1.3
; ============= ; =============
[Xiao_S3_WIO] [Xiao_S3_WIO]
@@ -175,6 +184,9 @@ build_flags =
-D ADMIN_PASSWORD="\"password\"" -D ADMIN_PASSWORD="\"password\""
; -D MESH_PACKET_LOGGING=1 ; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1 ; -D MESH_DEBUG=1
lib_deps =
${Xiao_S3_WIO.lib_deps}
adafruit/RTClib @ ^2.1.3
; ----------------- NRF52 --------------------- ; ----------------- NRF52 ---------------------
[nrf52_base] [nrf52_base]
@@ -213,6 +225,9 @@ build_flags =
-D ADMIN_PASSWORD="\"password\"" -D ADMIN_PASSWORD="\"password\""
; -D MESH_PACKET_LOGGING=1 ; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1 ; -D MESH_DEBUG=1
lib_deps =
${rak4631.lib_deps}
adafruit/RTClib @ ^2.1.3
[env:RAK_4631_chat_alice] [env:RAK_4631_chat_alice]
extends = rak4631 extends = rak4631

View File

@@ -111,7 +111,9 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
break; break;
} }
} }
if (!found) { if (found) {
pkt->markDoNotRetransmit(); // packet was for this node, so don't retransmit
} else {
MESH_DEBUG_PRINTLN("recv matches no peers, src_hash=%02X", (uint32_t)src_hash); MESH_DEBUG_PRINTLN("recv matches no peers, src_hash=%02X", (uint32_t)src_hash);
} }
} }
@@ -139,6 +141,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
int len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i); int len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i);
if (len > 0) { // success! if (len > 0) { // success!
onAnonDataRecv(pkt, pkt->getPayloadType(), sender, data, len); onAnonDataRecv(pkt, pkt->getPayloadType(), sender, data, len);
pkt->markDoNotRetransmit();
} }
} }
action = routeRecvPacket(pkt); action = routeRecvPacket(pkt);
@@ -217,7 +220,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
} }
DispatcherAction Mesh::routeRecvPacket(Packet* packet) { DispatcherAction Mesh::routeRecvPacket(Packet* packet) {
if (packet->isRouteFlood() && packet->path_len + PATH_HASH_SIZE <= MAX_PATH_SIZE && allowPacketForward(packet)) { if (packet->isRouteFlood() && !packet->isMarkedDoNotRetransmit()
&& packet->path_len + PATH_HASH_SIZE <= MAX_PATH_SIZE && allowPacketForward(packet)) {
// append this node's hash to 'path' // append this node's hash to 'path'
packet->path_len += self_id.copyHashTo(&packet->path[packet->path_len]); packet->path_len += self_id.copyHashTo(&packet->path[packet->path_len]);

View File

@@ -68,6 +68,9 @@ public:
* \returns one of PAYLOAD_VER_ values * \returns one of PAYLOAD_VER_ values
*/ */
uint8_t getPayloadVer() const { return (header >> PH_VER_SHIFT) & PH_VER_MASK; } uint8_t getPayloadVer() const { return (header >> PH_VER_SHIFT) & PH_VER_MASK; }
void markDoNotRetransmit() { header = 0xFF; }
bool isMarkedDoNotRetransmit() const { return header == 0xFF; }
}; };
} }