Przeglądaj źródła

* Mesh.cpp: optimisation to not retransmit packets handled by this node
* simple_repeater: now supports a remote CLI

Scott Powell 1 rok temu
rodzic
commit
22e87836db

+ 96 - 31
examples/simple_repeater/main.cpp

@@ -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 (timestamp > client->last_timestamp) {  // prevent replay attacks 
+        int reply_len = handleRequest(client, &data[4], len - 4);
+        if (reply_len == 0) return;  // invalid command
 
-      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];
+        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);
+            }
+          }
+        }
+      }
+    } 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
 
-        uint32_t timestamp;
-        memcpy(&timestamp, data, 4);
+      if (flags == 0 && timestamp > client->last_timestamp) {  // prevent replay attacks 
+        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
+        // 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
 
-          client->last_timestamp = timestamp;
+        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);
 
-          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);
+        mesh::Packet* ack = createAck(ack_hash);
+        if (ack) {
+          if (client->out_path_len < 0) {
+            sendFlood(ack);
           } 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);
-              }
+            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

+ 10 - 6
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:

+ 15 - 0
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

+ 6 - 2
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]);
 

+ 3 - 0
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; }
 };
 
 }