From ee9c72b8c787ee1cc964265a6e1dcfcbd629fe13 Mon Sep 17 00:00:00 2001 From: a-f-G-U-C <65810997+a-f-G-U-C@users.noreply.github.com> Date: Sat, 9 Oct 2021 13:48:30 +0000 Subject: [PATCH 1/6] issue 879 - define source types --- src/mesh/MeshTypes.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index 34d4f02c9..d45194d4f 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -15,6 +15,14 @@ typedef uint32_t PacketId; // A packet sequence number #define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER #define ERRNO_DISABLED 34 // the itnerface is disabled +/* + * Source of a received message + */ +enum RxSource { + RX_SRC_LOCAL, // message was generated locally + RX_SRC_RADIO // message was received from radio mesh +}; + /** * the max number of hops a message can pass through, used as the default max for hop_limit in MeshPacket. * From 5eb2e6401f324f967922e6453be68eb6dfe88926 Mon Sep 17 00:00:00 2001 From: a-f-G-U-C <65810997+a-f-G-U-C@users.noreply.github.com> Date: Sat, 9 Oct 2021 13:54:42 +0000 Subject: [PATCH 2/6] issue 879 - changes to Router.cpp --- src/mesh/Router.cpp | 11 +++++++---- src/mesh/Router.h | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 1c5d70101..3cf81be2f 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -161,7 +161,7 @@ ErrorCode Router::sendLocal(MeshPacket *p) // If we are sending a broadcast, we also treat it as if we just received it ourself // this allows local apps (and PCs) to see broadcasts sourced locally if (p->to == NODENUM_BROADCAST) { - handleReceived(p); + handleReceived(p, RX_SRC_LOCAL); } return send(p); @@ -324,7 +324,7 @@ NodeNum Router::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) +void Router::handleReceived(MeshPacket *p, RxSource src) { // Also, we should set the time from the ISR and it should have msec level resolution p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone @@ -333,13 +333,16 @@ void Router::handleReceived(MeshPacket *p) bool decoded = perhapsDecode(p); if (decoded) { // parsing was successful, queue for our recipient - printPacket("handleReceived", p); + if (src == RX_SRC_LOCAL) + printPacket("handleReceived(local)", p); + else + printPacket("handleReceived(remote)", p); } else { printPacket("packet decoding failed (no PSK?)", p); } // call plugins here - MeshPlugin::callPlugins(*p); + MeshPlugin::callPlugins(*p, src); } void Router::perhapsHandleReceived(MeshPacket *p) diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 9d7358af5..519f158f7 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -122,7 +122,7 @@ class Router : protected concurrency::OSThread * Note: this packet will never be called for messages sent/generated by this node. * Note: this method will free the provided packet. */ - void handleReceived(MeshPacket *p); + void handleReceived(MeshPacket *p, RxSource src = RX_SRC_RADIO); /** Frees the provided packet, and generates a NAK indicating the speicifed error while sending */ void abortSendAndNak(Routing_Error err, MeshPacket *p); From c3ebe80f53f9d1f76ee3bd314fbcb48f79ffc2ab Mon Sep 17 00:00:00 2001 From: a-f-G-U-C <65810997+a-f-G-U-C@users.noreply.github.com> Date: Sat, 9 Oct 2021 13:57:56 +0000 Subject: [PATCH 3/6] issue 879 - add opt-in flag for plugins --- MeshPlugin.cpp | 234 +++++++++++++++++++++++++++++++++++++++++++++++++ MeshPlugin.h | 135 ++++++++++++++++++++++++++++ 2 files changed, 369 insertions(+) create mode 100644 MeshPlugin.cpp create mode 100644 MeshPlugin.h diff --git a/MeshPlugin.cpp b/MeshPlugin.cpp new file mode 100644 index 000000000..4ebd2f4d7 --- /dev/null +++ b/MeshPlugin.cpp @@ -0,0 +1,234 @@ +#include "configuration.h" +#include "MeshPlugin.h" +#include "Channels.h" +#include "MeshService.h" +#include "NodeDB.h" +#include "plugins/RoutingPlugin.h" +#include + +std::vector *MeshPlugin::plugins; + +const MeshPacket *MeshPlugin::currentRequest; + +/** + * If any of the current chain of plugins has already sent a reply, it will be here. This is useful to allow + * the RoutingPlugin to avoid sending redundant acks + */ +MeshPacket *MeshPlugin::currentReply; + +MeshPlugin::MeshPlugin(const char *_name) : name(_name) +{ + // Can't trust static initalizer order, so we check each time + if (!plugins) + plugins = new std::vector(); + + plugins->push_back(this); +} + +void MeshPlugin::setup() {} + +MeshPlugin::~MeshPlugin() +{ + assert(0); // FIXME - remove from list of plugins once someone needs this feature +} + +MeshPacket *MeshPlugin::allocAckNak(Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex) +{ + Routing c = Routing_init_default; + + c.error_reason = err; + c.which_variant = Routing_error_reason_tag; + + // Now that we have moded sendAckNak up one level into the class heirarchy we can no longer assume we are a RoutingPlugin + // So we manually call pb_encode_to_bytes and specify routing port number + // auto p = allocDataProtobuf(c); + MeshPacket *p = router->allocForSending(); + p->decoded.portnum = PortNum_ROUTING_APP; + p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), Routing_fields, &c); + + p->priority = MeshPacket_Priority_ACK; + + p->hop_limit = 0; // Assume just immediate neighbors for now + p->to = to; + p->decoded.request_id = idFrom; + p->channel = chIndex; + DEBUG_MSG("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id); + + return p; +} + +MeshPacket *MeshPlugin::allocErrorResponse(Routing_Error err, const MeshPacket *p) +{ + auto r = allocAckNak(err, getFrom(p), p->id, p->channel); + + setReplyTo(r, *p); + + return r; +} + +void MeshPlugin::callPlugins(const MeshPacket &mp, RxSource src) +{ + // DEBUG_MSG("In call plugins\n"); + bool pluginFound = false; + + // We now allow **encrypted** packets to pass through the plugins + bool isDecoded = mp.which_payloadVariant == MeshPacket_decoded_tag; + + currentReply = NULL; // No reply yet + + // Was this message directed to us specifically? Will be false if we are sniffing someone elses packets + auto ourNodeNum = nodeDB.getNodeNum(); + bool toUs = mp.to == NODENUM_BROADCAST || mp.to == ourNodeNum; + + for (auto i = plugins->begin(); i != plugins->end(); ++i) { + auto &pi = **i; + + pi.currentRequest = ∓ + + /// We only call plugins that are interested in the packet (and the message is destined to us or we are promiscious) + bool wantsPacket = (isDecoded || pi.encryptedOk) && (pi.isPromiscuous || toUs) && pi.wantPacket(&mp); + + if ((src == RX_SRC_LOCAL) && !(pi.loopbackOk)) { + // new case, monitor separately for now, then FIXME merge above + wantsPacket = false; + } + + assert(!pi.myReply); // If it is !null it means we have a bug, because it should have been sent the previous time + + if (wantsPacket) { + DEBUG_MSG("Plugin %s wantsPacket=%d\n", pi.name, wantsPacket); + + pluginFound = true; + + /// received channel (or NULL if not decoded) + Channel *ch = isDecoded ? &channels.getByIndex(mp.channel) : NULL; + + /// Is the channel this packet arrived on acceptable? (security check) + /// Note: we can't know channel names for encrypted packets, so those are NEVER sent to boundChannel plugins + + /// Also: if a packet comes in on the local PC interface, we don't check for bound channels, because it is TRUSTED and it needs to + /// to be able to fetch the initial admin packets without yet knowing any channels. + + bool rxChannelOk = !pi.boundChannel || (mp.from == 0) || (ch && (strcmp(ch->settings.name, pi.boundChannel) == 0)); + + if (!rxChannelOk) { + // no one should have already replied! + assert(!currentReply); + + if (mp.decoded.want_response) { + printPacket("packet on wrong channel, returning error", &mp); + currentReply = pi.allocErrorResponse(Routing_Error_NOT_AUTHORIZED, &mp); + } else + printPacket("packet on wrong channel, but can't respond", &mp); + } else { + + bool handled = pi.handleReceived(mp); + + // Possibly send replies (but only if the message was directed to us specifically, i.e. not for promiscious + // sniffing) also: we only let the one plugin send a reply, once that happens, remaining plugins are not + // considered + + // NOTE: we send a reply *even if the (non broadcast) request was from us* which is unfortunate but necessary + // because currently when the phone sends things, it sends things using the local node ID as the from address. A + // better solution (FIXME) would be to let phones have their own distinct addresses and we 'route' to them like + // any other node. + if (mp.decoded.want_response && toUs && (getFrom(&mp) != ourNodeNum || mp.to == ourNodeNum) && !currentReply) { + pi.sendResponse(mp); + DEBUG_MSG("Plugin %s sent a response\n", pi.name); + } else { + DEBUG_MSG("Plugin %s considered\n", pi.name); + } + + // If the requester didn't ask for a response we might need to discard unused replies to prevent memory leaks + if (pi.myReply) { + DEBUG_MSG("Discarding an unneeded response\n"); + packetPool.release(pi.myReply); + pi.myReply = NULL; + } + + if (handled) { + DEBUG_MSG("Plugin %s handled and skipped other processing\n", pi.name); + break; + } + } + } + + pi.currentRequest = NULL; + } + + if (mp.decoded.want_response && toUs) { + if (currentReply) { + printPacket("Sending response", currentReply); + service.sendToMesh(currentReply); + currentReply = NULL; + } else if(mp.from != ourNodeNum) { + // Note: if the message started with the local node we don't want to send a no response reply + + // No one wanted to reply to this requst, tell the requster that happened + DEBUG_MSG("No one responded, send a nak\n"); + + // SECURITY NOTE! I considered sending back a different error code if we didn't find the psk (i.e. !isDecoded) + // but opted NOT TO. Because it is not a good idea to let remote nodes 'probe' to find out which PSKs were "good" vs + // bad. + routingPlugin->sendAckNak(Routing_Error_NO_RESPONSE, getFrom(&mp), mp.id, mp.channel); + } + } + + if (!pluginFound) + DEBUG_MSG("No plugins interested in portnum=%d, src=%s\n", + mp.decoded.portnum, + (src == RX_SRC_LOCAL) ? "LOCAL":"REMOTE"); +} + +MeshPacket *MeshPlugin::allocReply() +{ + auto r = myReply; + myReply = NULL; // Only use each reply once + return r; +} + +/** Messages can be received that have the want_response bit set. If set, this callback will be invoked + * so that subclasses can (optionally) send a response back to the original sender. Implementing this method + * is optional + */ +void MeshPlugin::sendResponse(const MeshPacket &req) +{ + auto r = allocReply(); + if (r) { + setReplyTo(r, req); + currentReply = r; + } else { + // Ignore - this is now expected behavior for routing plugin (because it ignores some replies) + // DEBUG_MSG("WARNING: Client requested response but this plugin did not provide\n"); + } +} + +/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet + * This ensures that if the request packet was sent reliably, the reply is sent that way as well. + */ +void setReplyTo(MeshPacket *p, const MeshPacket &to) +{ + assert(p->which_payloadVariant == MeshPacket_decoded_tag); // Should already be set by now + p->to = getFrom(&to); // Make sure that if we are sending to the local node, we use our local node addr, not 0 + p->channel = to.channel; // Use the same channel that the request came in on + + // No need for an ack if we are just delivering locally (it just generates an ignored ack) + p->want_ack = (to.from != 0) ? to.want_ack : false; + if (p->priority == MeshPacket_Priority_UNSET) + p->priority = MeshPacket_Priority_RELIABLE; + p->decoded.request_id = to.id; +} + +std::vector MeshPlugin::GetMeshPluginsWithUIFrames() +{ + + std::vector pluginsWithUIFrames; + for (auto i = plugins->begin(); i != plugins->end(); ++i) { + auto &pi = **i; + if (pi.wantUIFrame()) { + DEBUG_MSG("Plugin wants a UI Frame\n"); + pluginsWithUIFrames.push_back(&pi); + } + } + return pluginsWithUIFrames; +} diff --git a/MeshPlugin.h b/MeshPlugin.h new file mode 100644 index 000000000..2350c951a --- /dev/null +++ b/MeshPlugin.h @@ -0,0 +1,135 @@ +#pragma once + +#include "mesh/Channels.h" +#include "mesh/MeshTypes.h" +#include + +#ifndef NO_SCREEN +#include +#include +#endif + +/** A baseclass for any mesh "plugin". + * + * A plugin allows you to add new features to meshtastic device code, without needing to know messaging details. + * + * A key concept for this is that your plugin should use a particular "portnum" for each message type you want to receive + * and handle. + * + * Interally we use plugins to implement the core meshtastic text messaging and gps position sharing features. You + * can use these classes as examples for how to write your own custom plugin. See here: (FIXME) + */ +class MeshPlugin +{ + static std::vector *plugins; + + public: + /** Constructor + * name is for debugging output + */ + MeshPlugin(const char *_name); + + virtual ~MeshPlugin(); + + /** For use only by MeshService + */ + static void callPlugins(const MeshPacket &mp, RxSource src = RX_SRC_RADIO); + + static std::vector GetMeshPluginsWithUIFrames(); +#ifndef NO_SCREEN + virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { return; } +#endif + protected: + const char *name; + + /** Most plugins only care about packets that are destined for their node (i.e. broadcasts or has their node as the specific + recipient) But some plugs might want to 'sniff' packets that are merely being routed (passing through the current node). Those + plugins can set this to true and their handleReceived() will be called for every packet. + */ + bool isPromiscuous = false; + + /** Also receive a copy of LOCALLY GENERATED messages - most plugins should leave + * this setting disabled - see issue #877 */ + bool loopbackOk = false; + + /** Most plugins only understand decrypted packets. For plugins that also want to see encrypted packets, they should set this + * flag */ + bool encryptedOk = false; + + /** If a bound channel name is set, we will only accept received packets that come in on that channel. + * A special exception (FIXME, not sure if this is a good idea) - packets that arrive on the local interface + * are allowed on any channel (this lets the local user do anything). + * + * We will send responses on the same channel that the request arrived on. + */ + const char *boundChannel = NULL; + + /** + * If this plugin is currently handling a request currentRequest will be preset + * to the packet with the request. This is mostly useful for reply handlers. + * + * Note: this can be static because we are guaranteed to be processing only one + * plugin at a time. + */ + static const MeshPacket *currentRequest; + + /** + * If your handler wants to send a response, simply set currentReply and it will be sent at the end of response handling. + */ + MeshPacket *myReply = NULL; + + /** + * Initialize your plugin. This setup function is called once after all hardware and mesh protocol layers have + * been initialized + */ + virtual void setup(); + + /** + * @return true if you want to receive the specified portnum + */ + virtual bool wantPacket(const MeshPacket *p) = 0; + + /** Called to handle a particular incoming message + + @return true if you've guaranteed you've handled this message and no other handlers should be considered for it + */ + virtual bool handleReceived(const MeshPacket &mp) { return false; } + + /** Messages can be received that have the want_response bit set. If set, this callback will be invoked + * so that subclasses can (optionally) send a response back to the original sender. + * + * Note: most implementers don't need to override this, instead: If while handling a request you have a reply, just set + * the protected reply field in this instance. + * */ + virtual MeshPacket *allocReply(); + + /*** + * @return true if you want to be alloced a UI screen frame + */ + virtual bool wantUIFrame() { return false; } + + MeshPacket *allocAckNak(Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex); + + /// Send an error response for the specified packet. + MeshPacket *allocErrorResponse(Routing_Error err, const MeshPacket *p); + + private: + /** + * If any of the current chain of plugins has already sent a reply, it will be here. This is useful to allow + * the RoutingPlugin to avoid sending redundant acks + */ + static MeshPacket *currentReply; + + friend class ReliableRouter; + + /** Messages can be received that have the want_response bit set. If set, this callback will be invoked + * so that subclasses can (optionally) send a response back to the original sender. This method calls allocReply() + * to generate the reply message, and if !NULL that message will be delivered to whoever sent req + */ + void sendResponse(const MeshPacket &req); +}; + +/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet + * This ensures that if the request packet was sent reliably, the reply is sent that way as well. + */ +void setReplyTo(MeshPacket *p, const MeshPacket &to); \ No newline at end of file From 26330120ce852202fa048c97743eab096d172afe Mon Sep 17 00:00:00 2001 From: a-f-G-U-C <65810997+a-f-G-U-C@users.noreply.github.com> Date: Sat, 9 Oct 2021 13:59:54 +0000 Subject: [PATCH 4/6] oops --- MeshPlugin.cpp | 234 ------------------------------------------------- 1 file changed, 234 deletions(-) delete mode 100644 MeshPlugin.cpp diff --git a/MeshPlugin.cpp b/MeshPlugin.cpp deleted file mode 100644 index 4ebd2f4d7..000000000 --- a/MeshPlugin.cpp +++ /dev/null @@ -1,234 +0,0 @@ -#include "configuration.h" -#include "MeshPlugin.h" -#include "Channels.h" -#include "MeshService.h" -#include "NodeDB.h" -#include "plugins/RoutingPlugin.h" -#include - -std::vector *MeshPlugin::plugins; - -const MeshPacket *MeshPlugin::currentRequest; - -/** - * If any of the current chain of plugins has already sent a reply, it will be here. This is useful to allow - * the RoutingPlugin to avoid sending redundant acks - */ -MeshPacket *MeshPlugin::currentReply; - -MeshPlugin::MeshPlugin(const char *_name) : name(_name) -{ - // Can't trust static initalizer order, so we check each time - if (!plugins) - plugins = new std::vector(); - - plugins->push_back(this); -} - -void MeshPlugin::setup() {} - -MeshPlugin::~MeshPlugin() -{ - assert(0); // FIXME - remove from list of plugins once someone needs this feature -} - -MeshPacket *MeshPlugin::allocAckNak(Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex) -{ - Routing c = Routing_init_default; - - c.error_reason = err; - c.which_variant = Routing_error_reason_tag; - - // Now that we have moded sendAckNak up one level into the class heirarchy we can no longer assume we are a RoutingPlugin - // So we manually call pb_encode_to_bytes and specify routing port number - // auto p = allocDataProtobuf(c); - MeshPacket *p = router->allocForSending(); - p->decoded.portnum = PortNum_ROUTING_APP; - p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), Routing_fields, &c); - - p->priority = MeshPacket_Priority_ACK; - - p->hop_limit = 0; // Assume just immediate neighbors for now - p->to = to; - p->decoded.request_id = idFrom; - p->channel = chIndex; - DEBUG_MSG("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id); - - return p; -} - -MeshPacket *MeshPlugin::allocErrorResponse(Routing_Error err, const MeshPacket *p) -{ - auto r = allocAckNak(err, getFrom(p), p->id, p->channel); - - setReplyTo(r, *p); - - return r; -} - -void MeshPlugin::callPlugins(const MeshPacket &mp, RxSource src) -{ - // DEBUG_MSG("In call plugins\n"); - bool pluginFound = false; - - // We now allow **encrypted** packets to pass through the plugins - bool isDecoded = mp.which_payloadVariant == MeshPacket_decoded_tag; - - currentReply = NULL; // No reply yet - - // Was this message directed to us specifically? Will be false if we are sniffing someone elses packets - auto ourNodeNum = nodeDB.getNodeNum(); - bool toUs = mp.to == NODENUM_BROADCAST || mp.to == ourNodeNum; - - for (auto i = plugins->begin(); i != plugins->end(); ++i) { - auto &pi = **i; - - pi.currentRequest = ∓ - - /// We only call plugins that are interested in the packet (and the message is destined to us or we are promiscious) - bool wantsPacket = (isDecoded || pi.encryptedOk) && (pi.isPromiscuous || toUs) && pi.wantPacket(&mp); - - if ((src == RX_SRC_LOCAL) && !(pi.loopbackOk)) { - // new case, monitor separately for now, then FIXME merge above - wantsPacket = false; - } - - assert(!pi.myReply); // If it is !null it means we have a bug, because it should have been sent the previous time - - if (wantsPacket) { - DEBUG_MSG("Plugin %s wantsPacket=%d\n", pi.name, wantsPacket); - - pluginFound = true; - - /// received channel (or NULL if not decoded) - Channel *ch = isDecoded ? &channels.getByIndex(mp.channel) : NULL; - - /// Is the channel this packet arrived on acceptable? (security check) - /// Note: we can't know channel names for encrypted packets, so those are NEVER sent to boundChannel plugins - - /// Also: if a packet comes in on the local PC interface, we don't check for bound channels, because it is TRUSTED and it needs to - /// to be able to fetch the initial admin packets without yet knowing any channels. - - bool rxChannelOk = !pi.boundChannel || (mp.from == 0) || (ch && (strcmp(ch->settings.name, pi.boundChannel) == 0)); - - if (!rxChannelOk) { - // no one should have already replied! - assert(!currentReply); - - if (mp.decoded.want_response) { - printPacket("packet on wrong channel, returning error", &mp); - currentReply = pi.allocErrorResponse(Routing_Error_NOT_AUTHORIZED, &mp); - } else - printPacket("packet on wrong channel, but can't respond", &mp); - } else { - - bool handled = pi.handleReceived(mp); - - // Possibly send replies (but only if the message was directed to us specifically, i.e. not for promiscious - // sniffing) also: we only let the one plugin send a reply, once that happens, remaining plugins are not - // considered - - // NOTE: we send a reply *even if the (non broadcast) request was from us* which is unfortunate but necessary - // because currently when the phone sends things, it sends things using the local node ID as the from address. A - // better solution (FIXME) would be to let phones have their own distinct addresses and we 'route' to them like - // any other node. - if (mp.decoded.want_response && toUs && (getFrom(&mp) != ourNodeNum || mp.to == ourNodeNum) && !currentReply) { - pi.sendResponse(mp); - DEBUG_MSG("Plugin %s sent a response\n", pi.name); - } else { - DEBUG_MSG("Plugin %s considered\n", pi.name); - } - - // If the requester didn't ask for a response we might need to discard unused replies to prevent memory leaks - if (pi.myReply) { - DEBUG_MSG("Discarding an unneeded response\n"); - packetPool.release(pi.myReply); - pi.myReply = NULL; - } - - if (handled) { - DEBUG_MSG("Plugin %s handled and skipped other processing\n", pi.name); - break; - } - } - } - - pi.currentRequest = NULL; - } - - if (mp.decoded.want_response && toUs) { - if (currentReply) { - printPacket("Sending response", currentReply); - service.sendToMesh(currentReply); - currentReply = NULL; - } else if(mp.from != ourNodeNum) { - // Note: if the message started with the local node we don't want to send a no response reply - - // No one wanted to reply to this requst, tell the requster that happened - DEBUG_MSG("No one responded, send a nak\n"); - - // SECURITY NOTE! I considered sending back a different error code if we didn't find the psk (i.e. !isDecoded) - // but opted NOT TO. Because it is not a good idea to let remote nodes 'probe' to find out which PSKs were "good" vs - // bad. - routingPlugin->sendAckNak(Routing_Error_NO_RESPONSE, getFrom(&mp), mp.id, mp.channel); - } - } - - if (!pluginFound) - DEBUG_MSG("No plugins interested in portnum=%d, src=%s\n", - mp.decoded.portnum, - (src == RX_SRC_LOCAL) ? "LOCAL":"REMOTE"); -} - -MeshPacket *MeshPlugin::allocReply() -{ - auto r = myReply; - myReply = NULL; // Only use each reply once - return r; -} - -/** Messages can be received that have the want_response bit set. If set, this callback will be invoked - * so that subclasses can (optionally) send a response back to the original sender. Implementing this method - * is optional - */ -void MeshPlugin::sendResponse(const MeshPacket &req) -{ - auto r = allocReply(); - if (r) { - setReplyTo(r, req); - currentReply = r; - } else { - // Ignore - this is now expected behavior for routing plugin (because it ignores some replies) - // DEBUG_MSG("WARNING: Client requested response but this plugin did not provide\n"); - } -} - -/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet - * This ensures that if the request packet was sent reliably, the reply is sent that way as well. - */ -void setReplyTo(MeshPacket *p, const MeshPacket &to) -{ - assert(p->which_payloadVariant == MeshPacket_decoded_tag); // Should already be set by now - p->to = getFrom(&to); // Make sure that if we are sending to the local node, we use our local node addr, not 0 - p->channel = to.channel; // Use the same channel that the request came in on - - // No need for an ack if we are just delivering locally (it just generates an ignored ack) - p->want_ack = (to.from != 0) ? to.want_ack : false; - if (p->priority == MeshPacket_Priority_UNSET) - p->priority = MeshPacket_Priority_RELIABLE; - p->decoded.request_id = to.id; -} - -std::vector MeshPlugin::GetMeshPluginsWithUIFrames() -{ - - std::vector pluginsWithUIFrames; - for (auto i = plugins->begin(); i != plugins->end(); ++i) { - auto &pi = **i; - if (pi.wantUIFrame()) { - DEBUG_MSG("Plugin wants a UI Frame\n"); - pluginsWithUIFrames.push_back(&pi); - } - } - return pluginsWithUIFrames; -} From 4669e4713ca7a65e1e690392da4bd10846094d32 Mon Sep 17 00:00:00 2001 From: a-f-G-U-C <65810997+a-f-G-U-C@users.noreply.github.com> Date: Sat, 9 Oct 2021 14:00:14 +0000 Subject: [PATCH 5/6] oops --- MeshPlugin.h | 135 --------------------------------------------------- 1 file changed, 135 deletions(-) delete mode 100644 MeshPlugin.h diff --git a/MeshPlugin.h b/MeshPlugin.h deleted file mode 100644 index 2350c951a..000000000 --- a/MeshPlugin.h +++ /dev/null @@ -1,135 +0,0 @@ -#pragma once - -#include "mesh/Channels.h" -#include "mesh/MeshTypes.h" -#include - -#ifndef NO_SCREEN -#include -#include -#endif - -/** A baseclass for any mesh "plugin". - * - * A plugin allows you to add new features to meshtastic device code, without needing to know messaging details. - * - * A key concept for this is that your plugin should use a particular "portnum" for each message type you want to receive - * and handle. - * - * Interally we use plugins to implement the core meshtastic text messaging and gps position sharing features. You - * can use these classes as examples for how to write your own custom plugin. See here: (FIXME) - */ -class MeshPlugin -{ - static std::vector *plugins; - - public: - /** Constructor - * name is for debugging output - */ - MeshPlugin(const char *_name); - - virtual ~MeshPlugin(); - - /** For use only by MeshService - */ - static void callPlugins(const MeshPacket &mp, RxSource src = RX_SRC_RADIO); - - static std::vector GetMeshPluginsWithUIFrames(); -#ifndef NO_SCREEN - virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { return; } -#endif - protected: - const char *name; - - /** Most plugins only care about packets that are destined for their node (i.e. broadcasts or has their node as the specific - recipient) But some plugs might want to 'sniff' packets that are merely being routed (passing through the current node). Those - plugins can set this to true and their handleReceived() will be called for every packet. - */ - bool isPromiscuous = false; - - /** Also receive a copy of LOCALLY GENERATED messages - most plugins should leave - * this setting disabled - see issue #877 */ - bool loopbackOk = false; - - /** Most plugins only understand decrypted packets. For plugins that also want to see encrypted packets, they should set this - * flag */ - bool encryptedOk = false; - - /** If a bound channel name is set, we will only accept received packets that come in on that channel. - * A special exception (FIXME, not sure if this is a good idea) - packets that arrive on the local interface - * are allowed on any channel (this lets the local user do anything). - * - * We will send responses on the same channel that the request arrived on. - */ - const char *boundChannel = NULL; - - /** - * If this plugin is currently handling a request currentRequest will be preset - * to the packet with the request. This is mostly useful for reply handlers. - * - * Note: this can be static because we are guaranteed to be processing only one - * plugin at a time. - */ - static const MeshPacket *currentRequest; - - /** - * If your handler wants to send a response, simply set currentReply and it will be sent at the end of response handling. - */ - MeshPacket *myReply = NULL; - - /** - * Initialize your plugin. This setup function is called once after all hardware and mesh protocol layers have - * been initialized - */ - virtual void setup(); - - /** - * @return true if you want to receive the specified portnum - */ - virtual bool wantPacket(const MeshPacket *p) = 0; - - /** Called to handle a particular incoming message - - @return true if you've guaranteed you've handled this message and no other handlers should be considered for it - */ - virtual bool handleReceived(const MeshPacket &mp) { return false; } - - /** Messages can be received that have the want_response bit set. If set, this callback will be invoked - * so that subclasses can (optionally) send a response back to the original sender. - * - * Note: most implementers don't need to override this, instead: If while handling a request you have a reply, just set - * the protected reply field in this instance. - * */ - virtual MeshPacket *allocReply(); - - /*** - * @return true if you want to be alloced a UI screen frame - */ - virtual bool wantUIFrame() { return false; } - - MeshPacket *allocAckNak(Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex); - - /// Send an error response for the specified packet. - MeshPacket *allocErrorResponse(Routing_Error err, const MeshPacket *p); - - private: - /** - * If any of the current chain of plugins has already sent a reply, it will be here. This is useful to allow - * the RoutingPlugin to avoid sending redundant acks - */ - static MeshPacket *currentReply; - - friend class ReliableRouter; - - /** Messages can be received that have the want_response bit set. If set, this callback will be invoked - * so that subclasses can (optionally) send a response back to the original sender. This method calls allocReply() - * to generate the reply message, and if !NULL that message will be delivered to whoever sent req - */ - void sendResponse(const MeshPacket &req); -}; - -/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet - * This ensures that if the request packet was sent reliably, the reply is sent that way as well. - */ -void setReplyTo(MeshPacket *p, const MeshPacket &to); \ No newline at end of file From 1e455ac4c39248b759c988d29a314b149e733cfd Mon Sep 17 00:00:00 2001 From: a-f-G-U-C <65810997+a-f-G-U-C@users.noreply.github.com> Date: Sat, 9 Oct 2021 14:02:21 +0000 Subject: [PATCH 6/6] issue 879 - add opt-in flag for plugins --- src/mesh/MeshPlugin.cpp | 12 ++++++++++-- src/mesh/MeshPlugin.h | 6 +++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/mesh/MeshPlugin.cpp b/src/mesh/MeshPlugin.cpp index 65cb4ce36..4ebd2f4d7 100644 --- a/src/mesh/MeshPlugin.cpp +++ b/src/mesh/MeshPlugin.cpp @@ -66,7 +66,7 @@ MeshPacket *MeshPlugin::allocErrorResponse(Routing_Error err, const MeshPacket * return r; } -void MeshPlugin::callPlugins(const MeshPacket &mp) +void MeshPlugin::callPlugins(const MeshPacket &mp, RxSource src) { // DEBUG_MSG("In call plugins\n"); bool pluginFound = false; @@ -79,6 +79,7 @@ void MeshPlugin::callPlugins(const MeshPacket &mp) // Was this message directed to us specifically? Will be false if we are sniffing someone elses packets auto ourNodeNum = nodeDB.getNodeNum(); bool toUs = mp.to == NODENUM_BROADCAST || mp.to == ourNodeNum; + for (auto i = plugins->begin(); i != plugins->end(); ++i) { auto &pi = **i; @@ -87,6 +88,11 @@ void MeshPlugin::callPlugins(const MeshPacket &mp) /// We only call plugins that are interested in the packet (and the message is destined to us or we are promiscious) bool wantsPacket = (isDecoded || pi.encryptedOk) && (pi.isPromiscuous || toUs) && pi.wantPacket(&mp); + if ((src == RX_SRC_LOCAL) && !(pi.loopbackOk)) { + // new case, monitor separately for now, then FIXME merge above + wantsPacket = false; + } + assert(!pi.myReply); // If it is !null it means we have a bug, because it should have been sent the previous time if (wantsPacket) { @@ -169,7 +175,9 @@ void MeshPlugin::callPlugins(const MeshPacket &mp) } if (!pluginFound) - DEBUG_MSG("No plugins interested in portnum=%d\n", mp.decoded.portnum); + DEBUG_MSG("No plugins interested in portnum=%d, src=%s\n", + mp.decoded.portnum, + (src == RX_SRC_LOCAL) ? "LOCAL":"REMOTE"); } MeshPacket *MeshPlugin::allocReply() diff --git a/src/mesh/MeshPlugin.h b/src/mesh/MeshPlugin.h index 937c37dfe..2350c951a 100644 --- a/src/mesh/MeshPlugin.h +++ b/src/mesh/MeshPlugin.h @@ -33,7 +33,7 @@ class MeshPlugin /** For use only by MeshService */ - static void callPlugins(const MeshPacket &mp); + static void callPlugins(const MeshPacket &mp, RxSource src = RX_SRC_RADIO); static std::vector GetMeshPluginsWithUIFrames(); #ifndef NO_SCREEN @@ -48,6 +48,10 @@ class MeshPlugin */ bool isPromiscuous = false; + /** Also receive a copy of LOCALLY GENERATED messages - most plugins should leave + * this setting disabled - see issue #877 */ + bool loopbackOk = false; + /** Most plugins only understand decrypted packets. For plugins that also want to see encrypted packets, they should set this * flag */ bool encryptedOk = false;