diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 65cc1523..171a1d25 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -13,6 +13,7 @@ #include #include #include +#include /* ------------------------------ 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(×tamp, 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(×tamp, 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(×tamp, 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, ×tamp, 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 diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index 15189f47..f7b3617c 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -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: diff --git a/platformio.ini b/platformio.ini index b7821f9c..e76f164f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -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 diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 3d59796f..35af0f54 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -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]); diff --git a/src/Packet.h b/src/Packet.h index 53daf203..99daf0c4 100644 --- a/src/Packet.h +++ b/src/Packet.h @@ -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; } }; }