* 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/SimpleMeshTables.h>
#include <helpers/IdentityStore.h>
#include <RTClib.h>
/* ------------------------------ 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 {
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)
auto client = &known_clients[i];
if (timestamp > client->last_timestamp) { // prevent replay attacks
int reply_len = handleRequest(client, &data[4], len - 4);
if (reply_len == 0) return; // invalid command
uint32_t timestamp;
memcpy(&timestamp, data, 4);
client->last_timestamp = timestamp;
if (timestamp > client->last_timestamp) { // prevent replay attacks
int reply_len = handleRequest(client, &data[4], len - 4);
if (reply_len == 0) return; // invalid command
client->last_timestamp = timestamp;
if (packet->isRouteFlood()) {
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
mesh::Packet* path = createPathReturn(client->id, secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
if (path) sendFlood(path);
} else {
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 {
sendFlood(reply);
}
if (packet->isRouteFlood()) {
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
mesh::Packet* path = createPathReturn(client->id, secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
if (path) sendFlood(path);
} else {
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 {
sendFlood(reply);
}
}
}
}
} else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { // a CLI command
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!");
}
}
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)
@@ -456,13 +524,10 @@ void loop() {
if (len > 0 && command[len - 1] == '\r') { // received complete line
command[len - 1] = 0; // replace newline with C string null terminator
if (strcmp(command, "reboot") == 0) {
board.reboot(); // doesn't return
} else if (strcmp(command, "advert") == 0) {
the_mesh.sendSelfAdvertisement();
} else {
Serial.print(" ERROR: unknown command: "); Serial.println(command);
Serial.println(" (commands: reboot, advert)");
char reply[160];
the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
if (reply[0]) {
Serial.print(" -> "); Serial.println(reply);
}
command[0] = 0; // reset command buffer

View File

@@ -220,20 +220,24 @@ protected:
}
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
Serial.printf(" Got ACK! (round trip: %d millis)\n", _ms->getMillis() - last_msg_sent);
// NOTE: the same ACK can be received multiple times!
expected_ack_crc = 0; // reset our expected hash, now that we have received ACK
txt_send_timeout = 0;
} else {
uint32_t crc;
memcpy(&crc, data, 4);
MESH_DEBUG_PRINTLN(" unknown ACK received: %08X (expected: %08X)", crc, expected_ack_crc);
return true;
}
uint32_t crc;
memcpy(&crc, data, 4);
MESH_DEBUG_PRINTLN(" unknown ACK received: %08X (expected: %08X)", crc, expected_ack_crc);
return false;
}
public:

View File

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

View File

@@ -111,7 +111,9 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
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);
}
}
@@ -139,6 +141,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
int len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i);
if (len > 0) { // success!
onAnonDataRecv(pkt, pkt->getPayloadType(), sender, data, len);
pkt->markDoNotRetransmit();
}
}
action = routeRecvPacket(pkt);
@@ -217,7 +220,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
}
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'
packet->path_len += self_id.copyHashTo(&packet->path[packet->path_len]);

View File

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