diff --git a/src/main.cpp b/src/main.cpp index 2378458a6..3103335b5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,7 +33,7 @@ #include "error.h" #include "power.h" // #include "rom/rtc.h" -#include "FloodingRouter.h" +#include "ReliableRouter.h" #include "main.h" #include "screen.h" #include "sleep.h" @@ -53,7 +53,7 @@ meshtastic::PowerStatus powerStatus; bool ssd1306_found; bool axp192_found; -FloodingRouter realRouter; +ReliableRouter realRouter; Router &router = realRouter; // Users of router don't care what sort of subclass implements that API // ----------------------------------------------------------------------------- diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h index e7e1b9610..48a8f0bc7 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -27,10 +27,9 @@ Any entries in recentBroadcasts that are older than X seconds (longer than the max time a flood can take) will be discarded. */ -class FloodingRouter : public Router, private PacketHistory +class FloodingRouter : public Router, protected PacketHistory { private: - public: /** * Constructor diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 986deb3fd..ee2905ad4 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -46,8 +46,6 @@ MeshService service; #include "Router.h" -#define NUM_PACKET_ID 255 // 0 is consider invalid - static uint32_t sendOwnerCb() { service.sendOurOwner(); @@ -57,23 +55,6 @@ static uint32_t sendOwnerCb() static Periodic sendOwnerPeriod(sendOwnerCb); -/// Generate a unique packet id -// FIXME, move this someplace better -PacketId generatePacketId() -{ - static uint32_t i; // Note: trying to keep this in noinit didn't help for working across reboots - static bool didInit = false; - - if (!didInit) { - didInit = true; - i = random(0, NUM_PACKET_ID + - 1); // pick a random initial sequence number at boot (to prevent repeated reboots always starting at 0) - } - - i++; - return (i % NUM_PACKET_ID) + 1; // return number between 1 and 255 -} - MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE) { // assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro @@ -90,7 +71,7 @@ void MeshService::init() void MeshService::sendOurOwner(NodeNum dest, bool wantReplies) { - MeshPacket *p = allocForSending(); + MeshPacket *p = router.allocForSending(); p->to = dest; p->decoded.want_response = wantReplies; p->decoded.which_payload = SubPacket_user_tag; @@ -265,33 +246,13 @@ void MeshService::sendToMesh(MeshPacket *p) DEBUG_MSG("Providing time to mesh %u\n", p->decoded.position.time); } - // If the phone sent a packet just to us, don't send it out into the network - if (p->to == nodeDB.getNodeNum()) { - DEBUG_MSG("Dropping locally processed message\n"); + // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it + if (router.send(p) != ERRNO_OK) { + DEBUG_MSG("No radio was able to send packet, discarding...\n"); releaseToPool(p); - } else { - // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it - if (router.send(p) != ERRNO_OK) { - DEBUG_MSG("No radio was able to send packet, discarding...\n"); - releaseToPool(p); - } } } -MeshPacket *MeshService::allocForSending() -{ - MeshPacket *p = packetPool.allocZeroed(); - - p->which_payload = MeshPacket_decoded_tag; // Assume payload is decoded at start. - p->from = nodeDB.getNodeNum(); - p->to = NODENUM_BROADCAST; - p->hop_limit = HOP_RELIABLE; - p->id = generatePacketId(); - p->rx_time = getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp - - return p; -} - void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies) { NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum()); @@ -311,7 +272,7 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies) assert(node->has_position); // Update our local node info with our position (even if we don't decide to update anyone else) - MeshPacket *p = allocForSending(); + MeshPacket *p = router.allocForSending(); p->to = dest; p->decoded.which_payload = SubPacket_position_tag; p->decoded.position = node->position; @@ -325,7 +286,7 @@ int MeshService::onGPSChanged(void *unused) // DEBUG_MSG("got gps notify\n"); // Update our local node info with our position (even if we don't decide to update anyone else) - MeshPacket *p = allocForSending(); + MeshPacket *p = router.allocForSending(); p->decoded.which_payload = SubPacket_position_tag; Position &pos = p->decoded.position; diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index f3328225b..f6e688e19 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -67,9 +67,6 @@ class MeshService /// The owner User record just got updated, update our node DB and broadcast the info into the mesh void reloadOwner() { sendOurOwner(); } - /// Allocate and return a meshpacket which defaults as send to broadcast from the current node. - MeshPacket *allocForSending(); - /// Called when the user wakes up our GUI, normally sends our latest location to the mesh (if we have it), otherwise at least /// sends our owner void sendNetworkPing(NodeNum dest, bool wantReplies = false); diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp new file mode 100644 index 000000000..6ea884ca2 --- /dev/null +++ b/src/mesh/ReliableRouter.cpp @@ -0,0 +1,99 @@ +#include "ReliableRouter.h" +#include "MeshTypes.h" +#include "configuration.h" +#include "mesh-pb-constants.h" + +// ReliableRouter::ReliableRouter() {} + +/** + * If the message is want_ack, then add it to a list of packets to retransmit. + * If we run out of retransmissions, send a nak packet towards the original client to indicate failure. + */ +ErrorCode ReliableRouter::send(MeshPacket *p) +{ + if (p->want_ack) { + auto copy = packetPool.allocCopy(*p); + startRetransmission(copy); + } + + return FloodingRouter::send(p); +} + +/** + * If we receive a want_ack packet (do not check for wasSeenRecently), send back an ack (this might generate multiple ack sends in + * case the our first ack gets lost) + * + * If we receive an ack packet (do check wasSeenRecently), clear out any retransmissions and + * forward the ack to the application layer. + * + * If we receive a nak packet (do check wasSeenRecently), clear out any retransmissions + * and forward the nak to the application layer. + * + * Otherwise, let superclass handle it. + */ +void ReliableRouter::handleReceived(MeshPacket *p) +{ + if (p->to == getNodeNum()) { // ignore ack/nak/want_ack packets that are not address to us (for now) + if (p->want_ack) { + sendAckNak(true, p->from, p->id); + } + + if (perhapsDecode(p)) { + // If the payload is valid, look for ack/nak + + PacketId ackId = p->decoded.which_ack == SubPacket_success_id_tag ? p->decoded.ack.success_id : 0; + PacketId nakId = p->decoded.which_ack == SubPacket_fail_id_tag ? p->decoded.ack.fail_id : 0; + + // we are careful to only read/update wasSeenRecently _after_ confirming this is an ack (to not mess + // up broadcasts) + if ((ackId || nakId) && !wasSeenRecently(p)) { + if (ackId) { + DEBUG_MSG("Received a ack=%d, stopping retransmissions\n", ackId); + stopRetransmission(p->to, ackId); + } else { + DEBUG_MSG("Received a nak=%d, stopping retransmissions\n", nakId); + stopRetransmission(p->to, nakId); + } + } + } + } + + // handle the packet as normal + FloodingRouter::handleReceived(p); +} + +/** + * Send an ack or a nak packet back towards whoever sent idFrom + */ +void ReliableRouter::sendAckNak(bool isAck, NodeNum to, PacketId idFrom) +{ + DEBUG_MSG("Sending an ack=%d,to=%d,idFrom=%d", isAck, to, idFrom); + auto p = allocForSending(); + p->hop_limit = 0; // Assume just immediate neighbors for now + p->to = to; + + if (isAck) { + p->decoded.ack.success_id = idFrom; + p->decoded.which_ack = SubPacket_success_id_tag; + } else { + p->decoded.ack.fail_id = idFrom; + p->decoded.which_ack = SubPacket_fail_id_tag; + } + + send(p); +} + +/** + * Stop any retransmissions we are doing of the specified node/packet ID pair + */ +void ReliableRouter::stopRetransmission(NodeNum from, PacketId id) {} + +/** + * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. + */ +void ReliableRouter::startRetransmission(MeshPacket *p) {} + +/** + * Do any retransmissions that are scheduled (FIXME - for the time being called from loop) + */ +void ReliableRouter::doRetransmissions() {} \ No newline at end of file diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h new file mode 100644 index 000000000..75bedfb64 --- /dev/null +++ b/src/mesh/ReliableRouter.h @@ -0,0 +1,63 @@ +#pragma once + +#include "FloodingRouter.h" +#include "PeriodicTask.h" + +/** + * This is a mixin that extends Router with the ability to do (one hop only) reliable message sends. + */ +class ReliableRouter : public FloodingRouter +{ + private: + public: + /** + * Constructor + * + */ + // ReliableRouter(); + + /** + * Send a packet on a suitable interface. This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error + */ + virtual ErrorCode send(MeshPacket *p); + + /** Do our retransmission handling */ + virtual void loop() + { + doRetransmissions(); + FloodingRouter::loop(); + } + + protected: + /** + * Called from loop() + * Handle any packet that is received by an interface on this node. + * Note: some packets may merely being passed through this node and will be forwarded elsewhere. + * + * Note: this method will free the provided packet + */ + virtual void handleReceived(MeshPacket *p); + + private: + /** + * Send an ack or a nak packet back towards whoever sent idFrom + */ + void sendAckNak(bool isAck, NodeNum to, PacketId idFrom); + + /** + * Stop any retransmissions we are doing of the specified node/packet ID pair + */ + void stopRetransmission(NodeNum from, PacketId id); + + /** + * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. + */ + void startRetransmission(MeshPacket *p); + + /** + * Do any retransmissions that are scheduled (FIXME - for the time being called from loop) + */ + void doRetransmissions(); +}; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 21b928f22..a7d570781 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -44,6 +44,39 @@ void Router::loop() } } +#define NUM_PACKET_ID 255 // 0 is consider invalid + +/// Generate a unique packet id +// FIXME, move this someplace better +PacketId generatePacketId() +{ + static uint32_t i; // Note: trying to keep this in noinit didn't help for working across reboots + static bool didInit = false; + + if (!didInit) { + didInit = true; + i = random(0, NUM_PACKET_ID + + 1); // pick a random initial sequence number at boot (to prevent repeated reboots always starting at 0) + } + + i++; + return (i % NUM_PACKET_ID) + 1; // return number between 1 and 255 +} + +MeshPacket *Router::allocForSending() +{ + MeshPacket *p = packetPool.allocZeroed(); + + p->which_payload = MeshPacket_decoded_tag; // Assume payload is decoded at start. + p->from = nodeDB.getNodeNum(); + p->to = NODENUM_BROADCAST; + p->hop_limit = HOP_RELIABLE; + p->id = generatePacketId(); + p->rx_time = getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp + + return p; +} + /** * Send a packet on a suitable interface. This routine will * later free() the packet to pool. This routine is not allowed to stall. @@ -51,33 +84,40 @@ void Router::loop() */ ErrorCode Router::send(MeshPacket *p) { - // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) - - assert(p->which_payload == MeshPacket_encrypted_tag || - p->which_payload == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now - - // First convert from protobufs to raw bytes - if (p->which_payload == MeshPacket_decoded_tag) { - static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union - - size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), SubPacket_fields, &p->decoded); - - assert(numbytes <= MAX_RHPACKETLEN); - crypto->encrypt(p->from, p->id, numbytes, bytes); - - // Copy back into the packet and set the variant type - memcpy(p->encrypted.bytes, bytes, numbytes); - p->encrypted.size = numbytes; - p->which_payload = MeshPacket_encrypted_tag; - } - - if (iface) { - // DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - return iface->send(p); - } else { - DEBUG_MSG("Dropping packet - no interfaces - fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + // If this packet was destined only to apps on our node, don't send it out into the network + if (p->to == nodeDB.getNodeNum()) { + DEBUG_MSG("Dropping locally processed message\n"); packetPool.release(p); - return ERRNO_NO_INTERFACES; + return ERRNO_OK; + } else { + // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) + + assert(p->which_payload == MeshPacket_encrypted_tag || + p->which_payload == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now + + // First convert from protobufs to raw bytes + if (p->which_payload == MeshPacket_decoded_tag) { + static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union + + size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), SubPacket_fields, &p->decoded); + + assert(numbytes <= MAX_RHPACKETLEN); + crypto->encrypt(p->from, p->id, numbytes, bytes); + + // Copy back into the packet and set the variant type + memcpy(p->encrypted.bytes, bytes, numbytes); + p->encrypted.size = numbytes; + p->which_payload = MeshPacket_encrypted_tag; + } + + if (iface) { + // DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + return iface->send(p); + } else { + DEBUG_MSG("Dropping packet - no interfaces - fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + packetPool.release(p); + return ERRNO_NO_INTERFACES; + } } } @@ -90,18 +130,12 @@ void Router::sniffReceived(MeshPacket *p) DEBUG_MSG("Sniffing packet not sent to us fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); } -/** - * Handle any packet that is received by an interface on this node. - * Note: some packets may merely being passed through this node and will be forwarded elsewhere. - */ -void Router::handleReceived(MeshPacket *p) +bool Router::perhapsDecode(MeshPacket *p) { - // FIXME, this class shouldn't EVER need to know about the GPS, move getValidTime() into a non gps dependent function - // Also, we should set the time from the ISR and it should have msec level resolution - p->rx_time = getValidTime(); // store the arrival timestamp for the phone + if (p->which_payload == MeshPacket_decoded_tag) + return true; // If packet was already decoded just return - assert(p->which_payload == - MeshPacket_encrypted_tag); // I _think_ the only thing that pushes to us is raw devices that just received packets + assert(p->which_payload == MeshPacket_encrypted_tag); // FIXME - someday don't send routing packets encrypted. That would allow us to route for other channels without // being able to decrypt their data. @@ -113,14 +147,37 @@ void Router::handleReceived(MeshPacket *p) // Take those raw bytes and convert them back into a well structured protobuf we can understand if (!pb_decode_from_bytes(bytes, p->encrypted.size, SubPacket_fields, &p->decoded)) { - DEBUG_MSG("Invalid protobufs in received mesh packet, discarding.\n"); + DEBUG_MSG("Invalid protobufs in received mesh packet!\n"); + return false; } else { - // parsing was successful, queue for our recipient + // parsing was successful p->which_payload = MeshPacket_decoded_tag; + return true; + } +} + +NodeNum Router::getNodeNum() +{ + return nodeDB.getNodeNum(); +} + +/** + * Handle any packet that is received by an interface on this node. + * Note: some packets may merely being passed through this node and will be forwarded elsewhere. + */ +void Router::handleReceived(MeshPacket *p) +{ + // FIXME, this class shouldn't EVER need to know about the GPS, move getValidTime() into a non gps dependent function + // Also, we should set the time from the ISR and it should have msec level resolution + p->rx_time = getValidTime(); // store the arrival timestamp for the phone + + // Take those raw bytes and convert them back into a well structured protobuf we can understand + if (perhapsDecode(p)) { + // parsing was successful, queue for our recipient sniffReceived(p); - uint8_t ourAddr = nodeDB.getNodeNum(); - if (p->to == NODENUM_BROADCAST || p->to == ourAddr) { + + if (p->to == NODENUM_BROADCAST || p->to == getNodeNum()) { DEBUG_MSG("Notifying observers of received packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); notifyPacketReceived.notifyObservers(p); } diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 03d75d33d..d0a8e029c 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -44,7 +44,7 @@ class Router * do idle processing * Mostly looking in our incoming rxPacket queue and calling handleReceived. */ - void loop(); + virtual void loop(); /** * Send a packet on a suitable interface. This routine will @@ -53,6 +53,13 @@ class Router */ virtual ErrorCode send(MeshPacket *p); + /// Allocate and return a meshpacket which defaults as send to broadcast from the current node. + MeshPacket *allocForSending(); + + /** + * @return our local nodenum */ + NodeNum getNodeNum(); + protected: /** * Called from loop() @@ -65,10 +72,21 @@ class Router virtual void handleReceived(MeshPacket *p); /** - * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to + * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to * update routing tables etc... based on what we overhear (even for messages not destined to our node) */ virtual void sniffReceived(MeshPacket *p); + + /** + * Remove any encryption and decode the protobufs inside this packet (if necessary). + * + * @return true for success, false for corrupt packet. + */ + bool perhapsDecode(MeshPacket *p); }; -extern Router &router; \ No newline at end of file +extern Router &router; + +/// Generate a unique packet id +// FIXME, move this someplace better +PacketId generatePacketId(); \ No newline at end of file