From bda6a245923dba11363f7622a177d1ae904809a0 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 4 Feb 2024 09:46:47 -0600 Subject: [PATCH] WIP ATAK plugin message handling --- protobufs | 2 +- src/mesh/MeshModule.cpp | 5 +- src/mesh/MeshModule.h | 27 +++---- src/mesh/ProtobufModule.h | 26 +++++++ src/mesh/generated/meshtastic/config.pb.h | 2 +- src/mesh/generated/meshtastic/mesh.pb.c | 6 ++ src/mesh/generated/meshtastic/mesh.pb.h | 65 +++++++++++++++++ src/mesh/generated/meshtastic/portnums.pb.h | 3 + src/modules/AtakPluginModule.cpp | 79 +++++++++++++++++++++ src/modules/AtakPluginModule.h | 22 ++++++ src/modules/Modules.cpp | 3 +- 11 files changed, 218 insertions(+), 22 deletions(-) create mode 100644 src/modules/AtakPluginModule.cpp create mode 100644 src/modules/AtakPluginModule.h diff --git a/protobufs b/protobufs index b508d2e7ce..a5ecd23edd 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit b508d2e7ced34c752533eb02786e37402cc5a184 +Subproject commit a5ecd23eddb74bd53bd8c147c0dd98d4645c70d2 diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index a1e719721c..9c6ca78ee2 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -67,7 +67,7 @@ meshtastic_MeshPacket *MeshModule::allocErrorResponse(meshtastic_Routing_Error e return r; } -void MeshModule::callPlugins(const meshtastic_MeshPacket &mp, RxSource src) +void MeshModule::callPlugins(meshtastic_MeshPacket &mp, RxSource src) { // LOG_DEBUG("In call modules\n"); bool moduleFound = false; @@ -124,9 +124,10 @@ void MeshModule::callPlugins(const meshtastic_MeshPacket &mp, RxSource src) } else printPacket("packet on wrong channel, but can't respond", &mp); } else { - ProcessMessage handled = pi.handleReceived(mp); + pi.alterReceived(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 module send a reply, once that happens, remaining modules are not // considered diff --git a/src/mesh/MeshModule.h b/src/mesh/MeshModule.h index 323cc85959..ebe3af1a0b 100644 --- a/src/mesh/MeshModule.h +++ b/src/mesh/MeshModule.h @@ -64,7 +64,7 @@ class MeshModule /** For use only by MeshService */ - static void callPlugins(const meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO); + static void callPlugins(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO); static std::vector GetMeshModulesWithUIFrames(); static void observeUIEvents(Observer *observer); @@ -72,10 +72,7 @@ class MeshModule meshtastic_AdminMessage *request, meshtastic_AdminMessage *response); #if HAS_SCREEN - virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - return; - } + virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { return; } #endif protected: const char *name; @@ -135,10 +132,12 @@ class MeshModule @return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for it */ - virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) - { - return ProcessMessage::CONTINUE; - } + virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) { return ProcessMessage::CONTINUE; } + + /** Called to change a particular incoming message + This allows the module to change the message before it is passed through the rest of the call-chain. + */ + virtual void alterReceived(meshtastic_MeshPacket &mp) {} /** 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. @@ -151,14 +150,8 @@ class MeshModule /*** * @return true if you want to be alloced a UI screen frame */ - virtual bool wantUIFrame() - { - return false; - } - virtual Observable *getUIFrameObservable() - { - return NULL; - } + virtual bool wantUIFrame() { return false; } + virtual Observable *getUIFrameObservable() { return NULL; } meshtastic_MeshPacket *allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex); diff --git a/src/mesh/ProtobufModule.h b/src/mesh/ProtobufModule.h index 1771f4322f..d87bb47c34 100644 --- a/src/mesh/ProtobufModule.h +++ b/src/mesh/ProtobufModule.h @@ -30,6 +30,10 @@ template class ProtobufModule : protected SinglePortModule */ virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, T *decoded) = 0; + /** Called to make changes to a particular incoming message + */ + virtual void alterReceivedProtobuf(meshtastic_MeshPacket &mp, T *decoded){}; + /** * Return a mesh packet which has been preinited with a particular protobuf data payload and port number. * You can then send this packet (after customizing any of the payload fields you might need) with @@ -86,4 +90,26 @@ template class ProtobufModule : protected SinglePortModule return handleReceivedProtobuf(mp, decoded) ? ProcessMessage::STOP : ProcessMessage::CONTINUE; } + + /** Called to alter a particular incoming message + */ + virtual void alterReceived(meshtastic_MeshPacket &mp) override + { + auto &p = mp.decoded; + + T scratch; + T *decoded = NULL; + if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.decoded.portnum == ourPortNum) { + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, fields, &scratch)) { + decoded = &scratch; + } else { + LOG_ERROR("Error decoding protobuf module!\n"); + // if we can't decode it, nobody can process it! + return; + } + } + + return alterReceivedProtobuf(mp, decoded); + } }; \ No newline at end of file diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 1f1ff6a749..b06e9a7072 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -836,4 +836,4 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; } /* extern "C" */ #endif -#endif \ No newline at end of file +#endif diff --git a/src/mesh/generated/meshtastic/mesh.pb.c b/src/mesh/generated/meshtastic/mesh.pb.c index 790f8be2df..6cab2140ef 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.c +++ b/src/mesh/generated/meshtastic/mesh.pb.c @@ -60,6 +60,12 @@ PB_BIND(meshtastic_Neighbor, meshtastic_Neighbor, AUTO) PB_BIND(meshtastic_DeviceMetadata, meshtastic_DeviceMetadata, AUTO) +PB_BIND(meshtastic_TAK_Packet, meshtastic_TAK_Packet, AUTO) + + +PB_BIND(meshtastic_TAK_PLI, meshtastic_TAK_PLI, AUTO) + + diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 57054a74e1..aee9cfd7ea 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -796,6 +796,36 @@ typedef struct _meshtastic_FromRadio { }; } meshtastic_FromRadio; +/* Position Location Information from ATAK */ +typedef struct _meshtastic_TAK_PLI { + /* The new preferred location encoding, multiply by 1e-7 to get degrees + in floating point */ + int32_t latitude_i; + /* The new preferred location encoding, multiply by 1e-7 to get degrees + in floating point */ + int32_t longitude_i; + /* Altitude */ + int32_t altitude; +} meshtastic_TAK_PLI; + +/* Packets for the official ATAK Plugin */ +typedef struct _meshtastic_TAK_Packet { + pb_size_t which_callsign_variant; + union { + /* Uncompressed callsign from ATAK */ + char callsign_uncompressed[64]; + /* Compressed callsign using unishox2 for the wire */ + char callsign_compressed[64]; + } callsign_variant; + pb_size_t which_payload_variant; + union { + /* TAK position report */ + meshtastic_TAK_PLI tak_pli; + /* Other binary data */ + pb_callback_t data; + } payload_variant; +} meshtastic_TAK_Packet; + #ifdef __cplusplus extern "C" { @@ -869,6 +899,8 @@ extern "C" { #define meshtastic_DeviceMetadata_hw_model_ENUMTYPE meshtastic_HardwareModel + + /* Initializer values for message structs */ #define meshtastic_Position_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_User_init_default {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN} @@ -888,6 +920,8 @@ extern "C" { #define meshtastic_NeighborInfo_init_default {0, 0, 0, 0, {meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default}} #define meshtastic_Neighbor_init_default {0, 0, 0, 0} #define meshtastic_DeviceMetadata_init_default {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0} +#define meshtastic_TAK_Packet_init_default {0, {""}, 0, {meshtastic_TAK_PLI_init_default}} +#define meshtastic_TAK_PLI_init_default {0, 0, 0} #define meshtastic_Position_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_User_init_zero {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN} #define meshtastic_RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}} @@ -906,6 +940,8 @@ extern "C" { #define meshtastic_NeighborInfo_init_zero {0, 0, 0, 0, {meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero}} #define meshtastic_Neighbor_init_zero {0, 0, 0, 0} #define meshtastic_DeviceMetadata_init_zero {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0} +#define meshtastic_TAK_Packet_init_zero {0, {""}, 0, {meshtastic_TAK_PLI_init_zero}} +#define meshtastic_TAK_PLI_init_zero {0, 0, 0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_Position_latitude_i_tag 1 @@ -1032,6 +1068,13 @@ extern "C" { #define meshtastic_FromRadio_xmodemPacket_tag 12 #define meshtastic_FromRadio_metadata_tag 13 #define meshtastic_FromRadio_mqttClientProxyMessage_tag 14 +#define meshtastic_TAK_PLI_latitude_i_tag 1 +#define meshtastic_TAK_PLI_longitude_i_tag 2 +#define meshtastic_TAK_PLI_altitude_tag 3 +#define meshtastic_TAK_Packet_callsign_uncompressed_tag 1 +#define meshtastic_TAK_Packet_callsign_compressed_tag 2 +#define meshtastic_TAK_Packet_tak_pli_tag 3 +#define meshtastic_TAK_Packet_data_tag 4 /* Struct field encoding specification for nanopb */ #define meshtastic_Position_FIELDLIST(X, a) \ @@ -1251,6 +1294,22 @@ X(a, STATIC, SINGULAR, BOOL, hasRemoteHardware, 10) #define meshtastic_DeviceMetadata_CALLBACK NULL #define meshtastic_DeviceMetadata_DEFAULT NULL +#define meshtastic_TAK_Packet_FIELDLIST(X, a) \ +X(a, STATIC, ONEOF, STRING, (callsign_variant,callsign_uncompressed,callsign_variant.callsign_uncompressed), 1) \ +X(a, STATIC, ONEOF, STRING, (callsign_variant,callsign_compressed,callsign_variant.callsign_compressed), 2) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,tak_pli,payload_variant.tak_pli), 3) \ +X(a, CALLBACK, ONEOF, BYTES, (payload_variant,data,payload_variant.data), 4) +#define meshtastic_TAK_Packet_CALLBACK pb_default_field_callback +#define meshtastic_TAK_Packet_DEFAULT NULL +#define meshtastic_TAK_Packet_payload_variant_tak_pli_MSGTYPE meshtastic_TAK_PLI + +#define meshtastic_TAK_PLI_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 1) \ +X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 2) \ +X(a, STATIC, SINGULAR, INT32, altitude, 3) +#define meshtastic_TAK_PLI_CALLBACK NULL +#define meshtastic_TAK_PLI_DEFAULT NULL + extern const pb_msgdesc_t meshtastic_Position_msg; extern const pb_msgdesc_t meshtastic_User_msg; extern const pb_msgdesc_t meshtastic_RouteDiscovery_msg; @@ -1269,6 +1328,8 @@ extern const pb_msgdesc_t meshtastic_Compressed_msg; extern const pb_msgdesc_t meshtastic_NeighborInfo_msg; extern const pb_msgdesc_t meshtastic_Neighbor_msg; extern const pb_msgdesc_t meshtastic_DeviceMetadata_msg; +extern const pb_msgdesc_t meshtastic_TAK_Packet_msg; +extern const pb_msgdesc_t meshtastic_TAK_PLI_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_Position_fields &meshtastic_Position_msg @@ -1289,8 +1350,11 @@ extern const pb_msgdesc_t meshtastic_DeviceMetadata_msg; #define meshtastic_NeighborInfo_fields &meshtastic_NeighborInfo_msg #define meshtastic_Neighbor_fields &meshtastic_Neighbor_msg #define meshtastic_DeviceMetadata_fields &meshtastic_DeviceMetadata_msg +#define meshtastic_TAK_Packet_fields &meshtastic_TAK_Packet_msg +#define meshtastic_TAK_PLI_fields &meshtastic_TAK_PLI_msg /* Maximum encoded size of messages (where known) */ +/* meshtastic_TAK_Packet_size depends on runtime parameters */ #define meshtastic_Compressed_size 243 #define meshtastic_Data_size 270 #define meshtastic_DeviceMetadata_size 46 @@ -1306,6 +1370,7 @@ extern const pb_msgdesc_t meshtastic_DeviceMetadata_msg; #define meshtastic_QueueStatus_size 23 #define meshtastic_RouteDiscovery_size 40 #define meshtastic_Routing_size 42 +#define meshtastic_TAK_PLI_size 21 #define meshtastic_ToRadio_size 504 #define meshtastic_User_size 79 #define meshtastic_Waypoint_size 165 diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index 4fad85b863..88342e5dcc 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -119,6 +119,9 @@ typedef enum _meshtastic_PortNum { /* Aggregates edge info for the network by sending out a list of each node's neighbors ENCODING: Protobuf */ meshtastic_PortNum_NEIGHBORINFO_APP = 71, + /* ATAK Plugin + Portnum for payloads from the official Meshtastic ATAK plugin */ + meshtastic_PortNum_ATAK_PLUGIN = 72, /* Private applications should use portnums >= 256. To simplify initial development and testing you can use "PRIVATE_APP" in your code without needing to rebuild protobuf files (via [regen-protos.sh](https://github.com/meshtastic/firmware/blob/master/bin/regen-protos.sh)) */ diff --git a/src/modules/AtakPluginModule.cpp b/src/modules/AtakPluginModule.cpp new file mode 100644 index 0000000000..43534cd167 --- /dev/null +++ b/src/modules/AtakPluginModule.cpp @@ -0,0 +1,79 @@ +#include "AtakPluginModule.h" +#include "MeshService.h" +#include "NodeDB.h" +#include "PowerFSM.h" +#include "configuration.h" +#include "main.h" + +extern "C" { +#include "mesh/compression/unishox2.h" +} + +AtakPluginModule *atakPluginModule; + +AtakPluginModule::AtakPluginModule() + : ProtobufModule("atak", meshtastic_PortNum_ATAK_PLUGIN, &meshtastic_TAK_Packet_msg), + concurrency::OSThread("AtakPluginModule") +{ + ourPortNum = meshtastic_PortNum_ATAK_PLUGIN; +} + +/* +Encompasses the full construction and sending packet to mesh +Will be used for broadcast. +*/ +int32_t AtakPluginModule::runOnce() +{ + return default_broadcast_interval_secs; +} + +bool AtakPluginModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_TAK_Packet *r) +{ + return false; +} + +void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_TAK_Packet *t) +{ + // Not PLI, ignore it + if (t->which_payload_variant != meshtastic_TAK_Packet_tak_pli_tag) + return; + + // From Phone (EUD) + if (mp.from == 0) { + LOG_DEBUG("Received uncompressed TAK payload from phone with %d bytes\n", mp.decoded.payload.size); + // Compress for LoRA transport + meshtastic_TAK_Packet compressed = meshtastic_TAK_Packet_init_default; + compressed.which_callsign_variant = meshtastic_TAK_Packet_callsign_compressed_tag; + compressed.which_payload_variant = meshtastic_TAK_Packet_tak_pli_tag; + compressed.payload_variant.tak_pli = t->payload_variant.tak_pli; + auto length = + unishox2_compress_simple(t->callsign_variant.callsign_uncompressed, sizeof(t->callsign_variant.callsign_uncompressed), + compressed.callsign_variant.callsign_compressed); + LOG_DEBUG("Unompressed callsign %d bytes\n", strlen(t->callsign_variant.callsign_uncompressed)); + LOG_DEBUG("Compressed callsign %d bytes\n", length); + mp.decoded.payload.size = pb_encode_to_bytes(mp.decoded.payload.bytes, sizeof(mp.decoded.payload.bytes), + meshtastic_TAK_Packet_fields, &compressed); + LOG_DEBUG("Final payload size of %d bytes\n", mp.decoded.payload.size); + } else { + if (t->which_callsign_variant != meshtastic_TAK_Packet_callsign_compressed_tag) { + // Not compressed. Something + LOG_ERROR("Received uncompressed TAK_Packet atak plugin msg!\n"); + return; + } + // From another node on the mesh + // Decompress for Phone (EUD) + auto decompressedCopy = packetPool.allocCopy(mp); + meshtastic_TAK_Packet uncompressed = meshtastic_TAK_Packet_init_default; + uncompressed.which_callsign_variant = meshtastic_TAK_Packet_callsign_uncompressed_tag; + uncompressed.which_payload_variant = meshtastic_TAK_Packet_tak_pli_tag; + uncompressed.payload_variant.tak_pli = t->payload_variant.tak_pli; + auto length = unishox2_decompress_simple(t->callsign_variant.callsign_compressed, decompressedCopy->decoded.payload.size, + uncompressed.callsign_variant.callsign_uncompressed); + decompressedCopy->decoded.payload.size = + pb_encode_to_bytes(decompressedCopy->decoded.payload.bytes, sizeof(decompressedCopy->decoded.payload), + meshtastic_TAK_Packet_fields, &uncompressed); + service.sendToPhone(decompressedCopy); + delete decompressedCopy; + } + return; +} \ No newline at end of file diff --git a/src/modules/AtakPluginModule.h b/src/modules/AtakPluginModule.h new file mode 100644 index 0000000000..4e317b67f3 --- /dev/null +++ b/src/modules/AtakPluginModule.h @@ -0,0 +1,22 @@ +#pragma once +#include "ProtobufModule.h" + +/** + * Waypoint message handling for meshtastic + */ +class AtakPluginModule : public ProtobufModule, private concurrency::OSThread +{ + public: + /** Constructor + * name is for debugging output + */ + AtakPluginModule(); + + protected: + virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_TAK_Packet *t) override; + virtual void alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_TAK_Packet *t) override; + /* Does our periodic broadcast */ + int32_t runOnce() override; +}; + +extern AtakPluginModule *atakPluginModule; \ No newline at end of file diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 37c7576f6e..fd109b765d 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -6,6 +6,7 @@ #include "input/cardKbI2cImpl.h" #include "input/kbMatrixImpl.h" #include "modules/AdminModule.h" +#include "modules/AtakPluginModule.h" #include "modules/CannedMessageModule.h" #include "modules/DetectionSensorModule.h" #include "modules/NeighborInfoModule.h" @@ -61,7 +62,7 @@ void setupModules() traceRouteModule = new TraceRouteModule(); neighborInfoModule = new NeighborInfoModule(); detectionSensorModule = new DetectionSensorModule(); - + atakPluginModule = new AtakPluginModule(); // Note: if the rest of meshtastic doesn't need to explicitly use your module, you do not need to assign the instance // to a global variable.