|
|
@@ -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(×tamp, 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(×tamp, 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(×tamp, 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, ×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
|