microReticulum/examples/link_native/main.cpp
Chad Attermann 79b75bbd4e Replace string-concatenation logging with printf-style F-variants
Converted log calls across src/, examples/, and test/ to use DEBUGF/TRACEF/ERRORF/etc. macros instead of std::string + operator+. This eliminates temporary heap allocations on every log call, which matters on memory-constrained MCUs.
2026-03-01 10:59:33 -07:00

384 lines
11 KiB
C++

/*
##########################################################
# This RNS example demonstrates how to set up a link to #
# a destination, and pass data back and forth over it. #
##########################################################
*/
#include <UDPInterface.h>
#include <UniversalFileSystem.h>
#include <Reticulum.h>
#include <Interface.h>
#include <Link.h>
#include <Identity.h>
#include <Destination.h>
#include <Packet.h>
#include <Transport.h>
#include <Log.h>
#include <Bytes.h>
#include <Type.h>
#include <Utilities/OS.h>
#ifdef ARDUINO
#include <Arduino.h>
#else
#include <termios.h>
#include <fcntl.h>
#include <stdio.h>
#include <signal.h>
#endif
#include <stdlib.h>
#include <unistd.h>
#include <string>
#include <vector>
#include <map>
#include <functional>
#include <algorithm>
// Let's define an app name. We'll use this for all
// destinations we create. Since this echo example
// is part of a range of example utilities, we'll put
// them all within the app namespace "example_utilities"
#define APP_NAME "example_utilities"
RNS::Reticulum reticulum;
/*
##########################################################
#### Server Part #########################################
##########################################################
*/
// A reference to the latest client link that connected
RNS::Link latest_client_link({RNS::Type::NONE});
void client_disconnected(RNS::Link& link) {
RNS::log("Client disconnected");
}
void server_packet_received(const RNS::Bytes& message, const RNS::Packet& packet) {
// When data is received over any active link,
// it will all be directed to the last client
// that connected.
std::string text = message.toString();
RNS::logf(RNS::LOG_NOTICE, "Received data on the link: %s", text.c_str());
std::string reply_text = "I received \""+text+"\" over the link";
RNS::Bytes reply_data(reply_text);
// CBA TODO: Add Packet constructor that accepts Link but doesn't require Destination
//RNS::Packet(RNS::Type::NONE, latest_client_link, reply_data).send();
RNS::Packet(latest_client_link, reply_data).send();
}
// When a client establishes a link to our server
// destination, this function will be called with
// a reference to the link.
void client_connected(RNS::Link& link) {
RNS::log("Client connected");
link.set_link_closed_callback(client_disconnected);
link.set_packet_callback(server_packet_received);
latest_client_link = link;
}
void server_loop(RNS::Destination& destination) {
// Let the user know that everything is ready
RNS::logf(RNS::LOG_NOTICE, "Link example <%s> running, waiting for a connection.", destination.hash().toHex().c_str());
RNS::log("Hit enter to manually send an announce (Ctrl-C to quit)");
// We enter a loop that runs until the users exits.
// If the user hits enter, we will announce our server
// destination on the network, which will let clients
// know how to create messages directed towards it.
while (true) {
reticulum.loop();
// Non-blocking input
char ch;
while (read(STDIN_FILENO, &ch, 1) > 0) {
if (ch == '\n') {
destination.announce();
RNS::logf(RNS::LOG_NOTICE, "Sent announce from %s", destination.hash().toHex().c_str());
}
}
}
}
// This initialisation is executed when the users chooses
// to run as a server
void server() {
// Randomly create a new identity for our link example
RNS::Identity server_identity = RNS::Identity();
// We create a destination that clients can connect to. We
// want clients to create links to this destination, so we
// need to create a "single" destination type.
RNS::Destination server_destination = RNS::Destination(
server_identity,
RNS::Type::Destination::IN,
RNS::Type::Destination::SINGLE,
APP_NAME,
"linkexample"
);
// We configure a function that will get called every time
// a new client creates a link to this destination.
server_destination.set_link_established_callback(client_connected);
// Everything's ready!
// Let's Wait for client requests or user input
server_loop(server_destination);
}
/*
##########################################################
#### Client Part #########################################
##########################################################
*/
// A reference to the server link
RNS::Link server_link({RNS::Type::NONE});
// This function is called when a link
// has been established with the server
void link_established(RNS::Link& link) {
// We store a reference to the link
// instance for later use
server_link = link;
// Inform the user that the server is
// connected
RNS::log("Link established with server, enter some text to send, or \"quit\" to quit");
}
// When a link is closed, we'll inform the
// user, and exit the program
void link_closed(RNS::Link& link) {
if (link.teardown_reason() == RNS::Type::Link::TIMEOUT) {
RNS::log("The link timed out, exiting now");
}
else if (link.teardown_reason() == RNS::Type::Link::DESTINATION_CLOSED) {
RNS::log("The link was closed by the server, exiting now");
}
else {
RNS::log("Link closed, exiting now");
}
//RNS::Reticulum::exit_handler();
//RNS::Utilities::OS::sleep(1.5);
_exit(0);
}
// When a packet is received over the link, we
// simply print out the data.
void client_packet_received(const RNS::Bytes& message, const RNS::Packet& packet) {
std::string text = message.toString();
RNS::logf(RNS::LOG_NOTICE, "Received data on the link: %s", text.c_str());
printf("> ");
fflush(stdout);
}
void client_loop() {
// Wait for the link to become active
RNS::log("Waiting for link to become active...");
while (!server_link) {
reticulum.loop();
RNS::Utilities::OS::sleep(0.1);
}
std::string text;
printf("> ");
fflush(STDIN_FILENO);
bool should_quit = false;
while (!should_quit) {
reticulum.loop();
// Non-blocking input
char ch;
while (read(STDIN_FILENO, &ch, 1) > 0) {
if (ch == '\n') {
// Check if we should quit the example
if (text == "quit" || text == "q" || text == "exit") {
should_quit = true;
server_link.teardown();
}
// If not, send the entered text over the link
if (text != "") {
RNS::Bytes data(text);
if (data.size() <= RNS::Type::Link::MDU) {
printf("(sending data: %s)\n", text.c_str());
// CBA TODO: Add Packet constructor that accepts Link but doesn't require Destination
//RNS::Packet(RNS::Type::NONE, server_link, data).send();
RNS::Packet(server_link, data).send();
}
else {
RNS::logf(RNS::LOG_ERROR, "Cannot send this packet, the data size of %zu bytes exceeds the link packet MDU of %zu bytes", data.size(), (size_t)RNS::Type::Link::MDU);
}
}
text.clear();
printf("> ");
fflush(STDIN_FILENO);
} else {
text += ch;
}
}
RNS::Utilities::OS::sleep(0.1);
}
}
// This initialisation is executed when the users chooses
// to run as a client
void client(const char* destination_hexhash) {
// We need a binary representation of the destination
// hash that was entered on the command line
RNS::Bytes destination_hash;
try {
int dest_len = (RNS::Type::Reticulum::TRUNCATED_HASHLENGTH/8)*2;
if (strlen(destination_hexhash) != dest_len) {
throw std::invalid_argument("Destination length is invalid, must be "+std::to_string(dest_len)+" hexadecimal characters ("+std::to_string(dest_len/2)+" bytes).");
}
destination_hash.assignHex(destination_hexhash);
}
catch (std::exception& e) {
RNS::log("Invalid destination entered. Check your input!", RNS::LOG_ERROR);
return;
}
// Check if we know a path to the destination
if (!RNS::Transport::has_path(destination_hash)) {
RNS::log("Destination is not yet known. Requesting path and waiting for announce to arrive...");
RNS::Transport::request_path(destination_hash);
while (!RNS::Transport::has_path(destination_hash)) {
reticulum.loop();
RNS::Utilities::OS::sleep(0.1);
}
}
// Recall the server identity
RNS::Identity server_identity = RNS::Identity::recall(destination_hash);
// Inform the user that we'll begin connecting
RNS::log("Establishing link with server...");
// When the server identity is known, we set
// up a destination
RNS::Destination server_destination = RNS::Destination(
server_identity,
RNS::Type::Destination::OUT,
RNS::Type::Destination::SINGLE,
APP_NAME,
"linkexample"
);
// And create a link
RNS::Link link = RNS::Link(server_destination);
// We set a callback that will get executed
// every time a packet is received over the
// link
link.set_packet_callback(client_packet_received);
// We'll also set up functions to inform the
// user when the link is established or closed
link.set_link_established_callback(link_established);
link.set_link_closed_callback(link_closed);
// Everything is set up, so let's enter a loop
// for the user to interact with the example
client_loop();
}
/*
##########################################################
#### Program Startup #####################################
##########################################################
*/
// Signal handler function
void cleanup_handler(int signum) {
if (signum == SIGINT) {
printf("\nCtrl+C detected. Performing cleanup...\n");
printf("Cleanup complete. Exiting.\n");
_exit(0);
}
}
// This part of the program runs at startup,
// and parses input of from the user, and then
// starts up the desired program mode.
int main(int argc, char *argv[]) {
bool log_trace = false;
for (int i = 1; i < argc; ++i) {
if (argv[i] && strcasecmp(argv[i], "--log_trace") == 0) {
log_trace = true;
for (int j = i; j < argc - 1; ++j) {
argv[j] = argv[j + 1];
}
--argc;
--i;
}
}
#if defined(RNS_MEM_LOG)
RNS::loglevel(RNS::LOG_MEM);
#else
if (log_trace) {
RNS::loglevel(RNS::LOG_TRACE);
}
else {
RNS::loglevel(RNS::LOG_NOTICE);
}
#endif
// Register the signal handler for SIGINT
signal(SIGINT, cleanup_handler);
// Setup non-blocking input
int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
// Initialize and register filesystem
RNS::FileSystem universal_filesystem = new UniversalFileSystem();
universal_filesystem.init();
RNS::Utilities::OS::register_filesystem(universal_filesystem);
// Initialize and register interface
RNS::Interface udp_interface = new UDPInterface();
udp_interface.mode(RNS::Type::Interface::MODE_GATEWAY);
RNS::Transport::register_interface(udp_interface);
udp_interface.start();
// Initialize and start Reticulum
reticulum.start();
if (argc <= 1) {
printf("\nMust specify a destination for client mode, or \"-s\" or \"--server\" for server mode.\n\n");
return -1;
}
if (strcmp(argv[1], "--server") == 0 || strcmp(argv[1], "-s") == 0) {
server();
}
else {
client(argv[1]);
}
printf("\nLoop exited. Performing cleanup...\n");
printf("Cleanup complete. Exiting.\n");
return 0;
}