diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b4be8bb..4dc27c98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# Unreleased + +## Added + +### Transport + +* Added an optional packet-carrying `AnnounceHandler::received_announce(destination_hash, announced_identity, app_data, packet)` overload, giving handlers the receiving interface, hop count and signal quality of the announce. Its default forwards to the existing three-argument callback, so existing handlers are unchanged. + # microReticulum v0.5.1 ## Changed diff --git a/README.md b/README.md index e95fb424..6008206f 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,27 @@ Also note the following preprocessor directives used to tune `microStore::FileSt `-DRNS_PATH_TABLE_SEGMENT_COUNT`: Maximum number of segment files to rotate (minimum of 3) Appropriate settings should be selected to match the storage and memory resources available on the target platform. +## Announce Handlers + +Applications receive announces by registering a subclass of `RNS::AnnounceHandler` with `RNS::Transport::register_announce_handler()`. The basic callback is pure virtual and must always be implemented; override the extended one as well when the handler needs the announce packet: + +```cpp +class MyHandler : public RNS::AnnounceHandler { +public: + // Basic form: destination hash, announced identity and app data. + void received_announce(const RNS::Bytes& destination_hash, const RNS::Identity& announced_identity, const RNS::Bytes& app_data) override; + + // Extended form: additionally receives the announce packet, giving access to + // packet.receiving_interface(), packet.hops(), packet.rssi() and packet.snr(). + // The default implementation forwards to the basic form. + void received_announce(const RNS::Bytes& destination_hash, const RNS::Identity& announced_identity, const RNS::Bytes& app_data, const RNS::Packet& packet) override; +}; +``` + +The `Packet` passed to the extended form is only valid for the duration of the callback. Copy anything the handler needs to keep, such as `packet.hops()` or the receiving interface, rather than storing a reference to the packet. + +Pass an aspect filter such as `"lxmf.delivery"` to the `AnnounceHandler` constructor to receive only announces for that destination name, or `nullptr` for all announces. + ## Provisioning microReticulum ships an optional schema-driven configuration subsystem under `src/microReticulum/Provisioning/`. It maintains a registry of namespaces and fields, holds a "working" config in RAM plus a "draft" overlay for pending edits, and (when filesystem support is enabled) persists committed values atomically to flash. Apps frame and transport MsgPack request/response payloads using their preferred link (KISS, BLE GATT, Web Serial, etc.); the library handles everything from the engine inward. diff --git a/src/microReticulum/Transport.cpp b/src/microReticulum/Transport.cpp index 47475676..6a022246 100644 --- a/src/microReticulum/Transport.cpp +++ b/src/microReticulum/Transport.cpp @@ -2724,7 +2724,8 @@ TRACEF("path_announce_emitted=%lu", path_announce_emitted); handler->received_announce( packet.destination_hash(), announce_identity, - Identity::recall_app_data(packet.destination_hash()) + Identity::recall_app_data(packet.destination_hash()), + packet ); } } diff --git a/src/microReticulum/Transport.h b/src/microReticulum/Transport.h index 5d5a34e2..72625721 100644 --- a/src/microReticulum/Transport.h +++ b/src/microReticulum/Transport.h @@ -66,6 +66,17 @@ namespace RNS { // configured aspect filter. Filters must be specific, // and cannot use wildcards. virtual void received_announce(const Bytes& destination_hash, const Identity& announced_identity, const Bytes& app_data) = 0; + // Richer variant with the announce packet itself: receiving interface + // (packet.receiving_interface()), hop count (packet.hops()), signal + // stats (packet.rssi()/snr()), transport id. Defaults to the classic + // callback so existing handlers keep working; override this one to + // get the metadata. The packet is only valid for the duration of the + // call: copy anything you need to keep (e.g. packet.hops(), or the + // receiving interface) rather than holding the reference. + virtual void received_announce(const Bytes& destination_hash, const Identity& announced_identity, const Bytes& app_data, const Packet& packet) { + (void)packet; + received_announce(destination_hash, announced_identity, app_data); + } std::string& aspect_filter() { return _aspect_filter; } private: std::string _aspect_filter; diff --git a/test/test_rns_loopback/test_rns_loopback.cpp b/test/test_rns_loopback/test_rns_loopback.cpp index d6d37cff..fb54e528 100644 --- a/test/test_rns_loopback/test_rns_loopback.cpp +++ b/test/test_rns_loopback/test_rns_loopback.cpp @@ -32,6 +32,12 @@ class OutInterface : public RNS::InterfaceImpl { virtual bool send_outgoing(const RNS::Bytes &data) { HEADF(RNS::LOG_TRACE, "OutInterface.send_outgoing: data: %s", data.toHex().c_str()); + // Keep the last announce sent, so a test can replay it as if it had + // arrived from another node + if (data.size() > 0 && (data.data()[0] & 0x03) == RNS::Type::Packet::ANNOUNCE) { + last_announce = data; + } + // Loop data back to InInterface for testing // NOTE: This sends the incoming data directory to RNS::Interface (which relays to Transport) // and does not actually go through InInterface. @@ -42,9 +48,11 @@ class OutInterface : public RNS::InterfaceImpl { return true; } + static RNS::Bytes last_announce; private: RNS::Interface& _in_interface; }; +RNS::Bytes OutInterface::last_announce; // Test AnnounceHandler class ExampleAnnounceHandler : public RNS::AnnounceHandler { @@ -65,6 +73,44 @@ class ExampleAnnounceHandler : public RNS::AnnounceHandler { } }; +// Overrides only the basic callback: must still be called now that Transport +// dispatches through the packet-carrying overload +class BasicAnnounceHandler : public RNS::AnnounceHandler { +public: + BasicAnnounceHandler() : AnnounceHandler(nullptr) {} + virtual void received_announce(const RNS::Bytes& destination_hash, const RNS::Identity& announced_identity, const RNS::Bytes& app_data) { + ++calls; + last_destination_hash = destination_hash; + } + int calls = 0; + RNS::Bytes last_destination_hash; +}; + +// Overrides the packet-carrying callback as well, and records what it saw +class PacketAnnounceHandler : public RNS::AnnounceHandler { +public: + PacketAnnounceHandler() : AnnounceHandler(nullptr) {} + virtual void received_announce(const RNS::Bytes& destination_hash, const RNS::Identity& announced_identity, const RNS::Bytes& app_data) { + ++basic_calls; + } + virtual void received_announce(const RNS::Bytes& destination_hash, const RNS::Identity& announced_identity, const RNS::Bytes& app_data, const RNS::Packet& packet) { + ++packet_calls; + last_destination_hash = destination_hash; + last_packet_destination_hash = packet.destination_hash(); + last_packet_type = packet.packet_type(); + last_receiving_interface = packet.receiving_interface().get_hash(); + } + int basic_calls = 0; + int packet_calls = 0; + RNS::Bytes last_destination_hash; + RNS::Bytes last_packet_destination_hash; + uint8_t last_packet_type = 0xFF; + RNS::Bytes last_receiving_interface; +}; + +std::shared_ptr basic_announce_handler(new BasicAnnounceHandler()); +std::shared_ptr packet_announce_handler(new PacketAnnounceHandler()); + //ExampleAnnounceHandler announce_handler((const char*)"example_utilities.announcesample.fruits"); //RNS::HAnnounceHandler announce_handler(new ExampleAnnounceHandler("example_utilities.announcesample.fruits")); RNS::HAnnounceHandler announce_handler(new ExampleAnnounceHandler()); @@ -158,6 +204,8 @@ void testReticulum() { HEAD("Registering announce handler with Transport...", RNS::LOG_TRACE); RNS::Transport::register_announce_handler(announce_handler); + RNS::Transport::register_announce_handler(basic_announce_handler); + RNS::Transport::register_announce_handler(packet_announce_handler); HEAD("Announcing destination...", RNS::LOG_TRACE); // test path @@ -166,6 +214,30 @@ void testReticulum() { destination.announce(RNS::bytesFromString("test")); // 23.9% (+0.8%) + // The announce looped back above is for a local destination, so Transport + // does not hand it to announce handlers. Deregister the destination and + // replay the same announce, so it arrives as one from another node would. + HEAD("Replaying announce as a remote one...", RNS::LOG_TRACE); + TEST_ASSERT_TRUE(OutInterface::last_announce.size() > 0); + RNS::Transport::deregister_destination(destination); + in_interface.handle_incoming(OutInterface::last_announce); + + // A handler overriding only the basic callback is still called + TEST_ASSERT_EQUAL_INT(1, basic_announce_handler->calls); + TEST_ASSERT_TRUE(basic_announce_handler->last_destination_hash == destination.hash()); + + // A handler overriding the packet-carrying callback gets the packet, and + // is not called a second time through the basic one + TEST_ASSERT_EQUAL_INT(1, packet_announce_handler->packet_calls); + TEST_ASSERT_EQUAL_INT(0, packet_announce_handler->basic_calls); + TEST_ASSERT_TRUE(packet_announce_handler->last_destination_hash == destination.hash()); + TEST_ASSERT_TRUE(packet_announce_handler->last_packet_destination_hash == destination.hash()); + TEST_ASSERT_EQUAL_UINT8(RNS::Type::Packet::ANNOUNCE, packet_announce_handler->last_packet_type); + TEST_ASSERT_TRUE(packet_announce_handler->last_receiving_interface == in_interface.get_hash()); + + RNS::Transport::deregister_announce_handler(basic_announce_handler); + RNS::Transport::deregister_announce_handler(packet_announce_handler); + HEAD("Destroying Reticulum instance...", RNS::LOG_TRACE); reticulum = {RNS::Type::NONE};