2025-08-23 14:12:13 -06:00
/*
# #########################################################
# 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>
2026-02-15 09:59:25 -07:00
# include <signal.h>
2025-08-23 14:12:13 -06:00
# endif
# include <stdlib.h>
# include <unistd.h>
# include <string>
# include <vector>
# include <map>
# include <functional>
2026-02-15 09:59:25 -07:00
# include <algorithm>
2025-08-23 14:12:13 -06:00
// 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"
2025-08-24 20:52:38 -06:00
RNS : : Reticulum reticulum ;
2025-08-23 14:12:13 -06:00
/*
# #########################################################
# ### 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 ( ) ;
2026-03-01 10:59:33 -07:00
RNS : : logf ( RNS : : LOG_NOTICE , " Received data on the link: %s " , text . c_str ( ) ) ;
2025-08-23 14:12:13 -06:00
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();
2025-08-24 14:26:29 -06:00
RNS : : Packet ( latest_client_link , reply_data ) . send ( ) ;
2025-08-23 14:12:13 -06:00
}
// 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
2026-03-01 10:59:33 -07:00
RNS : : logf ( RNS : : LOG_NOTICE , " Link example <%s> running, waiting for a connection. " , destination . hash ( ) . toHex ( ) . c_str ( ) ) ;
2025-08-23 14:12:13 -06:00
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 ) {
2025-08-24 20:52:38 -06:00
reticulum . loop ( ) ;
// Non-blocking input
char ch ;
while ( read ( STDIN_FILENO , & ch , 1 ) > 0 ) {
if ( ch = = ' \n ' ) {
destination . announce ( ) ;
2026-03-01 10:59:33 -07:00
RNS : : logf ( RNS : : LOG_NOTICE , " Sent announce from %s " , destination . hash ( ) . toHex ( ) . c_str ( ) ) ;
2025-08-24 20:52:38 -06:00
}
}
2025-08-23 14:12:13 -06:00
}
}
// 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
2025-08-24 20:52:38 -06:00
server_loop ( server_destination ) ;
2025-08-23 14:12:13 -06:00
}
/*
# #########################################################
# ### 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 ( ) ;
2026-03-01 10:59:33 -07:00
RNS : : logf ( RNS : : LOG_NOTICE , " Received data on the link: %s " , text . c_str ( ) ) ;
2025-08-23 14:12:13 -06:00
printf ( " > " ) ;
fflush ( stdout ) ;
}
void client_loop ( ) {
2025-08-24 20:52:38 -06:00
// Wait for the link to become active
RNS : : log ( " Waiting for link to become active... " ) ;
2025-08-23 14:12:13 -06:00
while ( ! server_link ) {
2025-08-24 20:52:38 -06:00
reticulum . loop ( ) ;
2025-08-23 14:12:13 -06:00
RNS : : Utilities : : OS : : sleep ( 0.1 ) ;
}
2025-08-24 20:52:38 -06:00
std : : string text ;
printf ( " > " ) ;
fflush ( STDIN_FILENO ) ;
bool should_quit = false ;
2025-08-23 14:12:13 -06:00
while ( ! should_quit ) {
2025-08-24 20:52:38 -06:00
reticulum . loop ( ) ;
2025-08-23 14:12:13 -06:00
2025-08-24 20:52:38 -06:00
// Non-blocking input
char ch ;
while ( read ( STDIN_FILENO , & ch , 1 ) > 0 ) {
if ( ch = = ' \n ' ) {
2025-08-23 14:12:13 -06:00
2025-08-24 20:52:38 -06:00
// Check if we should quit the example
if ( text = = " quit " | | text = = " q " | | text = = " exit " ) {
should_quit = true ;
server_link . teardown ( ) ;
2025-08-23 14:12:13 -06:00
}
2025-08-24 20:52:38 -06:00
// 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 {
2026-03-01 10:59:33 -07:00
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 ) ;
2025-08-24 20:52:38 -06:00
}
2025-08-23 14:12:13 -06:00
}
2025-08-24 20:52:38 -06:00
text . clear ( ) ;
printf ( " > " ) ;
fflush ( STDIN_FILENO ) ;
} else {
text + = ch ;
2025-08-23 14:12:13 -06:00
}
}
2025-08-24 20:52:38 -06:00
RNS : : Utilities : : OS : : sleep ( 0.1 ) ;
2025-08-23 14:12:13 -06:00
}
}
// 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
2025-08-24 20:52:38 -06:00
client_loop ( ) ;
2025-08-23 14:12:13 -06:00
}
/*
# #########################################################
# ### Program Startup #####################################
# #########################################################
*/
// Signal handler function
void cleanup_handler ( int signum ) {
if ( signum = = SIGINT ) {
printf ( " \n Ctrl+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 [ ] ) {
2026-02-15 09:59:25 -07:00
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 ;
}
}
2026-02-09 13:52:13 -07:00
# if defined(RNS_MEM_LOG)
2025-08-23 14:12:13 -06:00
RNS : : loglevel ( RNS : : LOG_MEM ) ;
# else
2026-02-15 09:59:25 -07:00
if ( log_trace ) {
RNS : : loglevel ( RNS : : LOG_TRACE ) ;
}
else {
RNS : : loglevel ( RNS : : LOG_NOTICE ) ;
}
2025-08-23 14:12:13 -06:00
# endif
// Register the signal handler for SIGINT
signal ( SIGINT , cleanup_handler ) ;
2025-08-24 14:26:29 -06:00
// Setup non-blocking input
int flags = fcntl ( STDIN_FILENO , F_GETFL , 0 ) ;
fcntl ( STDIN_FILENO , F_SETFL , flags | O_NONBLOCK ) ;
2025-08-24 20:52:38 -06:00
// Initialize and register filesystem
2025-08-26 10:49:37 -06:00
RNS : : FileSystem universal_filesystem = new UniversalFileSystem ( ) ;
2025-08-24 20:52:38 -06:00
universal_filesystem . init ( ) ;
RNS : : Utilities : : OS : : register_filesystem ( universal_filesystem ) ;
// Initialize and register interface
2025-08-26 10:49:37 -06:00
RNS : : Interface udp_interface = new UDPInterface ( ) ;
2025-08-24 20:52:38 -06:00
udp_interface . mode ( RNS : : Type : : Interface : : MODE_GATEWAY ) ;
RNS : : Transport : : register_interface ( udp_interface ) ;
udp_interface . start ( ) ;
// Initialize and start Reticulum
reticulum . start ( ) ;
2025-08-23 14:12:13 -06:00
if ( argc < = 1 ) {
printf ( " \n Must 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 ( " \n Loop exited. Performing cleanup... \n " ) ;
printf ( " Cleanup complete. Exiting. \n " ) ;
return 0 ;
}