Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion src/microReticulum/Transport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/microReticulum/Transport.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
72 changes: 72 additions & 0 deletions test/test_rns_loopback/test_rns_loopback.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 {
Expand All @@ -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<BasicAnnounceHandler> basic_announce_handler(new BasicAnnounceHandler());
std::shared_ptr<PacketAnnounceHandler> 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());
Expand Down Expand Up @@ -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
Expand All @@ -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};

Expand Down
Loading