From fe298318be4d9fb4cda821ff06b5a3166fa90071 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 19 Oct 2026 10:41:51 +1100 Subject: [PATCH 1/5] * squashed --- examples/companion_radio/MyMesh.cpp | 35 ++++ examples/companion_radio/MyMesh.h | 4 +- examples/simple_repeater/MyMesh.cpp | 107 ++++++++--- examples/simple_repeater/MyMesh.h | 10 +- examples/simple_room_server/MyMesh.cpp | 107 ++++++++--- examples/simple_room_server/MyMesh.h | 9 +- examples/simple_sensor/SensorMesh.cpp | 101 ++++++++--- examples/simple_sensor/SensorMesh.h | 11 +- src/Dispatcher.cpp | 2 + src/Mesh.cpp | 214 ++++++++++++++++++++-- src/Mesh.h | 26 ++- src/Packet.cpp | 14 ++ src/Packet.h | 13 ++ src/Utils.cpp | 21 +++ src/Utils.h | 14 +- src/helpers/BaseChatMesh.cpp | 236 +++++++++++++++---------- src/helpers/BaseChatMesh.h | 29 +-- src/helpers/ClientACL.h | 3 + src/helpers/ContactInfo.h | 3 + 19 files changed, 755 insertions(+), 204 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 99a9aa30d6e..60a346bff76 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -188,6 +188,12 @@ void MyMesh::writeContactRespFrame(uint8_t code, const ContactInfo &contact) { i += 4; memcpy(&out_frame[i], &contact.lastmod, 4); i += 4; + // new fields + out_frame[i++] = contact.direct_mode; + out_frame[i++] = contact.next_seq_no; + memcpy(&out_frame[i], &contact.curr_session_id, 2); + i += 2; + _serial->writeFrame(out_frame, i); } @@ -212,6 +218,12 @@ void MyMesh::updateContactFromFrame(ContactInfo &contact, uint32_t& last_mod, co i += 4; if (len >= i + 4) { memcpy(&last_mod, &frame[i], 4); + if (len >= i + 4) { + contact.direct_mode = frame[i++]; + contact.next_seq_no = frame[i++]; + memcpy(&contact.curr_session_id, &frame[i], 2); + i += 2; + } } } } @@ -488,6 +500,29 @@ bool MyMesh::allowPacketForward(const mesh::Packet* packet) { return _prefs.isRepeatEn(); } +mesh::Packet* MyMesh::obtainPacketFor(ContactInfo& contact) { + auto pkt = obtainNewPacket(); + if (pkt) { + if (contact.out_path_len == OUT_PATH_UNKNOWN) { + pkt->header |= ROUTE_TYPE_FLOOD; + // NOTE: final route type, transport codes, path_len is set later in sendFloodScoped() + } else { + uint8_t crypto_mode = contact.direct_mode & 0x0F; + if (crypto_mode == CRYPTO_NEW_DIRECT) { + uint8_t dm_mode = contact.direct_mode >> 4; // upper 4 bits is which DM_MODE_* to use + pkt->header |= ROUTE_TYPE_TRANSPORT_DIRECT; + pkt->transport_codes[0] = (((uint16_t)dm_mode) << 14) | contact.next_seq_no++; // implicit, cursor = 0 + pkt->transport_codes[1] = (contact.curr_session_id & ~(3u << 14)); + pkt->transport_codes[1] |= (CRYPTO_NEW_DIRECT << 14); + } else { + pkt->header |= ROUTE_TYPE_DIRECT; + } + pkt->path_len = mesh::Packet::copyPath(pkt->path, contact.out_path, contact.out_path_len); + } + } + return pkt; +} + void MyMesh::sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint32_t delay_millis) { if (scope.isNull()) { sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 944790df922..9bf4dc1409f 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -4,7 +4,7 @@ #include /*------------ Frame Protocol --------------*/ -#define FIRMWARE_VER_CODE 14 +#define FIRMWARE_VER_CODE 15 #ifndef FIRMWARE_BUILD_DATE #define FIRMWARE_BUILD_DATE "15 Oct 2026" @@ -140,6 +140,8 @@ class MyMesh : public BaseChatMesh, public DataStoreHost { bool filterRecvFloodPacket(mesh::Packet* packet) override; bool allowPacketForward(const mesh::Packet* packet) override; + mesh::Packet* obtainPacketFor(ContactInfo& contact) override; + void sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint32_t delay_millis); void sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis=0) override; void sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis=0) override; diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 61b6c149e98..f73594d1795 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -60,6 +60,29 @@ #define LAZY_CONTACTS_WRITE_DELAY 5000 +mesh::Packet* MyMesh::obtainPacketFor(ClientInfo& client, const uint8_t* out_path, uint8_t out_path_len) { + auto pkt = obtainNewPacket(); + if (pkt) { + if (out_path_len == OUT_PATH_UNKNOWN) { + pkt->header |= ROUTE_TYPE_FLOOD; + // NOTE: final route type, transport codes, path_len is set later in sendFloodScoped() + } else { + uint8_t crypto_mode = client.direct_mode & 0x0F; + if (crypto_mode == CRYPTO_NEW_DIRECT) { + uint8_t dm_mode = client.direct_mode >> 4; // upper 4 bits is which DM_MODE_* to use + pkt->header |= ROUTE_TYPE_TRANSPORT_DIRECT; + pkt->transport_codes[0] = (((uint16_t)dm_mode) << 14) | client.next_seq_no++; // implicit, cursor = 0 + pkt->transport_codes[1] = (client.curr_session_id & ~(3u << 14)); + pkt->transport_codes[1] |= (CRYPTO_NEW_DIRECT << 14); + } else { + pkt->header |= ROUTE_TYPE_DIRECT; + } + pkt->path_len = mesh::Packet::copyPath(pkt->path, out_path, out_path_len); + } + } + return pkt; +} + void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float snr) { #if MAX_NEIGHBOURS // check if neighbours enabled // find existing neighbour, else use least recently updated @@ -123,6 +146,11 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr client->permissions |= perms; memcpy(client->shared_secret, secret, PUB_KEY_SIZE); + // start new session + getRNG()->random((uint8_t *) &client->curr_session_id, sizeof(client->curr_session_id)); + client->next_seq_no = 0; + client->direct_mode = CRYPTO_LEGACY; // TODO: can Client send a new request param, indicating new-direct support? + if (perms != PERM_ACL_GUEST) { // keep number of FS writes to a minimum dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); } @@ -140,8 +168,9 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr reply_data[7] = client->permissions; getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness reply_data[12] = FIRMWARE_VER_LEVEL; // New field + reply_data[13] = (1 << CRYPTO_NEW_DIRECT) | (1 << CRYPTO_LEGACY); // New bitfield: which crypto-types supported - return 13; // reply length + return 14; // reply length } uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { @@ -431,6 +460,16 @@ void MyMesh::sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, ui } } +bool MyMesh::isHashNeighbor(const uint8_t* hash, uint8_t hash_size) { +#if MAX_NEIGHBOURS + for (int i = 0; i < MAX_NEIGHBOURS; i++) { + auto n = &neighbours[i]; + if (n->heard_timestamp > 0 && n->id.isHashMatch(hash, hash_size)) return true; + } +#endif + return false; +} + bool MyMesh::allowPacketForward(const mesh::Packet *packet) { if (_prefs.disable_fwd) return false; if (packet->isRouteFlood() @@ -569,7 +608,7 @@ mesh::DispatcherAction MyMesh::onRecvPacket(mesh::Packet* pkt) { } void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender, - uint8_t *data, size_t len) { + uint8_t *data, size_t len, uint8_t crypto_type) { if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin // client (unknown at this stage) uint32_t timestamp; @@ -595,9 +634,18 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m // a DIRECT login can reply via the stored out_path, as onPeerDataRecv() does for REQ ClientInfo* client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); - bool have_out_path = client != NULL && client->out_path_len != OUT_PATH_UNKNOWN; + if (client == NULL) { + MESH_DEBUG_PRINTLN("onAnonDataRecv: client ACL cannot find slot!"); // This shouldn't happen! + return; + } - auto route = mesh::chooseReplyRoute(packet->isRouteFlood(), reply_path_len != 0xFF, have_out_path); + if (crypto_type == CRYPTO_NEW_DIRECT) { + if (client->direct_mode == CRYPTO_LEGACY) { + client->direct_mode = (DM_MODE_SEQ_NORMAL << 4) | CRYPTO_NEW_DIRECT; // have received from Client in new scheme, so they must support it + } + } + + auto route = mesh::chooseReplyRoute(packet->isRouteFlood(), reply_path_len != 0xFF, client->out_path_len != OUT_PATH_UNKNOWN); if (route == mesh::REPLY_ROUTE_PATH_RETURN) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response @@ -607,13 +655,17 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m return; } - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); - if (reply == NULL) return; - + mesh::Packet* reply; if (route == mesh::REPLY_ROUTE_DIRECT_SUPPLIED) { - sendDirect(reply, reply_path, reply_path_len, SERVER_RESPONSE_DELAY); - } else if (route == mesh::REPLY_ROUTE_DIRECT_OUT_PATH) { - sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); + reply = obtainPacketFor(*client, reply_path, reply_path_len); + } else { + reply = obtainPacketFor(*client, client->out_path, client->out_path_len); + } + if (reply == NULL) return; // packet pool exhausted + + encodeDatagram(reply, PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); + if (reply->isRouteDirect()) { + send(reply, SERVER_RESPONSE_DELAY); } else { sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } @@ -661,7 +713,7 @@ void MyMesh::onAdvertRecv(mesh::Packet *packet, const mesh::Identity &id, uint32 } void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, const uint8_t *secret, - uint8_t *data, size_t len) { + uint8_t *data, size_t len, uint8_t crypto_type) { int i = matching_peer_indexes[sender_idx]; if (i < 0 || i >= acl.getNumClients()) { // get from our known_clients table (sender SHOULD already be known in this context) MESH_DEBUG_PRINTLN("onPeerDataRecv: invalid peer idx: %d", i); @@ -669,6 +721,12 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, } ClientInfo* client = acl.getClientByIdx(i); + if (crypto_type == CRYPTO_NEW_DIRECT) { + if (client->direct_mode == CRYPTO_LEGACY) { + client->direct_mode = (DM_MODE_SEQ_NORMAL << 4) | CRYPTO_NEW_DIRECT; // have received from Client in new scheme, so they must support it + } + } + if (type == PAYLOAD_TYPE_REQ) { // request (from a Known admin client!) uint32_t timestamp; memcpy(×tamp, data, 4); @@ -686,11 +744,11 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); if (path) sendFloodReply(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { - mesh::Packet *reply = - createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); + auto reply = obtainPacketFor(*client, client->out_path, client->out_path_len); if (reply) { - if (client->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT - sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); + encodeDatagram(reply, PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); + if (reply->isRouteDirect()) { + send(reply, SERVER_RESPONSE_DELAY); } else { sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } @@ -714,7 +772,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, // 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 - if (flags == TXT_TYPE_PLAIN) { // for legacy CLI, send Acks + if (flags == TXT_TYPE_PLAIN) { // for legacy CLI, send Acks <==== TODO, DEPRECATE? 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, @@ -748,12 +806,13 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique temp[4] = (TXT_TYPE_CLI_DATA << 2); // NOTE: legacy was: TXT_TYPE_PLAIN - auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len); + auto reply = obtainPacketFor(*client, client->out_path, client->out_path_len); if (reply) { - if (client->out_path_len == OUT_PATH_UNKNOWN) { - sendFloodReply(reply, CLI_REPLY_DELAY_MILLIS, packet->getPathHashSize()); + encodeDatagram(reply, PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len); + if (reply->isRouteDirect()) { + send(reply, CLI_REPLY_DELAY_MILLIS); } else { - sendDirect(reply, client->out_path, client->out_path_len, CLI_REPLY_DELAY_MILLIS); + sendFloodReply(reply, CLI_REPLY_DELAY_MILLIS, packet->getPathHashSize()); } } } @@ -764,7 +823,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, } bool MyMesh::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) { + uint8_t path_len, uint8_t extra_type, uint8_t *extra, uint8_t extra_len, uint8_t crypto_type) { // TODO: prevent replay attacks int i = matching_peer_indexes[sender_idx]; @@ -772,6 +831,12 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t MESH_DEBUG_PRINTLN("PATH to client, path_len=%d", (uint32_t)path_len); auto client = acl.getClientByIdx(i); + if (crypto_type == CRYPTO_NEW_DIRECT) { + if (client->direct_mode == CRYPTO_LEGACY) { + client->direct_mode = (DM_MODE_SEQ_NORMAL << 4) | CRYPTO_NEW_DIRECT; // have received from Client in new scheme, so they must support it + } + } + // store a copy of path, for sendDirect() client->out_path_len = mesh::Packet::copyPath(client->out_path, path, path_len); client->last_activity = getRTCClock()->getCurrentTime(); diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index dc89325861c..0b0034e6740 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -132,6 +132,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { return _prefs.airtime_factor; } + // sets up an outgoing Packet with appropriate route_type, transport codes and path + mesh::Packet* obtainPacketFor(ClientInfo& client, const uint8_t* out_path, uint8_t out_path_len); + bool allowPacketForward(const mesh::Packet* packet) override; const char* getLogDateTime() override; void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override; @@ -156,6 +159,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { uint8_t getExtraAckTransmitCount() const override { return _prefs.multi_acks; } + bool isHashNeighbor(const uint8_t* hash, uint8_t hash_size) override; #if ENV_INCLUDE_GPS == 1 void applyGpsPrefs() { @@ -165,12 +169,12 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { mesh::DispatcherAction onRecvPacket(mesh::Packet* pkt) override; - void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override; + void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len, uint8_t crypto_type) override; int searchPeersByHash(const uint8_t* hash) override; void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override; void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len); - 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 onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len, uint8_t crypto_type) 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, uint8_t crypto_type) override; void onControlDataRecv(mesh::Packet* packet) override; void sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index da4c776c0ea..0b01d4aa1c1 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -38,6 +38,29 @@ struct ServerStats { uint16_t n_posted, n_post_push; }; +mesh::Packet* MyMesh::obtainPacketFor(ClientInfo& client, const uint8_t* out_path, uint8_t out_path_len) { + auto pkt = obtainNewPacket(); + if (pkt) { + if (out_path_len == OUT_PATH_UNKNOWN) { + pkt->header |= ROUTE_TYPE_FLOOD; + // NOTE: final route type, transport codes, path_len is set later in sendFloodScoped() + } else { + uint8_t crypto_mode = client.direct_mode & 0x0F; + if (crypto_mode == CRYPTO_NEW_DIRECT) { + uint8_t dm_mode = client.direct_mode >> 4; // upper 4 bits is which DM_MODE_* to use + pkt->header |= ROUTE_TYPE_TRANSPORT_DIRECT; + pkt->transport_codes[0] = (((uint16_t)dm_mode) << 14) | client.next_seq_no++; // implicit, cursor = 0 + pkt->transport_codes[1] = (client.curr_session_id & ~(3u << 14)); + pkt->transport_codes[1] |= (CRYPTO_NEW_DIRECT << 14); + } else { + pkt->header |= ROUTE_TYPE_DIRECT; + } + pkt->path_len = mesh::Packet::copyPath(pkt->path, out_path, out_path_len); + } + } + return pkt; +} + void MyMesh::addPost(ClientInfo *client, const char *postData) { storePost(client->id, postData); } @@ -88,17 +111,17 @@ void MyMesh::pushPostToClient(ClientInfo *client, PostInfo &post) { mesh::Utils::sha256((uint8_t *)&client->extra.room.pending_ack, 4, reply_data, len, client->id.pub_key, PUB_KEY_SIZE); client->extra.room.push_post_timestamp = post.post_timestamp; - auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, client->shared_secret, reply_data, len); + auto reply = obtainPacketFor(*client, client->out_path, client->out_path_len); if (reply) { - if (client->out_path_len == OUT_PATH_UNKNOWN) { + encodeDatagram(reply, PAYLOAD_TYPE_TXT_MSG, client->id, client->shared_secret, reply_data, len); + if (reply->isRouteDirect()) { + send(reply); + uint8_t path_hash_count = client->out_path_len & 63; + client->extra.room.ack_timeout = futureMillis(PUSH_TIMEOUT_BASE + PUSH_ACK_TIMEOUT_FACTOR * (path_hash_count + 1)); + } else { unsigned long delay_millis = 0; sendFloodScoped(default_scope, reply, delay_millis, _prefs.path_hash_mode + 1); // REVISIT client->extra.room.ack_timeout = futureMillis(PUSH_ACK_TIMEOUT_FLOOD); - } else { - sendDirect(reply, client->out_path, client->out_path_len); - - uint8_t path_hash_count = client->out_path_len & 63; - client->extra.room.ack_timeout = futureMillis(PUSH_TIMEOUT_BASE + PUSH_ACK_TIMEOUT_FACTOR * (path_hash_count + 1)); } _num_post_pushes++; // stats } else { @@ -322,7 +345,7 @@ mesh::DispatcherAction MyMesh::onRecvPacket(mesh::Packet* pkt) { } void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender, - uint8_t *data, size_t len) { + uint8_t *data, size_t len, uint8_t crypto_type) { if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin // client (unknown at this stage) uint32_t sender_timestamp, sender_sync_since; @@ -372,6 +395,11 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m client->permissions |= perm; memcpy(client->shared_secret, secret, PUB_KEY_SIZE); + // start new session + getRNG()->random((uint8_t *) &client->curr_session_id, sizeof(client->curr_session_id)); + client->next_seq_no = 0; + client->direct_mode = CRYPTO_LEGACY; // TODO: can Client send a new request param, indicating new-direct support? + dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); } @@ -379,6 +407,12 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m client->out_path_len = OUT_PATH_UNKNOWN; // need to rediscover out_path } + if (crypto_type == CRYPTO_NEW_DIRECT) { + if (client->direct_mode == CRYPTO_LEGACY) { + client->direct_mode = (DM_MODE_SEQ_NORMAL << 4) | CRYPTO_NEW_DIRECT; // have received from Client in new scheme, so they must support it + } + } + uint32_t now = getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp // TODO: maybe reply with count of messages waiting to be synced for THIS client? @@ -389,19 +423,21 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m reply_data[7] = client->permissions; // NEW getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness reply_data[12] = FIRMWARE_VER_LEVEL; // New field + reply_data[13] = (1 << CRYPTO_NEW_DIRECT) | (1 << CRYPTO_LEGACY); // New bitfield: which crypto-types supported next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS); // delay next push, give RESPONSE packet time to arrive first if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet *path = createPathReturn(sender, client->shared_secret, packet->path, packet->path_len, - PAYLOAD_TYPE_RESPONSE, reply_data, 13); + PAYLOAD_TYPE_RESPONSE, reply_data, 14); if (path) sendFloodReply(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { - mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->shared_secret, reply_data, 13); + auto reply = obtainPacketFor(*client, client->out_path, client->out_path_len); if (reply) { - if (client->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT - sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); + encodeDatagram(reply, PAYLOAD_TYPE_RESPONSE, sender, client->shared_secret, reply_data, 14); + if (reply->isRouteDirect()) { + send(reply, SERVER_RESPONSE_DELAY); } else { sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } @@ -431,13 +467,19 @@ void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) { } void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, const uint8_t *secret, - uint8_t *data, size_t len) { + uint8_t *data, size_t len, uint8_t crypto_type) { int i = matching_peer_indexes[sender_idx]; if (i < 0 || i >= acl.getNumClients()) { // 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 = acl.getClientByIdx(i); + if (crypto_type == CRYPTO_NEW_DIRECT) { + if (client->direct_mode == CRYPTO_LEGACY) { + client->direct_mode = (DM_MODE_SEQ_NORMAL << 4) | CRYPTO_NEW_DIRECT; // have received from Client in new scheme, so they must support it + } + } + if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { // a CLI command or new Post uint32_t sender_timestamp; memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) @@ -498,13 +540,19 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, } else { uint32_t d = TXT_ACK_DELAY; if (getExtraAckTransmitCount() > 0) { - mesh::Packet *a1 = createMultiAck(ack_hash, 1); - if (a1) sendDirect(a1, client->out_path, client->out_path_len, d); + mesh::Packet *a1 = obtainPacketFor(*client, client->out_path, client->out_path_len); + if (a1) { + encodeMultiAck(a1, (uint8_t *) &ack_hash, 4, 1); + send(a1, d); + } d += 300; } - mesh::Packet *a2 = createAck(ack_hash); - if (a2) sendDirect(a2, client->out_path, client->out_path_len, d); + mesh::Packet *a2 = obtainPacketFor(*client, client->out_path, client->out_path_len); + if (a2) { + encodeAck(a2, (uint8_t *)&ack_hash, 4); + send(a2, d); + } delay_millis = d + REPLY_DELAY_MILLIS; } } else { @@ -523,12 +571,13 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, // 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); + auto reply = obtainPacketFor(*client, client->out_path, client->out_path_len); if (reply) { - if (client->out_path_len == OUT_PATH_UNKNOWN) { - sendFloodReply(reply, delay_millis + SERVER_RESPONSE_DELAY, packet->getPathHashSize()); + encodeDatagram(reply, PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len); + if (reply->isRouteDirect()) { + send(reply, delay_millis + SERVER_RESPONSE_DELAY); } else { - sendDirect(reply, client->out_path, client->out_path_len, delay_millis + SERVER_RESPONSE_DELAY); + sendFloodReply(reply, delay_millis + SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } } } @@ -583,10 +632,11 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); if (path) sendFloodReply(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { - mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); + auto reply = obtainPacketFor(*client, client->out_path, client->out_path_len); if (reply) { - if (client->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT - sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); + encodeDatagram(reply, PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); + if (reply->isRouteDirect()) { + send(reply, SERVER_RESPONSE_DELAY); } else { sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } @@ -599,13 +649,20 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, } bool MyMesh::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) { + uint8_t path_len, uint8_t extra_type, uint8_t *extra, uint8_t extra_len, uint8_t crypto_type) { // TODO: prevent replay attacks int i = matching_peer_indexes[sender_idx]; if (i >= 0 && i < acl.getNumClients()) { // get from our known_clients table (sender SHOULD already be known in this context) MESH_DEBUG_PRINTLN("PATH to client, path_len=%d", (uint32_t)path_len); auto client = acl.getClientByIdx(i); + + if (crypto_type == CRYPTO_NEW_DIRECT) { + if (client->direct_mode == CRYPTO_LEGACY) { + client->direct_mode = (DM_MODE_SEQ_NORMAL << 4) | CRYPTO_NEW_DIRECT; // have received from Client in new scheme, so they must support it + } + } + client->out_path_len = mesh::Packet::copyPath(client->out_path, path, path_len); // store a copy of path, for sendDirect() client->last_activity = getRTCClock()->getCurrentTime(); } else { diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 4dee8bdbf40..a0f7da5ce32 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -158,12 +158,15 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { mesh::DispatcherAction onRecvPacket(mesh::Packet* pkt) override; + // sets up an outgoing Packet with appropriate route_type, transport codes and path + mesh::Packet* obtainPacketFor(ClientInfo& client, const uint8_t* out_path, uint8_t out_path_len); + bool allowPacketForward(const mesh::Packet* packet) override; - void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override; + void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len, uint8_t crypto_type) override; int searchPeersByHash(const uint8_t* hash) override ; 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 onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len, uint8_t crypto_type) 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, uint8_t crypto_type) override; void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override; #if ENV_INCLUDE_GPS == 1 diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index b186066810d..5c7da5be717 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -101,6 +101,29 @@ static float findTelemValue(const uint8_t* buf, uint8_t size, uint8_t channel, u /* ------------------ end Cayenne LPP helpers ----------------------*/ +mesh::Packet* SensorMesh::obtainPacketFor(ClientInfo& client, const uint8_t* out_path, uint8_t out_path_len) { + auto pkt = obtainNewPacket(); + if (pkt) { + if (out_path_len == OUT_PATH_UNKNOWN) { + pkt->header |= ROUTE_TYPE_FLOOD; + // NOTE: final route type, transport codes, path_len is set later in sendFloodScoped() + } else { + uint8_t crypto_mode = client.direct_mode & 0x0F; + if (crypto_mode == CRYPTO_NEW_DIRECT) { + uint8_t dm_mode = client.direct_mode >> 4; // upper 4 bits is which DM_MODE_* to use + pkt->header |= ROUTE_TYPE_TRANSPORT_DIRECT; + pkt->transport_codes[0] = (((uint16_t)dm_mode) << 14) | client.next_seq_no++; // implicit, cursor = 0 + pkt->transport_codes[1] = (client.curr_session_id & ~(3u << 14)); + pkt->transport_codes[1] |= (CRYPTO_NEW_DIRECT << 14); + } else { + pkt->header |= ROUTE_TYPE_DIRECT; + } + pkt->path_len = mesh::Packet::copyPath(pkt->path, out_path, out_path_len); + } + } + return pkt; +} + bool SensorMesh::telemHasChanged(ClientInfo* c) { auto buf = telemetry.getBuffer(); uint8_t size = telemetry.getSize(); @@ -259,7 +282,7 @@ mesh::Packet* SensorMesh::createSelfAdvert() { return createAdvert(self_id, app_data, app_data_len); } -void SensorMesh::sendAlert(const ClientInfo* c, Trigger* t) { +void SensorMesh::sendAlert(ClientInfo* c, Trigger* t) { int text_len = strlen(t->text); uint8_t data[MAX_PACKET_PAYLOAD]; @@ -271,10 +294,11 @@ void SensorMesh::sendAlert(const ClientInfo* c, Trigger* t) { mesh::Utils::sha256((uint8_t *)&t->expected_acks[t->attempt], 4, data, 5 + text_len, self_id.pub_key, PUB_KEY_SIZE); t->attempt++; - auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, c->id, c->shared_secret, data, 5 + text_len); + auto pkt = obtainPacketFor(*c, c->out_path, c->out_path_len); if (pkt) { - if (c->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT - sendDirect(pkt, c->out_path, c->out_path_len); + encodeDatagram(pkt, PAYLOAD_TYPE_TXT_MSG, c->id, c->shared_secret, data, 5 + text_len); + if (pkt->isRouteDirect()) { + send(pkt); } else { unsigned long delay_millis = 0; sendFloodScoped(default_scope, pkt, delay_millis, _prefs.path_hash_mode + 1); @@ -409,8 +433,9 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* reply_data[7] = client->permissions; getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness reply_data[12] = FIRMWARE_VER_LEVEL; + reply_data[13] = (1 << CRYPTO_NEW_DIRECT) | (1 << CRYPTO_LEGACY); // New bitfield: which crypto-types supported - return 13; // reply length + return 14; // reply length } void SensorMesh::handleCommand(ClientInfo* from, uint32_t sender_timestamp, char* command, char* reply) { @@ -535,7 +560,7 @@ mesh::DispatcherAction SensorMesh::onRecvPacket(mesh::Packet* pkt) { return Mesh::onRecvPacket(pkt); } -void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) { +void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len, uint8_t crypto_type) { if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage) uint32_t timestamp; memcpy(×tamp, data, 4); @@ -558,8 +583,24 @@ void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, con PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); if (path) sendFloodReply(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); - if (reply) sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); + auto client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); + if (client == NULL) { + MESH_DEBUG_PRINTLN("onAnonDataRecv: client ACL cannot find slot!"); // This shouldn't happen! + return; + } + if (crypto_type == CRYPTO_NEW_DIRECT) { + client->direct_mode = (DM_MODE_SEQ_NORMAL << 4) | CRYPTO_NEW_DIRECT; // have received from Client in new scheme, so they must support it + } + + auto reply = obtainPacketFor(*client, client->out_path, client->out_path_len); + if (reply) { + encodeDatagram(reply, PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); + if (reply->isRouteDirect()) { + send(reply, SERVER_RESPONSE_DELAY); + } else { + sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); + } + } } } } @@ -584,24 +625,30 @@ void SensorMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { } } -void SensorMesh::sendAckTo(const ClientInfo& dest, uint32_t ack_hash, uint8_t path_hash_size) { +void SensorMesh::sendAckTo(ClientInfo& dest, uint32_t ack_hash, uint8_t path_hash_size) { if (dest.out_path_len == OUT_PATH_UNKNOWN) { mesh::Packet* ack = createAck(ack_hash); if (ack) sendFloodScoped(default_scope, ack, TXT_ACK_DELAY, path_hash_size); } else { uint32_t d = TXT_ACK_DELAY; if (getExtraAckTransmitCount() > 0) { - mesh::Packet* a1 = createMultiAck(ack_hash, 1); - if (a1) sendDirect(a1, dest.out_path, dest.out_path_len, d); + mesh::Packet* a1 = obtainPacketFor(dest, dest.out_path, dest.out_path_len); + if (a1) { + encodeMultiAck(a1, (uint8_t *) &ack_hash, 4, 1); + send(a1, d); + } d += 300; } - mesh::Packet* a2 = createAck(ack_hash); - if (a2) sendDirect(a2, dest.out_path, dest.out_path_len, d); + mesh::Packet* a2 = obtainPacketFor(dest, dest.out_path, dest.out_path_len); + if (a2) { + encodeAck(a2, (uint8_t *) &ack_hash, 4); + send(a2, d); + } } } -void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) { +void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len, uint8_t crypto_type) { int i = matching_peer_indexes[sender_idx]; if (i < 0 || i >= acl.getNumClients()) { MESH_DEBUG_PRINTLN("onPeerDataRecv: Invalid sender idx: %d", i); @@ -609,6 +656,9 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i } ClientInfo* from = acl.getClientByIdx(i); + if (crypto_type == CRYPTO_NEW_DIRECT) { + from->direct_mode = (DM_MODE_SEQ_NORMAL << 4) | CRYPTO_NEW_DIRECT; // have received from Client in new scheme, so they must support it + } if (type == PAYLOAD_TYPE_REQ) { // request (from a known contact) uint32_t timestamp; @@ -627,10 +677,11 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); if (path) sendFloodReply(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from->id, secret, reply_data, reply_len); + auto reply = obtainPacketFor(*from, from->out_path, from->out_path_len); if (reply) { - if (from->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT - sendDirect(reply, from->out_path, from->out_path_len, SERVER_RESPONSE_DELAY); + encodeDatagram(reply, PAYLOAD_TYPE_RESPONSE, from->id, secret, reply_data, reply_len); + if (reply->isRouteDirect()) { + send(reply, SERVER_RESPONSE_DELAY); } else { sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } @@ -682,12 +733,13 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique temp[4] = (TXT_TYPE_CLI_DATA << 2); - auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, from->id, secret, temp, 5 + text_len); + auto reply = obtainPacketFor(*from, from->out_path, from->out_path_len); if (reply) { - if (from->out_path_len == OUT_PATH_UNKNOWN) { - sendFloodReply(reply, CLI_REPLY_DELAY_MILLIS, packet->getPathHashSize()); + encodeDatagram(reply, PAYLOAD_TYPE_TXT_MSG, from->id, secret, temp, 5 + text_len); + if (reply->isRouteDirect()) { + send(reply, CLI_REPLY_DELAY_MILLIS); } else { - sendDirect(reply, from->out_path, from->out_path_len, CLI_REPLY_DELAY_MILLIS); + sendFloodReply(reply, CLI_REPLY_DELAY_MILLIS, packet->getPathHashSize()); } } } @@ -1055,10 +1107,11 @@ void SensorMesh::loop() { memcpy(&reply_data[4], ×tamp, 4); memcpy(&reply_data[8], telemetry.getBuffer(), tlen); - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, c->id, c->shared_secret, reply_data, 8 + tlen); + auto reply = obtainPacketFor(*c, c->out_path, c->out_path_len); if (reply) { - if (c->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT - sendDirect(reply, c->out_path, c->out_path_len, 0); + encodeDatagram(reply, PAYLOAD_TYPE_RESPONSE, c->id, c->shared_secret, reply_data, 8 + tlen); + if (reply->isRouteDirect()) { + send(reply); } else { sendFloodScoped(scope, reply, 0, _prefs.path_hash_mode + 1); } diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 590ca0c7657..92ddcfbda7e 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -119,6 +119,9 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { virtual int querySeriesData(uint32_t start_secs_ago, uint32_t end_secs_ago, MinMaxAvg dest[], int max_num) = 0; // for app to implement virtual bool handleCustomCommand(uint32_t sender_timestamp, char* command, char* reply) { return false; } + // sets up an outgoing Packet with appropriate route_type, transport codes and path + mesh::Packet* obtainPacketFor(ClientInfo& client, const uint8_t* out_path, uint8_t out_path_len); + // Mesh overrides float getAirtimeBudgetFactor() const override; bool allowPacketForward(const mesh::Packet* packet) override; @@ -129,16 +132,16 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { bool getCADEnabled() const override; int getAGCResetInterval() const override; mesh::DispatcherAction onRecvPacket(mesh::Packet* pkt) override; - void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override; + void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len, uint8_t crypto_type) override; int searchPeersByHash(const uint8_t* hash) override; 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; + void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len, uint8_t crypto_type) 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 onControlDataRecv(mesh::Packet* packet) override; void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override; virtual bool handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint8_t flags, size_t len); - void sendAckTo(const ClientInfo& dest, uint32_t ack_hash, uint8_t path_hash_size=1); + void sendAckTo(ClientInfo& dest, uint32_t ack_hash, uint8_t path_hash_size=1); private: FILESYSTEM* _fs; unsigned long next_local_advert, next_flood_advert; @@ -169,7 +172,7 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { uint8_t handleRequest(ClientInfo* from, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); - void sendAlert(const ClientInfo* c, Trigger* t); + void sendAlert(ClientInfo* c, Trigger* t); #if ENV_INCLUDE_GPS == 1 void applyGpsPrefs() { diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index c0610b7f8a7..4eb9d3b14e0 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -358,8 +358,10 @@ Packet* Dispatcher::obtainNewPacket() { if (pkt == NULL) { _err_flags |= ERR_EVENT_FULL; } else { + pkt->header = 0; pkt->payload_len = pkt->path_len = 0; pkt->_snr = 0; + pkt->transport_codes[0] = pkt->transport_codes[1] = 0; } return pkt; } diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 9dc42f0b04b..bbf467ebaa2 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -75,7 +75,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { return ACTION_RELEASE; } - if (pkt->isRouteDirect() && pkt->getPathHashCount() > 0) { + if (pkt->isRouteDirect() && !pkt->isPathAtEnd()) { // check for 'early received' ACK if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) { int i = 0; @@ -86,8 +86,46 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { } } - if (self_id.isHashMatch(pkt->path, pkt->getPathHashSize()) && allowPacketForward(pkt)) { - uint32_t d = getDirectRetransmitDelay(pkt); + uint8_t hash_size = pkt->getPathHashSize(); + bool is_match; + uint32_t d = getDirectRetransmitDelay(pkt); + if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT) { + uint8_t mode = pkt->transport_codes[0] >> 14; + uint8_t cursor = (pkt->transport_codes[0] >> 8) & 63; // lower 6 bits only + if (mode == DM_MODE_SEQ_NORMAL) { + const uint8_t* curr_hash = &pkt->path[cursor * hash_size]; + is_match = self_id.isHashMatch(curr_hash, hash_size); + } else if (mode == DM_MODE_SEQ_WIDE || mode == DM_MODE_SEQ_WIDE_MODIFIED) { + uint8_t* curr_hash = &pkt->path[cursor * hash_size]; + if (self_id.isHashMatch(curr_hash, hash_size)) { + is_match = true; + } else { + if (isHashNeighbor(curr_hash, hash_size)) { + is_match = true; + // RE-WRITE curr hash with self_id, so dest can tell which path actually was taken + self_id.copyHashTo(curr_hash, hash_size); + // RE-WRITE the mode bits, to indicate path was modified + pkt->transport_codes[0] &= ~(3 << 14); // clear the hi 2 bits + pkt->transport_codes[0] |= ((uint16_t)DM_MODE_SEQ_WIDE_MODIFIED) << 14; + } else { + is_match = false; + } + } + d = getRetransmitDelay(pkt); // For WIDE mode, use flood retransmit delay factor + } else { // DM_MODE_SEQ_LINK_ID + #if 0 + // TODO: allowPacketForward() should check if Link-Id is allowed + is_match = true; // Note: cursor Not applied + #else + is_match = false; // Not implemented + #endif + } + } else { + // original DIRECT mode (path array gets consumed, current hash is always at start of path array) + is_match = self_id.isHashMatch(pkt->path, hash_size); + } + + if (is_match && allowPacketForward(pkt)) { if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) { return forwardMultipartDirect(pkt, d); } else if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) { @@ -155,7 +193,21 @@ 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; + uint8_t crypto_type; + if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT && (pkt->transport_codes[1] >> 14) == CRYPTO_NEW_DIRECT) { + uint8_t iv[3]; + memcpy(iv, &pkt->transport_codes[1], 2); // session-id + iv[2] = (pkt->transport_codes[0] & 0xFF); // seq-no + len = Utils::MACThenDecrypt(secret, iv, 3, data, macAndData, pkt->payload_len - i); + crypto_type = CRYPTO_NEW_DIRECT; + } else if (!pkt->hasTransportCodes() || (pkt->transport_codes[1] >> 14) == CRYPTO_LEGACY) { + len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i); + crypto_type = CRYPTO_LEGACY; + } else { + MESH_DEBUG_PRINTLN("%s unknown Crypto Type indicator: %u", getLogDateTime(), (uint32_t)(pkt->transport_codes[1] >> 14)); + len = 0; // failed to decode + } if (len > 0) { // success! if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH) { int k = 0; @@ -170,7 +222,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { uint8_t extra_type = data[k++] & 0x0F; // upper 4 bits reserved for future use uint8_t* extra = &data[k]; uint8_t extra_len = len - k; // remainder of packet (may be padded with zeroes!) - if (onPeerPathRecv(pkt, j, secret, path, path_len, extra_type, extra, extra_len)) { + if (onPeerPathRecv(pkt, j, secret, path, path_len, extra_type, extra, extra_len, crypto_type)) { if (pkt->isRouteFlood()) { // send a reciprocal return path to sender, but send DIRECTLY! mesh::Packet* rpath = createPathReturn(&src_hash, secret, pkt->path, pkt->path_len, 0, NULL, 0); @@ -178,7 +230,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { } } } else { - onPeerDataRecv(pkt, pkt->getPayloadType(), j, secret, data, len); + onPeerDataRecv(pkt, pkt->getPayloadType(), j, secret, data, len, crypto_type); } found = true; break; @@ -212,9 +264,24 @@ 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); + uint8_t crypto_type; + int len; + if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT && (pkt->transport_codes[1] >> 14) == CRYPTO_NEW_DIRECT) { + uint8_t iv[3]; + memcpy(iv, &pkt->transport_codes[1], 2); // session-id / crypto-type + iv[2] = (pkt->transport_codes[0] & 0xFF); // seq-no + len = Utils::MACThenDecrypt(secret, iv, 3, data, macAndData, pkt->payload_len - i); + crypto_type = CRYPTO_NEW_DIRECT; + } else if (!pkt->hasTransportCodes() || (pkt->transport_codes[1] >> 14) == CRYPTO_LEGACY) { + len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i); + crypto_type = CRYPTO_LEGACY; + } else { + MESH_DEBUG_PRINTLN("%s unknown Crypto Type indicator: %u", getLogDateTime(), (uint32_t)(pkt->transport_codes[1] >> 14)); + len = 0; // failed to decode + } + if (len > 0) { // success! - onAnonDataRecv(pkt, secret, sender, data, len); + onAnonDataRecv(pkt, secret, sender, data, len, crypto_type); pkt->markDoNotRetransmit(); } } @@ -332,12 +399,20 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { } void Mesh::removeSelfFromPath(Packet* pkt) { - // remove our hash from 'path' - pkt->setPathHashCount(pkt->getPathHashCount() - 1); // decrement the count + if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT) { + // advance the cursor + uint8_t cursor = (pkt->transport_codes[0] >> 8) & 63; // lower 6 bits only + cursor++; + pkt->transport_codes[0] &= ~(((uint16_t)63) << 8); // clear the bits + pkt->transport_codes[0] |= (((uint16_t)cursor) << 8); // set new cursor (lower 6 bits) + } else { + // remove our hash from 'path' + pkt->setPathHashCount(pkt->getPathHashCount() - 1); // decrement the count - uint8_t sz = pkt->getPathHashSize(); - for (int k = 0; k < pkt->getPathHashCount()*sz; k += sz) { // shuffle path by 1 'entry' - memcpy(&pkt->path[k], &pkt->path[k + sz], sz); + uint8_t sz = pkt->getPathHashSize(); + for (int k = 0; k < pkt->getPathHashCount()*sz; k += sz) { // shuffle path by 1 'entry' + memcpy(&pkt->path[k], &pkt->path[k + sz], sz); + } } } @@ -363,6 +438,8 @@ DispatcherAction Mesh::forwardMultipartDirect(Packet* pkt, uint32_t delay_millis if (type == PAYLOAD_TYPE_ACK && pkt->payload_len >= 5) { // a multipart ACK Packet tmp; tmp.header = pkt->header; + tmp.transport_codes[0] = pkt->transport_codes[0]; + tmp.transport_codes[1] = pkt->transport_codes[1]; tmp.path_len = Packet::copyPath(tmp.path, pkt->path, pkt->path_len); tmp.payload_len = pkt->payload_len - 1; memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len); @@ -384,7 +461,9 @@ void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) { if (a1) { a1->path_len = Packet::copyPath(a1->path, packet->path, packet->path_len); a1->header &= ~PH_ROUTE_MASK; - a1->header |= ROUTE_TYPE_DIRECT; + a1->header |= packet->hasTransportCodes() ? ROUTE_TYPE_TRANSPORT_DIRECT : ROUTE_TYPE_DIRECT; + a1->transport_codes[0] = packet->transport_codes[0]; + a1->transport_codes[1] = packet->transport_codes[1]; sendPacket(a1, 0, delay_millis); delay_millis += 300; } @@ -395,7 +474,9 @@ void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) { if (a2) { a2->path_len = Packet::copyPath(a2->path, packet->path, packet->path_len); a2->header &= ~PH_ROUTE_MASK; - a2->header |= ROUTE_TYPE_DIRECT; + a2->header |= packet->hasTransportCodes() ? ROUTE_TYPE_TRANSPORT_DIRECT : ROUTE_TYPE_DIRECT; + a2->transport_codes[0] = packet->transport_codes[0]; + a2->transport_codes[1] = packet->transport_codes[1]; sendPacket(a2, 0, delay_millis); } } @@ -485,6 +566,35 @@ Packet* Mesh::createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, return packet; } +void Mesh::encodeDatagram(Packet* packet, 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)) { + MESH_DEBUG_PRINTLN("%s Mesh::encodeDatagram(): error, invalid type: %u", getLogDateTime(), (uint32_t)type); + return; + } + if (data_len + CIPHER_MAC_SIZE + CIPHER_BLOCK_SIZE-1 > MAX_PACKET_PAYLOAD) { + MESH_DEBUG_PRINTLN("%s Mesh::encodeDatagram(): error, data_len too long: %u", getLogDateTime(), data_len); + return; + } + packet->header |= (type << PH_TYPE_SHIFT); // ROUTE_TYPE_* should already be set! + + int len = 0; + len += dest.copyHashTo(&packet->payload[len]); // dest hash + len += self_id.copyHashTo(&packet->payload[len]); // src hash + + if (packet->getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT && (packet->transport_codes[1] >> 14) == CRYPTO_NEW_DIRECT) { + // new encryption + uint8_t iv[3]; + memcpy(iv, &packet->transport_codes[1], 2); // session-id + iv[2] = packet->transport_codes[0] & 0xFF; // seq-no + len += Utils::encryptThenMAC(secret, iv, 3, &packet->payload[len], data, data_len); + } else { + // legacy encryption + len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len); + } + + packet->payload_len = len; +} + 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 + CIPHER_MAC_SIZE + CIPHER_BLOCK_SIZE-1 > MAX_PACKET_PAYLOAD) return NULL; @@ -509,6 +619,39 @@ Packet* Mesh::createDatagram(uint8_t type, const Identity& dest, const uint8_t* return packet; } +void Mesh::encodeAnonDatagram(Packet* packet, uint8_t type, const LocalIdentity& sender, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len) { + if (type != PAYLOAD_TYPE_ANON_REQ) { + MESH_DEBUG_PRINTLN("%s Mesh::encodeAnonDatagram(): error, invalid type %u", getLogDateTime(), (uint32_t)type); + return; + } + if (data_len + 1 + PUB_KEY_SIZE + CIPHER_BLOCK_SIZE-1 > MAX_PACKET_PAYLOAD) { + MESH_DEBUG_PRINTLN("%s Mesh::encodeAnonDatagram(): error, too long: %u", getLogDateTime(), data_len); + return; + } + + packet->header |= (type << PH_TYPE_SHIFT); // ROUTE_TYPE_* should already be set! + + int len = 0; + if (type == PAYLOAD_TYPE_ANON_REQ) { + len += dest.copyHashTo(&packet->payload[len]); // dest hash + memcpy(&packet->payload[len], sender.pub_key, PUB_KEY_SIZE); len += PUB_KEY_SIZE; // sender pub_key + } else { + // FUTURE: + } + if (packet->getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT && (packet->transport_codes[1] >> 14) == CRYPTO_NEW_DIRECT) { + // new encryption + uint8_t iv[3]; + memcpy(iv, &packet->transport_codes[1], 2); // session-id + iv[2] = packet->transport_codes[0] & 0xFF; // seq-no + len += Utils::encryptThenMAC(secret, iv, 3, &packet->payload[len], data, data_len); + } else { + // legacy encryption + len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len); + } + + packet->payload_len = len; +} + Packet* Mesh::createAnonDatagram(uint8_t type, const LocalIdentity& sender, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len) { if (type == PAYLOAD_TYPE_ANON_REQ) { if (data_len + 1 + PUB_KEY_SIZE + CIPHER_BLOCK_SIZE-1 > MAX_PACKET_PAYLOAD) return NULL; @@ -586,6 +729,21 @@ Packet* Mesh::createMultiAck(const uint8_t* ack, uint8_t len, uint8_t remaining) return packet; } +void Mesh::encodeAck(Packet* packet, const uint8_t* ack, uint8_t len) { + packet->header |= (PAYLOAD_TYPE_ACK << PH_TYPE_SHIFT); // ROUTE_TYPE_* should already be set! + + memcpy(packet->payload, ack, len); + packet->payload_len = len; +} + +void Mesh::encodeMultiAck(Packet* packet, const uint8_t* ack, uint8_t len, uint8_t remaining) { + packet->header |= (PAYLOAD_TYPE_MULTIPART << PH_TYPE_SHIFT); // ROUTE_TYPE_* should already be set! + + packet->payload[0] = (remaining << 4) | PAYLOAD_TYPE_ACK; + memcpy(&packet->payload[1], ack, len); + packet->payload_len = 1 + len; +} + Packet* Mesh::createRawData(const uint8_t* data, size_t len) { if (len > sizeof(Packet::payload)) return NULL; // invalid arg @@ -714,6 +872,30 @@ void Mesh::sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uin sendPacket(packet, pri, delay_millis); } +void Mesh::send(Packet* packet, uint32_t delay_millis) { + // packet header should already be set + // transport codes should already be set (optional) + // path/path_len should already be set (if direct) + + uint8_t pri; + if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) { // TRACE packets are different + // for TRACE packets, path is appended to end of PAYLOAD. (path is used for SNR's) + // TODO: memcpy(&packet->payload[packet->payload_len], path, path_len); // NOTE: path_len here can be > 64, and NOT in the new scheme + // packet->payload_len += path_len; + + packet->path_len = 0; + pri = 5; // maybe make this configurable + } else { + if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) { + pri = 1; // slightly less priority + } else { + pri = 0; + } + } + _tables->markSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us + sendPacket(packet, pri, delay_millis); +} + void Mesh::sendZeroHop(Packet* packet, uint32_t delay_millis) { packet->header &= ~PH_ROUTE_MASK; packet->header |= ROUTE_TYPE_DIRECT; @@ -728,7 +910,7 @@ void Mesh::sendZeroHop(Packet* packet, uint32_t delay_millis) { void Mesh::sendZeroHop(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis) { packet->header &= ~PH_ROUTE_MASK; packet->header |= ROUTE_TYPE_TRANSPORT_DIRECT; - packet->transport_codes[0] = transport_codes[0]; + packet->transport_codes[0] = transport_codes[0]; // REVISIT: does this clash with new direct mode?? packet->transport_codes[1] = transport_codes[1]; packet->path_len = 0; // path_len of zero means Zero Hop diff --git a/src/Mesh.h b/src/Mesh.h index 22bb5c6d592..4999885a10c 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -91,8 +91,9 @@ class Mesh : public Dispatcher { * \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 + * \param crypto_type the crypto method that was used with this packet, one of CRYPTO_* */ - virtual void onPeerDataRecv(Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) { } + virtual void onPeerDataRecv(Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len, uint8_t crypto_type) { } /** * \brief A TRACE packet has been received. (and has reached the end of its given path) @@ -113,7 +114,7 @@ class Mesh : public Dispatcher { * \param secret the pre-calculated shared-secret (handy for sending response packet) * \returns true, if path was accepted and that reciprocal path should be sent */ - virtual bool onPeerPathRecv(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) { return false; } + virtual bool onPeerPathRecv(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, uint8_t crypto_type) { return false; } /** * \brief A new incoming Advertisement has been received. @@ -126,14 +127,18 @@ class Mesh : public Dispatcher { * NOTE: these can be received multiple times (per sender/contents), via different routes * \param secret ECDH shared secret * \param sender public key provided by sender + * \param data decrypted data from payload + * \param crypto_type the crypto method that was used with this packet, one of CRYPTO_* */ - virtual void onAnonDataRecv(Packet* packet, const uint8_t* secret, const Identity& sender, uint8_t* data, size_t len) { } + virtual void onAnonDataRecv(Packet* packet, const uint8_t* secret, const Identity& sender, uint8_t* data, size_t len, uint8_t crypto_type) { } +#if 0 // ?? /** * \brief A path TO 'sender' has been received. (also with optional 'extra' data encoded) * NOTE: these can be received multiple times (per sender), via different routes */ virtual void onPathRecv(Packet* packet, Identity& sender, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { } +#endif /** * \brief A control packet has been received. @@ -166,6 +171,12 @@ class Mesh : public Dispatcher { */ virtual void onAckRecv(Packet* packet, uint32_t ack_crc) { } + /** + * \brief Hook for sub-classes to test if hash is a neighbor. + * \returns true, if given path hash is a direct neighbor of this node. + */ + virtual bool isHashNeighbor(const uint8_t* hash, uint8_t hash_size) { return false; } + Mesh(Radio& radio, MillisecondClock& ms, RNG& rng, RTCClock& rtc, PacketManager& mgr, MeshTables& tables) : Dispatcher(radio, ms, mgr), _rng(&rng), _rtc(&rtc), _tables(&tables) { @@ -196,6 +207,11 @@ class Mesh : public Dispatcher { Packet* createTrace(uint32_t tag, uint32_t auth_code, uint8_t flags = 0); Packet* createControlData(const uint8_t* data, size_t len); + void encodeDatagram(Packet* pkt, uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t len); + void encodeAnonDatagram(Packet* pkt, uint8_t type, const LocalIdentity& sender, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len); + void encodeAck(Packet* pkt, const uint8_t* ack, uint8_t len); + void encodeMultiAck(Packet* pkt, const uint8_t* ack, uint8_t len, uint8_t remaining); + /** * \brief send a locally-generated Packet with flood routing */ @@ -223,6 +239,10 @@ class Mesh : public Dispatcher { */ void sendZeroHop(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis=0); + /** + * \brief send a locally-generated Packet. Headers, tranport_codes and path/path_len should Already Be Set! + */ + void send(Packet* packet, uint32_t delay_millis=0); }; } diff --git a/src/Packet.cpp b/src/Packet.cpp index aad3e2f48ee..9ae49410fd9 100644 --- a/src/Packet.cpp +++ b/src/Packet.cpp @@ -38,6 +38,15 @@ int Packet::getRawLength() const { return 2 + getPathByteLen() + payload_len + (hasTransportCodes() ? 4 : 0); } +bool Packet::isPathAtEnd() const { + if (getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT) { + // has cursor reached end? + uint8_t cursor = (transport_codes[0] >> 8) & 63; // lower 6 bits only + return cursor >= getPathHashCount(); + } + return getPathHashCount() == 0; // legacy Direct mode, path gets consumed +} + void Packet::calculatePacketHash(uint8_t* hash) const { SHA256 sha; uint8_t t = getPayloadType(); @@ -45,6 +54,11 @@ void Packet::calculatePacketHash(uint8_t* hash) const { if (t == PAYLOAD_TYPE_TRACE) { sha.update(&path_len, sizeof(path_len)); // CAVEAT: TRACE packets can revisit same node on return path } + if (getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT && transport_codes[1] != 0) { // has seq-no & session-id + sha.update(&transport_codes[1], 2); // session-id + uint8_t seq_no = transport_codes[0] & 0xFF; // low 8 bits + sha.update(&seq_no, 1); + } sha.update(payload, payload_len); sha.finalize(hash, MAX_HASH_SIZE); } diff --git a/src/Packet.h b/src/Packet.h index c19d9e9d8fc..f20fecf6002 100644 --- a/src/Packet.h +++ b/src/Packet.h @@ -16,6 +16,17 @@ namespace mesh { #define ROUTE_TYPE_DIRECT 0x02 // direct route, 'path' is supplied #define ROUTE_TYPE_TRANSPORT_DIRECT 0x03 // direct route + transport codes +#define CRYPTO_LEGACY 0 +#define CRYPTO_NEW_DIRECT 1 +#define CRYPTO_RESERVED1 2 +#define CRYPTO_RESERVED2 3 + +// new Transport_Direct encoding (upper 2 bits of transport_code[0]) +#define DM_MODE_SEQ_NORMAL 0 // normal path, sequenced +#define DM_MODE_SEQ_WIDE 1 // wide/fuzzy path, sequenced +#define DM_MODE_SEQ_WIDE_MODIFIED 2 // wide/fuzzy path (modified along the way), sequenced +#define DM_MODE_SEQ_LINK_ID 3 // fixed Link-Id, sequenced + #define PAYLOAD_TYPE_REQ 0x00 // request (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob) #define PAYLOAD_TYPE_RESPONSE 0x01 // response to REQ or ANON_REQ (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob) #define PAYLOAD_TYPE_TXT_MSG 0x02 // a plain text message (prefixed with dest/src hashes, MAC) (enc data: timestamp, text) @@ -82,6 +93,8 @@ class Packet { void setPathHashCount(uint8_t n) { path_len &= ~63; path_len |= n; } void setPathHashSizeAndCount(uint8_t sz, uint8_t n) { path_len = ((sz - 1) << 6) | (n & 63); } + bool isPathAtEnd() const; + static uint8_t copyPath(uint8_t* dest, const uint8_t* src, uint8_t path_len); // returns path_len static size_t writePath(uint8_t* dest, const uint8_t* src, uint8_t path_len); // returns byte length written static bool isValidPathLen(uint8_t path_len); diff --git a/src/Utils.cpp b/src/Utils.cpp index dc547bd2f05..9781856832f 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -174,6 +174,27 @@ int Utils::MACThenDecrypt(const uint8_t* shared_secret, uint8_t* dest, const uin return 0; // invalid HMAC } +static void derive_key(uint8_t* derived_key, const uint8_t* key, int key_len, const uint8_t* iv, int iv_len) { + // TODO: look into HKDF + for (int i = 0; i < 32; i++) { + derived_key[i] = key[i % key_len] ^ iv[i % iv_len]; + } +} + +int Utils::encryptThenMAC(const uint8_t* shared_secret, const uint8_t* iv, int iv_len, uint8_t* dest, const uint8_t* src, int src_len) { + uint8_t derived[PUB_KEY_SIZE]; + derive_key(derived, shared_secret, PUB_KEY_SIZE, iv, iv_len); + + return Utils::encryptThenMAC(derived, dest, src, src_len); +} + +int Utils::MACThenDecrypt(const uint8_t* shared_secret, const uint8_t* iv, int iv_len, uint8_t* dest, const uint8_t* src, int src_len) { + uint8_t derived[PUB_KEY_SIZE]; + derive_key(derived, shared_secret, PUB_KEY_SIZE, iv, iv_len); + + return Utils::MACThenDecrypt(derived, dest, src, src_len); +} + static const char hex_chars[] = "0123456789ABCDEF"; void Utils::toHex(char* dest, const uint8_t* src, size_t len) { diff --git a/src/Utils.h b/src/Utils.h index 0f1af39d8d9..4103b61158e 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -43,7 +43,7 @@ class Utils { static int decrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len); /** - * \brief encrypts bytes in src, then calculates MAC on ciphertext, inserting into leading bytes of 'dest'. + * \brief encrypts bytes in src, then calculates MAC on ciphertext, inserting into leading bytes of 'dest'. (using ECB mode) * \returns total length of bytes in 'dest' (MAC + ciphertext) */ static int encryptThenMAC(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len); @@ -54,6 +54,18 @@ class Utils { */ static int MACThenDecrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len); + /** + * \brief encrypts bytes in src, then calculates auth/tag, inserting into leading 8 bytes of 'dest'. (using IV, and streaming mode) + * \returns total length of bytes in 'dest' (tag + ciphertext) + */ + static int encryptThenMAC(const uint8_t* shared_secret, const uint8_t* iv, int iv_len, uint8_t* dest, const uint8_t* src, int src_len); + + /** + * \brief decrypts bytes in src after the 8 byte auth/tag, then checks the 8 byte tag (using IV, and streaming mode). + * \returns zero if tag is invalid, otherwise the length of decrypted bytes in 'dest' + */ + static int MACThenDecrypt(const uint8_t* shared_secret, const uint8_t* iv, int iv_len, uint8_t* dest, const uint8_t* src, int src_len); + /** * \brief converts 'src' bytes with given length to Hex representation, and null terminates. */ diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 28592415ca9..c78d1b64b7b 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -11,6 +11,32 @@ #define CLI_REPLY_DELAY_MILLIS 600 +mesh::Packet* BaseChatMesh::obtainPacketFor(ContactInfo& contact) { // this is just a default legacy impl + auto pkt = obtainNewPacket(); + if (pkt) { + if (contact.out_path_len == OUT_PATH_UNKNOWN) { + pkt->header |= ROUTE_TYPE_FLOOD; + pkt->path_len = 0; + } else { + pkt->header |= ROUTE_TYPE_DIRECT; + pkt->path_len = mesh::Packet::copyPath(pkt->path, contact.out_path, contact.out_path_len); + } + } + return pkt; +} + +void BaseChatMesh::updateLinkQualityStats(ContactInfo& from, mesh::Packet* packet) { + uint16_t session_id = packet->transport_codes[1]; + uint8_t seq_no = (packet->transport_codes[0] & 0xFF); // lower 8 bits + // if (session_id == last_session_id) { + // TODO: compare seq_no with last received seq_no... keep tally of how many packets were NOT received, etc + // } else { + // TODO: reset stats for Contact + // last_session_id = session_id; + //} + //last_seq_no = seq_no; +} + void BaseChatMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) { sendFlood(pkt, delay_millis); } @@ -40,20 +66,26 @@ mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name, double lat, doubl return createAdvert(self_id, app_data, app_data_len); } -void BaseChatMesh::sendAckTo(const ContactInfo& dest, const uint8_t* ack_hash, uint8_t ack_len) { +void BaseChatMesh::sendAckTo(ContactInfo& dest, const uint8_t* ack_hash, uint8_t ack_len) { if (dest.out_path_len == OUT_PATH_UNKNOWN) { mesh::Packet* ack = createAck(ack_hash, ack_len); if (ack) sendFloodScoped(dest, ack, TXT_ACK_DELAY); } else { uint32_t d = TXT_ACK_DELAY; if (getExtraAckTransmitCount() > 0) { - mesh::Packet* a1 = createMultiAck(ack_hash, ack_len, 1); - if (a1) sendDirect(a1, dest.out_path, dest.out_path_len, d); + mesh::Packet* a1 = obtainPacketFor(dest); + if (a1) { + encodeMultiAck(a1, ack_hash, ack_len, 1); + send(a1, d); + } d += 300; } - mesh::Packet* a2 = createAck(ack_hash, ack_len); - if (a2) sendDirect(a2, dest.out_path, dest.out_path_len, d); + mesh::Packet* a2 = obtainPacketFor(dest); + if (a2) { + encodeAck(a2, ack_hash, ack_len); + send(a2, d); + } } } @@ -105,6 +137,13 @@ ContactInfo* BaseChatMesh::allocateContactSlot(bool transient_only) { return NULL; // no space, no overwrite or all contacts are all favourites } +void BaseChatMesh::initContactSlot(ContactInfo& c) { // zero transient fields, on contacts slot re-use + c.shared_secret_valid = false; // mark shared_secret as needing calculation + c.direct_mode = CRYPTO_LEGACY; // until we find out otherwise + c.curr_session_id = 0; + c.next_seq_no = 0; +} + void BaseChatMesh::populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp) { memset(&ci, 0, sizeof(ci)); ci.id = id; @@ -180,10 +219,10 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, MESH_DEBUG_PRINTLN("onAdvertRecv: unable to allocate contact slot for new contact"); return; } - + populateContactFromAdvert(*from, id, parser, timestamp); from->sync_since = 0; - from->shared_secret_valid = false; + initContactSlot(*from); } // update @@ -194,6 +233,7 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, from->gps_lat = parser.getIntLat(); from->gps_lon = parser.getIntLon(); } + from->last_advert_timestamp = timestamp; from->lastmod = getRTCClock()->getCurrentTime(); @@ -219,7 +259,7 @@ void BaseChatMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { } } -void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) { +void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len, uint8_t crypto_type) { int i = matching_peer_indexes[sender_idx]; if (i < 0 || i >= num_contacts) { MESH_DEBUG_PRINTLN("onPeerDataRecv: Invalid sender idx: %d", i); @@ -227,6 +267,12 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender } ContactInfo& from = contacts[i]; + if (crypto_type == CRYPTO_NEW_DIRECT) { + if (from.direct_mode == CRYPTO_LEGACY) { + from.direct_mode = (DM_MODE_SEQ_NORMAL << 4) | CRYPTO_NEW_DIRECT; // have received from Contact in new scheme, so they must support it + } + updateLinkQualityStats(from, packet); + } if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { uint32_t sender_timestamp; @@ -250,7 +296,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, - PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 6); + PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 6); if (path) sendFloodScoped(from, path, TXT_ACK_DELAY); } else { sendAckTo(from, ack_hash, 6); @@ -259,6 +305,12 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender char *text = (char *)&data[5]; onCommandDataRecv(from, packet, sender_timestamp, text); // let UI know // NOTE: no ack expected for CLI_DATA replies + + if (packet->isRouteFlood()) { + // let this sender know path TO here, so they can use sendDirect() (NOTE: no ACK as extra) + mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, 0, NULL, 0); + if (path) sendFloodScoped(from, path); + } } else if (flags == TXT_TYPE_CLI_COMMAND) { uint8_t temp[166]; char *command = (char *)&data[5]; @@ -278,12 +330,13 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender memcpy(temp, ×tamp, 4); temp[4] = (TXT_TYPE_CLI_DATA << 2); - auto reply_pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, from.id, secret, temp, 5 + text_len); + auto reply_pkt = obtainPacketFor(from); if (reply_pkt) { - if (from.out_path_len == OUT_PATH_UNKNOWN) { - sendFloodScoped(from, reply_pkt, CLI_REPLY_DELAY_MILLIS); + encodeDatagram(reply_pkt, PAYLOAD_TYPE_TXT_MSG, from.id, secret, temp, 5 + text_len); + if (reply_pkt->isRouteDirect()) { + send(reply_pkt, CLI_REPLY_DELAY_MILLIS); } else { - sendDirect(reply_pkt, from.out_path, from.out_path_len, CLI_REPLY_DELAY_MILLIS); + sendFloodScoped(from, reply_pkt, CLI_REPLY_DELAY_MILLIS); } } } @@ -319,10 +372,11 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender PAYLOAD_TYPE_RESPONSE, temp_buf, reply_len); if (path) sendFloodScoped(from, path, SERVER_RESPONSE_DELAY); } else { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, temp_buf, reply_len); + auto reply = obtainPacketFor(from); if (reply) { - if (from.out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT - sendDirect(reply, from.out_path, from.out_path_len, SERVER_RESPONSE_DELAY); + encodeDatagram(reply, PAYLOAD_TYPE_RESPONSE, from.id, secret, temp_buf, reply_len); + if (reply->isRouteDirect()) { + send(reply, SERVER_RESPONSE_DELAY); } else { sendFloodScoped(from, reply, SERVER_RESPONSE_DELAY); } @@ -338,7 +392,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender } } -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) { +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, uint8_t crypto_type) { int i = matching_peer_indexes[sender_idx]; if (i < 0 || i >= num_contacts) { MESH_DEBUG_PRINTLN("onPeerPathRecv: Invalid sender idx: %d", i); @@ -346,6 +400,12 @@ bool BaseChatMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const ui } ContactInfo& from = contacts[i]; + if (crypto_type == CRYPTO_NEW_DIRECT) { + if (from.direct_mode == CRYPTO_LEGACY) { + from.direct_mode = (DM_MODE_SEQ_NORMAL << 4) | CRYPTO_NEW_DIRECT; // have received from Contact in new scheme, so they must support it + } + updateLinkQualityStats(from, packet); + } return onContactPathRecv(from, packet->path, packet->path_len, path, path_len, extra_type, extra, extra_len); } @@ -378,6 +438,8 @@ void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) { if (packet->isRouteFlood() && from->out_path_len != OUT_PATH_UNKNOWN) { // we have direct path, but other node is still sending flood, so maybe they didn't receive reciprocal path properly(?) handleReturnPathRetry(*from, packet->path, packet->path_len); + } else if (packet->getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT) { + updateLinkQualityStats(*from, packet); } } } @@ -448,7 +510,7 @@ void BaseChatMesh::onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mes } } -mesh::Packet* BaseChatMesh::composeMsgPacket(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack) { +mesh::Packet* BaseChatMesh::composeMsgPacket(ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack) { int text_len = strlen(text); if (text_len > MAX_TEXT_LEN) return NULL; if (attempt > 3 && text_len > MAX_TEXT_LEN-2) return NULL; @@ -467,29 +529,33 @@ mesh::Packet* BaseChatMesh::composeMsgPacket(const ContactInfo& recipient, uint3 temp[len++] = attempt; // hide attempt number at tail end of payload } - return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, len); + auto pkt = obtainPacketFor(recipient); + if (pkt) { + encodeDatagram(pkt, PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, len); + } + return pkt; } -int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout) { +int BaseChatMesh::sendMessage(ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout) { mesh::Packet* pkt = composeMsgPacket(recipient, timestamp, attempt, text, expected_ack); if (pkt == NULL) return MSG_SEND_FAILED; uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); int rc; - if (recipient.out_path_len == OUT_PATH_UNKNOWN) { + if (pkt->isRouteDirect()) { + send(pkt); + txt_send_timeout = futureMillis(est_timeout = calcDirectTimeoutMillisFor(t, recipient.out_path_len)); + rc = MSG_SEND_SENT_DIRECT; + } else { sendFloodScoped(recipient, pkt); txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t)); rc = MSG_SEND_SENT_FLOOD; - } else { - sendDirect(pkt, recipient.out_path, recipient.out_path_len); - txt_send_timeout = futureMillis(est_timeout = calcDirectTimeoutMillisFor(t, recipient.out_path_len)); - rc = MSG_SEND_SENT_DIRECT; } return rc; } -int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, uint8_t txt_type, const char* text, uint32_t& est_timeout) { +int BaseChatMesh::sendCommandData(ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, uint8_t txt_type, const char* text, uint32_t& est_timeout) { int text_len = strlen(text); if (text_len > MAX_TEXT_LEN) return MSG_SEND_FAILED; @@ -498,19 +564,21 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest temp[4] = (attempt & 3) | (txt_type << 2); memcpy(&temp[5], text, text_len + 1); - auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, 5 + text_len); + auto pkt = obtainPacketFor(recipient); if (pkt == NULL) return MSG_SEND_FAILED; + encodeDatagram(pkt, PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, 5 + text_len); + uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); int rc; - if (recipient.out_path_len == OUT_PATH_UNKNOWN) { + if (pkt->isRouteDirect()) { + send(pkt); + txt_send_timeout = futureMillis(est_timeout = calcDirectTimeoutMillisFor(t, recipient.out_path_len)); + rc = MSG_SEND_SENT_DIRECT; + } else { sendFloodScoped(recipient, pkt); txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t)); rc = MSG_SEND_SENT_FLOOD; - } else { - sendDirect(pkt, recipient.out_path, recipient.out_path_len); - txt_send_timeout = futureMillis(est_timeout = calcDirectTimeoutMillisFor(t, recipient.out_path_len)); - rc = MSG_SEND_SENT_DIRECT; } return rc; } @@ -600,7 +668,22 @@ bool BaseChatMesh::importContact(const uint8_t src_buf[], uint8_t len) { return false; // error } -int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password, uint32_t& est_timeout) { +int BaseChatMesh::stdSend(mesh::Packet* pkt, const ContactInfo& recipient, uint32_t& est_timeout) { + int rc; + uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); + if (pkt->isRouteDirect()) { + send(pkt); + est_timeout = calcDirectTimeoutMillisFor(t, recipient.out_path_len); + rc = MSG_SEND_SENT_DIRECT; + } else { + sendFloodScoped(recipient, pkt); + est_timeout = calcFloodTimeoutMillisFor(t); + rc = MSG_SEND_SENT_FLOOD; + } + return rc; +} + +int BaseChatMesh::sendLogin(ContactInfo& recipient, const char* password, uint32_t& est_timeout) { mesh::Packet* pkt; { int tlen; @@ -618,24 +701,15 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password, tlen = 4 + len; } - pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.getSharedSecret(self_id), temp, tlen); - } - if (pkt) { - uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); - if (recipient.out_path_len == OUT_PATH_UNKNOWN) { - sendFloodScoped(recipient, pkt); - est_timeout = calcFloodTimeoutMillisFor(t); - return MSG_SEND_SENT_FLOOD; - } else { - sendDirect(pkt, recipient.out_path, recipient.out_path_len); - est_timeout = calcDirectTimeoutMillisFor(t, recipient.out_path_len); - return MSG_SEND_SENT_DIRECT; - } + pkt = obtainPacketFor(recipient); + if (pkt == NULL) return MSG_SEND_FAILED; + + encodeAnonDatagram(pkt, PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.getSharedSecret(self_id), temp, tlen); } - return MSG_SEND_FAILED; + return stdSend(pkt, recipient, est_timeout); } -int BaseChatMesh::sendAnonReq(const ContactInfo& recipient, const uint8_t* data, uint8_t len, uint32_t& tag, uint32_t& est_timeout) { +int BaseChatMesh::sendAnonReq(ContactInfo& recipient, const uint8_t* data, uint8_t len, uint32_t& tag, uint32_t& est_timeout) { mesh::Packet* pkt; { uint8_t temp[MAX_PACKET_PAYLOAD]; @@ -643,24 +717,16 @@ int BaseChatMesh::sendAnonReq(const ContactInfo& recipient, const uint8_t* data, memcpy(temp, &tag, 4); // tag to match later (also extra blob to help make packet_hash unique) memcpy(&temp[4], data, len); - pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + len); - } - if (pkt) { - uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); - if (recipient.out_path_len == OUT_PATH_UNKNOWN) { - sendFloodScoped(recipient, pkt); - est_timeout = calcFloodTimeoutMillisFor(t); - return MSG_SEND_SENT_FLOOD; - } else { - sendDirect(pkt, recipient.out_path, recipient.out_path_len); - est_timeout = calcDirectTimeoutMillisFor(t, recipient.out_path_len); - return MSG_SEND_SENT_DIRECT; - } + pkt = obtainPacketFor(recipient); + if (pkt == NULL) return MSG_SEND_FAILED; + + encodeAnonDatagram(pkt, PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + len); } - return MSG_SEND_FAILED; + + return stdSend(pkt, recipient, est_timeout); } -int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_data, uint8_t data_len, uint32_t& tag, uint32_t& est_timeout) { +int BaseChatMesh::sendRequest(ContactInfo& recipient, const uint8_t* req_data, uint8_t data_len, uint32_t& tag, uint32_t& est_timeout) { if (data_len > MAX_PACKET_PAYLOAD - 16) return MSG_SEND_FAILED; mesh::Packet* pkt; @@ -670,24 +736,16 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_ memcpy(temp, &tag, 4); // mostly an extra blob to help make packet_hash unique memcpy(&temp[4], req_data, data_len); - pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + data_len); - } - if (pkt) { - uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); - if (recipient.out_path_len == OUT_PATH_UNKNOWN) { - sendFloodScoped(recipient, pkt); - est_timeout = calcFloodTimeoutMillisFor(t); - return MSG_SEND_SENT_FLOOD; - } else { - sendDirect(pkt, recipient.out_path, recipient.out_path_len); - est_timeout = calcDirectTimeoutMillisFor(t, recipient.out_path_len); - return MSG_SEND_SENT_DIRECT; - } + auto pkt = obtainPacketFor(recipient); + if (pkt == NULL) return MSG_SEND_FAILED; + + encodeDatagram(pkt, PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + data_len); } - return MSG_SEND_FAILED; + + return stdSend(pkt, recipient, est_timeout); } -int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout) { +int BaseChatMesh::sendRequest(ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout) { mesh::Packet* pkt; { uint8_t temp[13]; @@ -697,21 +755,13 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, u memset(&temp[5], 0, 4); // reserved (possibly for 'since' param) getRNG()->random(&temp[9], 4); // random blob to help make packet-hash unique - pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, sizeof(temp)); - } - if (pkt) { - uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); - if (recipient.out_path_len == OUT_PATH_UNKNOWN) { - sendFloodScoped(recipient, pkt); - est_timeout = calcFloodTimeoutMillisFor(t); - return MSG_SEND_SENT_FLOOD; - } else { - sendDirect(pkt, recipient.out_path, recipient.out_path_len); - est_timeout = calcDirectTimeoutMillisFor(t, recipient.out_path_len); - return MSG_SEND_SENT_DIRECT; - } + auto pkt = obtainPacketFor(recipient); + if (pkt == NULL) return MSG_SEND_FAILED; + + encodeDatagram(pkt, PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, sizeof(temp)); } - return MSG_SEND_FAILED; + + return stdSend(pkt, recipient, est_timeout); } bool BaseChatMesh::startConnection(const ContactInfo& contact, uint16_t keep_alive_secs) { @@ -883,7 +933,7 @@ bool BaseChatMesh::addContact(const ContactInfo& contact) { ContactInfo* dest = allocateContactSlot(contact.type == ADV_TYPE_NONE); if (dest) { *dest = contact; - dest->shared_secret_valid = false; // mark shared_secret as needing calculation + initContactSlot(*dest); return true; // success } return false; diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 835384b7415..d6846cb2a7a 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -74,8 +74,8 @@ class BaseChatMesh : public mesh::Mesh { uint8_t temp_buf[MAX_TRANS_UNIT]; ConnectionInfo connections[MAX_CONNECTIONS]; - mesh::Packet* composeMsgPacket(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack); - void sendAckTo(const ContactInfo& dest, const uint8_t* ack_hash, uint8_t ack_len=4); + mesh::Packet* composeMsgPacket(ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack); + void sendAckTo(ContactInfo& dest, const uint8_t* ack_hash, uint8_t ack_len=4); protected: BaseChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::PacketManager& mgr, mesh::MeshTables& tables) @@ -98,9 +98,12 @@ class BaseChatMesh : public mesh::Mesh { memset(contacts, 0, sizeof(contacts[0])*MAX_ANON_CONTACTS); // set all to have type = ADV_TYPE_NONE(0) num_contacts = MAX_ANON_CONTACTS; // seed the first contacts for anon requests } + void initContactSlot(ContactInfo& c); void populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp); ContactInfo* allocateContactSlot(bool transient_only=false); // helper to find slot for new contact + void updateLinkQualityStats(ContactInfo& from, mesh::Packet* packet); + // 'UI' concepts, for sub-classes to implement virtual bool isAutoAddEnabled() const { return true; } virtual bool shouldAutoAddContactType(uint8_t type) const { return true; } @@ -126,6 +129,9 @@ class BaseChatMesh : public mesh::Mesh { virtual void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) = 0; virtual void handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len); + // override in derived classes, to support setting up an outgoing Packet with appropriate route_type, transport codes and path + virtual mesh::Packet* obtainPacketFor(ContactInfo& contact); + virtual void sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis=0); virtual void sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis=0); @@ -137,8 +143,8 @@ class BaseChatMesh : public mesh::Mesh { void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) override; int searchPeersByHash(const uint8_t* hash) override; 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 onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len, uint8_t crypto_type) 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, uint8_t crypto_type) 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; @@ -153,17 +159,20 @@ class BaseChatMesh : public mesh::Mesh { ContactInfo* checkConnectionsAck(const uint8_t* data); void checkConnections(); + // helper + int stdSend(mesh::Packet* pkt, const ContactInfo& recipient, uint32_t& est_timeout); // returns one of MSG_SEND_* codes + public: mesh::Packet* createSelfAdvert(const char* name); mesh::Packet* createSelfAdvert(const char* name, double lat, double lon); - int sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout); - int sendCommandData(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, uint8_t txt_type, const char* text, uint32_t& est_timeout); + int sendMessage(ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout); + int sendCommandData(ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, uint8_t txt_type, const char* text, uint32_t& est_timeout); bool sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& channel, const char* sender_name, const char* text, int text_len); bool sendGroupData(mesh::GroupChannel& channel, uint8_t* path, uint8_t path_len, uint16_t data_type, const uint8_t* data, int data_len); - int sendLogin(const ContactInfo& recipient, const char* password, uint32_t& est_timeout); - int sendAnonReq(const ContactInfo& recipient, const uint8_t* data, uint8_t len, uint32_t& tag, uint32_t& est_timeout); - int sendRequest(const ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout); - int sendRequest(const ContactInfo& recipient, const uint8_t* req_data, uint8_t data_len, uint32_t& tag, uint32_t& est_timeout); + int sendLogin(ContactInfo& recipient, const char* password, uint32_t& est_timeout); + int sendAnonReq(ContactInfo& recipient, const uint8_t* data, uint8_t len, uint32_t& tag, uint32_t& est_timeout); + int sendRequest(ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout); + int sendRequest(ContactInfo& recipient, const uint8_t* req_data, uint8_t data_len, uint32_t& tag, uint32_t& est_timeout); 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); diff --git a/src/helpers/ClientACL.h b/src/helpers/ClientACL.h index 9938bc1e1f6..0050e7dfd2a 100644 --- a/src/helpers/ClientACL.h +++ b/src/helpers/ClientACL.h @@ -15,7 +15,10 @@ struct ClientInfo { mesh::Identity id; uint8_t permissions; + uint8_t direct_mode; uint8_t out_path_len; + uint8_t next_seq_no; + uint16_t curr_session_id; uint8_t out_path[MAX_PATH_SIZE]; uint8_t shared_secret[PUB_KEY_SIZE]; uint32_t last_timestamp; // by THEIR clock (transient) diff --git a/src/helpers/ContactInfo.h b/src/helpers/ContactInfo.h index 5a156c82929..f93fea77af3 100644 --- a/src/helpers/ContactInfo.h +++ b/src/helpers/ContactInfo.h @@ -12,6 +12,9 @@ struct ContactInfo { uint8_t flags; uint8_t out_path_len; mutable bool shared_secret_valid; // flag to indicate if shared_secret has been calculated + uint8_t direct_mode; + uint8_t next_seq_no; + uint16_t curr_session_id; uint8_t out_path[MAX_PATH_SIZE]; uint32_t last_advert_timestamp; // by THEIR clock uint32_t lastmod; // by OUR clock From a8b3118b42abc9d01819b11d13cfe8e7fc210ffc Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 23 Oct 2026 13:04:26 +1100 Subject: [PATCH 2/5] * added 'wide.snr.thresh' config * repeater: Wide mode, isHashNeighbor() now only considers neighbor nodes with SNR >= threshold --- examples/simple_repeater/MyMesh.cpp | 4 +++- examples/simple_room_server/MyMesh.cpp | 1 + examples/simple_sensor/SensorMesh.cpp | 1 + src/helpers/CommonCLI.cpp | 7 +++++++ src/helpers/CommonCLI.h | 2 ++ 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index f73594d1795..e8d725f2578 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -462,9 +462,10 @@ void MyMesh::sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, ui bool MyMesh::isHashNeighbor(const uint8_t* hash, uint8_t hash_size) { #if MAX_NEIGHBOURS + int8_t snr_threshold = (int8_t)(_prefs.wide_snr_threshold * 4); for (int i = 0; i < MAX_NEIGHBOURS; i++) { auto n = &neighbours[i]; - if (n->heard_timestamp > 0 && n->id.isHashMatch(hash, hash_size)) return true; + if (n->heard_timestamp > 0 && n->snr >= snr_threshold && n->id.isHashMatch(hash, hash_size)) return true; } #endif return false; @@ -974,6 +975,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.interference_threshold = 0; // disabled _prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on') _prefs.loop_detect = LOOP_DETECT_MINIMAL; + _prefs.wide_snr_threshold = -8.0f; // bridge defaults _prefs.bridge_enabled = 1; // enabled diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 0b01d4aa1c1..ffa24617b2a 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -725,6 +725,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc #ifdef ROOM_PASSWORD StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password)); #endif + _prefs.wide_snr_threshold = -8.0f; // GPS defaults _prefs.gps_enabled = 0; diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 5c7da5be717..706bbaad0f6 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -867,6 +867,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise _prefs.flood_max = 64; _prefs.interference_threshold = 0; // disabled _prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on') + _prefs.wide_snr_threshold = -8.0f; // GPS defaults _prefs.gps_enabled = 0; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 04199e24d1d..b3b9eca257d 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -604,6 +604,11 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } + } else if (memcmp(config, "wide.snr.thresh ", 16) == 0) { + config += 16; + _prefs->wide_snr_threshold = atof(config); + savePrefs(); + strcpy(reply, "OK"); } else if (sender_timestamp == 0 && memcmp(config, "freq ", 5) == 0) { _prefs->freq = atof(&config[5]); savePrefs(); @@ -747,6 +752,8 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep } else { strcpy(reply, "> strict"); } + } else if (memcmp(config, "wide.snr.thresh", 15) == 0) { + sprintf(reply, "> %.2f", _prefs->wide_snr_threshold); } else if (memcmp(config, "public.key", 10) == 0) { strcpy(reply, "> "); mesh::Utils::toHex(&reply[2], _callbacks->getSelfId().pub_key, PUB_KEY_SIZE); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 2759b0c4f0e..3d218aeadac 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -72,6 +72,7 @@ class NodePrefs : public ConfigSerializer { uint8_t loop_detect = 0; uint8_t cad_enabled = 0; // hardware Channel Activity Detection before TX (boolean) uint8_t extra_sf[4]; + float wide_snr_threshold; // DM_MODE_SEQ_WIDE SNR threshold private: class RadioPrefs : public CommonRadioPrefs { @@ -186,6 +187,7 @@ class NodePrefs : public ConfigSerializer { def("f_max_uns", _parent->flood_max_unscoped); def("f_max_adv", _parent->flood_max_advert); def("loop", _parent->loop_detect); + def("wide_snr", _parent->wide_snr_threshold); } public: RepeatPrefs(NodePrefs* parent) : _parent(parent) { } From 5250eb744051e02e5a3d4f2615a384f842864f7d Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 23 Oct 2026 14:04:29 +1100 Subject: [PATCH 3/5] * workaround for edge case described here: https://github.com/meshcore-dev/MeshCore/pull/3630#issuecomment-6275517866 --- src/Mesh.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Mesh.cpp b/src/Mesh.cpp index bbf467ebaa2..01acfc41c70 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -107,6 +107,16 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { // RE-WRITE the mode bits, to indicate path was modified pkt->transport_codes[0] &= ~(3 << 14); // clear the hi 2 bits pkt->transport_codes[0] |= ((uint16_t)DM_MODE_SEQ_WIDE_MODIFIED) << 14; + // WORKAROUND: edge case where _next_ hash in the path is THIS node! + if (cursor + 1 < pkt->getPathHashCount()) { + auto next_hash = curr_hash + hash_size; + if (self_id.isHashMatch(next_hash, hash_size)) { + pkt->setPathHashCount(pkt->getPathHashCount() - 1); // remove next_hash + for (uint8_t k = cursor*hash_size; k < pkt->getPathHashCount()*hash_size; k += hash_size) { // shuffle path by 1 'entry' + memcpy(&pkt->path[k], &pkt->path[k + hash_size], hash_size); + } + } + } } else { is_match = false; } From f9e893ee8de26adfe85a795eea7687f9224e695e Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 27 Oct 2026 11:30:29 +1100 Subject: [PATCH 4/5] * updateContactFromFrame() bug fix --- examples/companion_radio/MyMesh.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 60a346bff76..5d54ad869f1 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -218,6 +218,7 @@ void MyMesh::updateContactFromFrame(ContactInfo &contact, uint32_t& last_mod, co i += 4; if (len >= i + 4) { memcpy(&last_mod, &frame[i], 4); + i += 4; if (len >= i + 4) { contact.direct_mode = frame[i++]; contact.next_seq_no = frame[i++]; From 9a6c0ae71c21346d3835e4ec90e8331a31e94763 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 1 Nov 2026 17:52:27 +1100 Subject: [PATCH 5/5] * misc fixes --- examples/simple_repeater/MyMesh.cpp | 6 +++++- examples/simple_sensor/SensorMesh.cpp | 11 +++++++++-- examples/simple_sensor/SensorMesh.h | 2 +- src/helpers/BaseChatMesh.cpp | 4 ++-- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 7945ef74a2a..11d23ed225f 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -636,7 +636,11 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m // a DIRECT login can reply via the stored out_path, as onPeerDataRecv() does for REQ ClientInfo* client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); if (client == NULL) { - MESH_DEBUG_PRINTLN("onAnonDataRecv: client ACL cannot find slot!"); // This shouldn't happen! + // prob one of ANON_REQ_TYPE_REGIONS/_OWNER/_BASIC, and not in ACL + if (reply_path_len != 0xFF) { + mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); + if (reply) { sendDirect(reply, reply_path, reply_path_len, SERVER_RESPONSE_DELAY); } + } return; } diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 706bbaad0f6..43d12b343cc 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -657,7 +657,9 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i ClientInfo* from = acl.getClientByIdx(i); if (crypto_type == CRYPTO_NEW_DIRECT) { - from->direct_mode = (DM_MODE_SEQ_NORMAL << 4) | CRYPTO_NEW_DIRECT; // have received from Client in new scheme, so they must support it + if (from->direct_mode == CRYPTO_LEGACY) { + from->direct_mode = (DM_MODE_SEQ_NORMAL << 4) | CRYPTO_NEW_DIRECT; // have received from Client in new scheme, so they must support it + } } if (type == PAYLOAD_TYPE_REQ) { // request (from a known contact) @@ -794,7 +796,7 @@ void SensorMesh::onControlDataRecv(mesh::Packet* packet) { } } -bool SensorMesh::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) { +bool SensorMesh::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, uint8_t crypto_type) { int i = matching_peer_indexes[sender_idx]; if (i < 0 || i >= acl.getNumClients()) { MESH_DEBUG_PRINTLN("onPeerPathRecv: Invalid sender idx: %d", i); @@ -802,6 +804,11 @@ bool SensorMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint } ClientInfo* from = acl.getClientByIdx(i); + if (crypto_type == CRYPTO_NEW_DIRECT) { + if (from->direct_mode == CRYPTO_LEGACY) { + from->direct_mode = (DM_MODE_SEQ_NORMAL << 4) | CRYPTO_NEW_DIRECT; // have received from Client in new scheme, so they must support it + } + } MESH_DEBUG_PRINTLN("PATH to contact, path_len=%d", (uint32_t) path_len); // NOTE: for this impl, we just replace the current 'out_path' regardless, whenever sender sends us a new out_path. diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 92ddcfbda7e..b5112797faf 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -136,7 +136,7 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { int searchPeersByHash(const uint8_t* hash) override; 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, uint8_t crypto_type) 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; + 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, uint8_t crypto_type) override; void onControlDataRecv(mesh::Packet* packet) override; void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override; diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index b03ddc5a76a..fd37c07f145 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -747,7 +747,7 @@ int BaseChatMesh::sendRequest(ContactInfo& recipient, const uint8_t* req_data, memcpy(temp, &tag, 4); // mostly an extra blob to help make packet_hash unique memcpy(&temp[4], req_data, data_len); - auto pkt = obtainPacketFor(recipient); + pkt = obtainPacketFor(recipient); if (pkt == NULL) return MSG_SEND_FAILED; encodeDatagram(pkt, PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + data_len); @@ -766,7 +766,7 @@ int BaseChatMesh::sendRequest(ContactInfo& recipient, uint8_t req_type, uint32_ memset(&temp[5], 0, 4); // reserved (possibly for 'since' param) getRNG()->random(&temp[9], 4); // random blob to help make packet-hash unique - auto pkt = obtainPacketFor(recipient); + pkt = obtainPacketFor(recipient); if (pkt == NULL) return MSG_SEND_FAILED; encodeDatagram(pkt, PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, sizeof(temp));