From 0db15db625a3c05ac4f1a27e591af7c3459fc439 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 17 Feb 2025 19:22:31 +1100 Subject: [PATCH] * new Packet type: PAYLOAD_TYPE_TRACE --- examples/companion_radio/main.cpp | 4 ++ examples/simple_secure_chat/main.cpp | 4 ++ src/Dispatcher.cpp | 5 ++- src/Mesh.cpp | 43 +++++++++++++++++-- src/Mesh.h | 10 +++++ src/Packet.cpp | 7 +++- src/Packet.h | 4 ++ src/helpers/BaseChatMesh.cpp | 63 ++++++++++++++++++++++++++++ src/helpers/BaseChatMesh.h | 3 ++ 9 files changed, 135 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index af677537..842abdb3 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -448,6 +448,10 @@ protected: // TODO: check for Get Stats response } + void onContactTraceRecv(const ContactInfo& contact, uint32_t sender_timestamp, const uint8_t hash[], int8_t snr[], uint8_t path_len) override { + // TODO: write an out_frame + } + uint32_t calcFloodTimeoutMillisFor(uint32_t pkt_airtime_millis) const override { return SEND_TIMEOUT_BASE_MILLIS + (FLOOD_SEND_TIMEOUT_FACTOR * pkt_airtime_millis); } diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index 7d589828..0483eaf1 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -268,6 +268,10 @@ protected: // not supported } + void onContactTraceRecv(const ContactInfo& contact, uint32_t sender_timestamp, const uint8_t hash[], int8_t snr[], uint8_t path_len) override { + // TODO: write an out_frame + } + uint32_t calcFloodTimeoutMillisFor(uint32_t pkt_airtime_millis) const override { return SEND_TIMEOUT_BASE_MILLIS + (FLOOD_SEND_TIMEOUT_FACTOR * pkt_airtime_millis); } diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 81822baf..fa1895bd 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -105,7 +105,7 @@ void Dispatcher::checkRecv() { pkt->payload_len = len - i; // payload is remainder memcpy(pkt->payload, &raw[i], pkt->payload_len); - score = _radio->packetScore(_radio->getLastSNR(), len); + score = _radio->packetScore(pkt->_snr = _radio->getLastSNR(), len); air_time = _radio->getEstAirtimeFor(len); } } @@ -117,7 +117,7 @@ void Dispatcher::checkRecv() { #if MESH_PACKET_LOGGING Serial.printf("PACKET: recv, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d\n", 2 + pkt->path_len + pkt->payload_len, pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len, - (int)_radio->getLastSNR(), (int)_radio->getLastRSSI(), (int)(score*1000)); + (int)pkt->getSNR(), (int)_radio->getLastRSSI(), (int)(score*1000)); #endif if (pkt->isRouteFlood()) { @@ -198,6 +198,7 @@ Packet* Dispatcher::obtainNewPacket() { n_full_events++; } else { pkt->payload_len = pkt->path_len = 0; + pkt->_snr = 0; } return pkt; } diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 96092f30..8980e6aa 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -41,6 +41,16 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { // remove our hash from 'path', then re-broadcast pkt->path_len -= PATH_HASH_SIZE; memcpy(pkt->path, &pkt->path[PATH_HASH_SIZE], pkt->path_len); + + if (pkt->getPayloadType() == PAYLOAD_TYPE_TRACE && pkt->payload_len + PATH_HASH_SIZE+1 < MAX_PACKET_PAYLOAD) { + if ((pkt->payload[0] & 3) == 0) { + // append our hash + SNR + pkt->payload_len += self_id.copyHashTo(&pkt->payload[pkt->payload_len]); + pkt->payload[pkt->payload_len++] = (int8_t) (pkt->getSNR()*4); + } else { + // unknown flags:type, don't append any info + } + } return ACTION_RETRANSMIT(0); // Routed traffic is HIGHEST priority (and NO per-hop delay) } return ACTION_RELEASE; // this node is NOT the next hop (OR this packet has already been forwarded), so discard. @@ -61,16 +71,21 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { } break; } + case PAYLOAD_TYPE_TRACE: case PAYLOAD_TYPE_PATH: case PAYLOAD_TYPE_REQ: case PAYLOAD_TYPE_RESPONSE: case PAYLOAD_TYPE_TXT_MSG: { int i = 0; + if (pkt->getPayloadType() == PAYLOAD_TYPE_TRACE) { + //uint8_t flags = pkt->payload[i]; // reserved for now + i++; // skip over + } uint8_t dest_hash = pkt->payload[i++]; uint8_t src_hash = pkt->payload[i++]; uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data - if (i + 2 >= pkt->payload_len) { + if (i + CIPHER_MAC_SIZE >= pkt->payload_len) { MESH_DEBUG_PRINTLN("Mesh::onRecvPacket(): incomplete data packet"); } else if (!_tables->hasSeen(pkt)) { // NOTE: this is a 'first packet wins' impl. When receiving from multiple paths, the first to arrive wins. @@ -88,7 +103,12 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { // decrypt, checking MAC is valid uint8_t data[MAX_PACKET_PAYLOAD]; - int len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i); + int len; + if (pkt->getPayloadType() == PAYLOAD_TYPE_TRACE) { + len = Utils::MACThenDecrypt(secret, data, macAndData, CIPHER_MAC_SIZE+CIPHER_BLOCK_SIZE); // encrypted part is fixed-len + } else { + len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i); + } if (len > 0) { // success! if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH) { int k = 0; @@ -104,6 +124,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { if (rpath) sendDirect(rpath, path, path_len); } } + } else if (pkt->getPayloadType() == PAYLOAD_TYPE_TRACE) { + onPeerTraceRecv(pkt, j, secret, data); } else { onPeerDataRecv(pkt, pkt->getPayloadType(), j, secret, data, len); } @@ -227,6 +249,16 @@ DispatcherAction Mesh::routeRecvPacket(Packet* packet) { // append this node's hash to 'path' packet->path_len += self_id.copyHashTo(&packet->path[packet->path_len]); + if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE && (packet->payload[0] & 3) != 0) { + // flags:type must be zero (otherwise, some future/unknown sub-type) + return ACTION_RELEASE; // don't forward, just discard + } + + if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE && packet->payload_len + 1 < MAX_PACKET_PAYLOAD) { + // append packet SNR + packet->payload[packet->payload_len++] = (int8_t) (packet->getSNR()*4); + } + uint32_t d = getRetransmitDelay(packet); // as this propagates outwards, give it lower and lower priority return ACTION_RETRANSMIT_DELAYED(packet->path_len, d); // give priority to closer sources, than ones further away @@ -316,8 +348,8 @@ Packet* Mesh::createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, } Packet* Mesh::createDatagram(uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len) { - if (type == PAYLOAD_TYPE_TXT_MSG || type == PAYLOAD_TYPE_REQ || type == PAYLOAD_TYPE_RESPONSE) { - if (data_len + 2 + CIPHER_BLOCK_SIZE-1 > MAX_PACKET_PAYLOAD) return NULL; + if (type == PAYLOAD_TYPE_TXT_MSG || type == PAYLOAD_TYPE_REQ || type == PAYLOAD_TYPE_RESPONSE || type == PAYLOAD_TYPE_TRACE) { + if (data_len + CIPHER_MAC_SIZE + CIPHER_BLOCK_SIZE-1 > MAX_PACKET_PAYLOAD) return NULL; } else { return NULL; // invalid type } @@ -330,6 +362,9 @@ Packet* Mesh::createDatagram(uint8_t type, const Identity& dest, const uint8_t* packet->header = (type << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later int len = 0; + if (type == PAYLOAD_TYPE_TRACE) { + packet->payload[len++] = 0; // reserved: flags:type + } len += dest.copyHashTo(&packet->payload[len]); // dest hash len += self_id.copyHashTo(&packet->payload[len]); // src hash len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len); diff --git a/src/Mesh.h b/src/Mesh.h index e5da9406..7e257e3e 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -81,9 +81,19 @@ protected: * \param type one of: PAYLOAD_TYPE_TXT_MSG, PAYLOAD_TYPE_REQ, PAYLOAD_TYPE_RESPONSE * \param sender_idx index of peer, [0..n) where n is what searchPeersByHash() returned * \param secret the pre-calculated shared-secret (handy for sending response packet) + * \param data decrypted data from payload */ virtual void onPeerDataRecv(Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) { } + /** + * \brief A (now verified) TRACE packet has been received (by a known peer). + * NOTE: these can be received multiple times (per sender/msg-id), via different routes + * \param sender_idx index of peer, [0..n) where n is what searchPeersByHash() returned + * \param secret the pre-calculated shared-secret (handy for sending response packet) + * \param data decrypted data from payload (fixed length, one CIPHER_BLOCK) + */ + virtual void onPeerTraceRecv(Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* data) { } + /** * \brief A path TO peer (sender_idx) has been received. (also with optional 'extra' data encoded) * NOTE: these can be received multiple times (per sender), via differen routes diff --git a/src/Packet.cpp b/src/Packet.cpp index a342c37f..82a73339 100644 --- a/src/Packet.cpp +++ b/src/Packet.cpp @@ -10,12 +10,15 @@ Packet::Packet() { payload_len = 0; } - void Packet::calculatePacketHash(uint8_t* hash) const { SHA256 sha; uint8_t t = getPayloadType(); sha.update(&t, 1); - sha.update(payload, payload_len); + if (t == PAYLOAD_TYPE_TRACE) { + sha.update(payload, 3+CIPHER_MAC_SIZE+CIPHER_BLOCK_SIZE); // the 'content' part of TRACE packets is just the fixed-len encrypted part + } else { + sha.update(payload, payload_len); + } sha.finalize(hash, MAX_HASH_SIZE); } diff --git a/src/Packet.h b/src/Packet.h index c8769d8c..c4185875 100644 --- a/src/Packet.h +++ b/src/Packet.h @@ -25,6 +25,7 @@ namespace mesh { #define PAYLOAD_TYPE_GRP_DATA 0x06 // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: timestamp, blob) #define PAYLOAD_TYPE_ANON_REQ 0x07 // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...) #define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra) +#define PAYLOAD_TYPE_TRACE 0x09 // trace a path, collecting SNI for each hop //... #define PAYLOAD_TYPE_RESERVEDM 0x0F // FUTURE @@ -44,6 +45,7 @@ public: uint16_t payload_len, path_len; uint8_t path[MAX_PATH_SIZE]; uint8_t payload[MAX_PACKET_PAYLOAD]; + float _snr; /** * \brief calculate the hash of payload + type @@ -72,6 +74,8 @@ public: void markDoNotRetransmit() { header = 0xFF; } bool isMarkedDoNotRetransmit() const { return header == 0xFF; } + float getSNR() const { return _snr; } + /** * \brief save entire packet as a blob * \param dest (OUT) destination buffer (assumed to be MAX_MTU_SIZE) diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 093e0400..9133e795 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -135,6 +135,50 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender } } +void BaseChatMesh::onPeerTraceRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* data) { + int i = matching_peer_indexes[sender_idx]; + if (i < 0 || i >= num_contacts) { + MESH_DEBUG_PRINTLN("onPeerTraceRecv: Invalid sender idx: %d", i); + return; + } + + if ((packet->payload[0] & 3) != 0) { + MESH_DEBUG_PRINTLN("onPeerTraceRecv: unknown TRACE sub-type: %u", (uint32_t)packet->payload[0]); + return; + } + + ContactInfo& from = contacts[i]; + + uint32_t timestamp; + memcpy(×tamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) + uint8_t sender_flags = data[4]; + + int j = 3 + CIPHER_MAC_SIZE + CIPHER_BLOCK_SIZE; // skip fixed-len part + + uint8_t path_hashes[64]; + int8_t path_snr[65]; + uint16_t len; + if (packet->isRouteFlood()) { + memcpy(path_hashes, packet->path, len = packet->path_len); + memset(path_snr, 0, sizeof(path_snr)); + memcpy(path_snr, &packet->payload[j], packet->payload_len - j); // 'track' should just contain SNRs + } else { + len = 0; + while (j + 1 < packet->payload_len) { + path_hashes[len] = packet->payload[j++]; // pairs of Hash + SNR + path_snr[len] = packet->payload[j++]; + len++; + } + } + path_snr[len] = (int8_t)(packet->getSNR()*4); // also include last hop (to this node) + + onContactTraceRecv(from, timestamp, path_hashes, path_snr, len); + + if (sender_flags & 1) { // the 'wants reply' flag + // TODO: send a TRACE packet back + } +} + bool BaseChatMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { int i = matching_peer_indexes[sender_idx]; if (i < 0 || i >= num_contacts) { @@ -229,6 +273,25 @@ int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp, return rc; } +bool BaseChatMesh::sendContactTraceDirect(const ContactInfo& recipient, bool wantReply) { + if (recipient.out_path_len < 0) return false; // Error: no known path + + uint8_t temp[CIPHER_BLOCK_SIZE]; + memset(temp, 0, sizeof(temp)); + + uint32_t timestamp = getRTCClock()->getCurrentTime(); + memcpy(temp, ×tamp, 4); + temp[4] = wantReply ? 1 : 0; + // TODO: any other data to encrypt?? + + auto pkt = createDatagram(PAYLOAD_TYPE_TRACE, recipient.id, recipient.shared_secret, temp, sizeof(temp)); + if (pkt) { + sendDirect(pkt, recipient.out_path, recipient.out_path_len); + return true; // success + } + return false; // error +} + bool BaseChatMesh::sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& channel, const char* sender_name, const char* text, int text_len) { uint8_t temp[5+MAX_TEXT_LEN+32]; memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 814336b5..f77fbd83 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -87,6 +87,7 @@ protected: virtual void onSendTimeout() = 0; virtual void onChannelMessageRecv(const mesh::GroupChannel& channel, int in_path_len, uint32_t timestamp, const char *text) = 0; virtual void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) = 0; + virtual void onContactTraceRecv(const ContactInfo& contact, uint32_t sender_timestamp, const uint8_t hash[], int8_t snr[], uint8_t path_len) = 0; // storage concepts, for sub-classes to override/implement virtual int getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { return 0; } // not implemented @@ -98,6 +99,7 @@ protected: void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override; void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override; bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; + void onPeerTraceRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* data) override; void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override; #ifdef MAX_GROUP_CHANNELS int searchChannelsByHash(const uint8_t* hash, mesh::GroupChannel channels[], int max_matches) override; @@ -112,6 +114,7 @@ public: bool shareContactZeroHop(const ContactInfo& contact); uint8_t exportContact(const ContactInfo& contact, uint8_t dest_buf[]); bool importContact(const uint8_t src_buf[], uint8_t len); + bool sendContactTraceDirect(const ContactInfo& recipient, bool wantReply); void resetPathTo(ContactInfo& recipient); void scanRecentContacts(int last_n, ContactVisitor* visitor); ContactInfo* searchContactsByPrefix(const char* name_prefix);