Identify.py
1 ########################################################## 2 # This RNS example demonstrates how to set up a link to # 3 # a destination, and identify the initiator to it's peer # 4 ########################################################## 5 6 import os 7 import sys 8 import time 9 import argparse 10 import RNS 11 12 # Let's define an app name. We'll use this for all 13 # destinations we create. Since this echo example 14 # is part of a range of example utilities, we'll put 15 # them all within the app namespace "example_utilities" 16 APP_NAME = "example_utilities" 17 18 ########################################################## 19 #### Server Part ######################################### 20 ########################################################## 21 22 # A reference to the latest client link that connected 23 latest_client_link = None 24 25 # This initialisation is executed when the users chooses 26 # to run as a server 27 def server(configpath): 28 # We must first initialise Reticulum 29 reticulum = RNS.Reticulum(configpath) 30 31 # Randomly create a new identity for our link example 32 server_identity = RNS.Identity() 33 34 # We create a destination that clients can connect to. We 35 # want clients to create links to this destination, so we 36 # need to create a "single" destination type. 37 server_destination = RNS.Destination( 38 server_identity, 39 RNS.Destination.IN, 40 RNS.Destination.SINGLE, 41 APP_NAME, 42 "identifyexample" 43 ) 44 45 # We configure a function that will get called every time 46 # a new client creates a link to this destination. 47 server_destination.set_link_established_callback(client_connected) 48 49 # Everything's ready! 50 # Let's Wait for client requests or user input 51 server_loop(server_destination) 52 53 def server_loop(destination): 54 # Let the user know that everything is ready 55 RNS.log( 56 "Link identification example "+ 57 RNS.prettyhexrep(destination.hash)+ 58 " running, waiting for a connection." 59 ) 60 61 RNS.log("Hit enter to manually send an announce (Ctrl-C to quit)") 62 63 # We enter a loop that runs until the users exits. 64 # If the user hits enter, we will announce our server 65 # destination on the network, which will let clients 66 # know how to create messages directed towards it. 67 while True: 68 entered = input() 69 destination.announce() 70 RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash)) 71 72 # When a client establishes a link to our server 73 # destination, this function will be called with 74 # a reference to the link. 75 def client_connected(link): 76 global latest_client_link 77 78 RNS.log("Client connected") 79 link.set_link_closed_callback(client_disconnected) 80 link.set_packet_callback(server_packet_received) 81 link.set_remote_identified_callback(remote_identified) 82 latest_client_link = link 83 84 def client_disconnected(link): 85 RNS.log("Client disconnected") 86 87 def remote_identified(link, identity): 88 RNS.log("Remote identified as: "+str(identity)) 89 90 def server_packet_received(message, packet): 91 global latest_client_link 92 93 # Get the originating identity for display 94 remote_peer = "unidentified peer" 95 if packet.link.get_remote_identity() != None: 96 remote_peer = str(packet.link.get_remote_identity()) 97 98 # When data is received over any active link, 99 # it will all be directed to the last client 100 # that connected. 101 text = message.decode("utf-8") 102 103 RNS.log("Received data from "+remote_peer+": "+text) 104 105 reply_text = "I received \""+text+"\" over the link from "+remote_peer 106 reply_data = reply_text.encode("utf-8") 107 RNS.Packet(latest_client_link, reply_data).send() 108 109 110 ########################################################## 111 #### Client Part ######################################### 112 ########################################################## 113 114 # A reference to the server link 115 server_link = None 116 117 # A reference to the client identity 118 client_identity = None 119 120 # This initialisation is executed when the users chooses 121 # to run as a client 122 def client(destination_hexhash, configpath): 123 global client_identity 124 # We need a binary representation of the destination 125 # hash that was entered on the command line 126 try: 127 dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2 128 if len(destination_hexhash) != dest_len: 129 raise ValueError( 130 "Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2) 131 ) 132 133 destination_hash = bytes.fromhex(destination_hexhash) 134 except: 135 RNS.log("Invalid destination entered. Check your input!\n") 136 sys.exit(0) 137 138 # We must first initialise Reticulum 139 reticulum = RNS.Reticulum(configpath) 140 141 # Create a new client identity 142 client_identity = RNS.Identity() 143 RNS.log( 144 "Client created new identity "+ 145 str(client_identity) 146 ) 147 148 # Check if we know a path to the destination 149 if not RNS.Transport.has_path(destination_hash): 150 RNS.log("Destination is not yet known. Requesting path and waiting for announce to arrive...") 151 RNS.Transport.request_path(destination_hash) 152 while not RNS.Transport.has_path(destination_hash): 153 time.sleep(0.1) 154 155 # Recall the server identity 156 server_identity = RNS.Identity.recall(destination_hash) 157 158 # Inform the user that we'll begin connecting 159 RNS.log("Establishing link with server...") 160 161 # When the server identity is known, we set 162 # up a destination 163 server_destination = RNS.Destination( 164 server_identity, 165 RNS.Destination.OUT, 166 RNS.Destination.SINGLE, 167 APP_NAME, 168 "identifyexample" 169 ) 170 171 # And create a link 172 link = RNS.Link(server_destination) 173 174 # We set a callback that will get executed 175 # every time a packet is received over the 176 # link 177 link.set_packet_callback(client_packet_received) 178 179 # We'll also set up functions to inform the 180 # user when the link is established or closed 181 link.set_link_established_callback(link_established) 182 link.set_link_closed_callback(link_closed) 183 184 # Everything is set up, so let's enter a loop 185 # for the user to interact with the example 186 client_loop() 187 188 def client_loop(): 189 global server_link 190 191 # Wait for the link to become active 192 while not server_link: 193 time.sleep(0.1) 194 195 should_quit = False 196 while not should_quit: 197 try: 198 print("> ", end=" ") 199 text = input() 200 201 # Check if we should quit the example 202 if text == "quit" or text == "q" or text == "exit": 203 should_quit = True 204 server_link.teardown() 205 206 # If not, send the entered text over the link 207 if text != "": 208 data = text.encode("utf-8") 209 if len(data) <= RNS.Link.MDU: 210 RNS.Packet(server_link, data).send() 211 else: 212 RNS.log( 213 "Cannot send this packet, the data size of "+ 214 str(len(data))+" bytes exceeds the link packet MDU of "+ 215 str(RNS.Link.MDU)+" bytes", 216 RNS.LOG_ERROR 217 ) 218 219 except Exception as e: 220 RNS.log("Error while sending data over the link: "+str(e)) 221 should_quit = True 222 server_link.teardown() 223 224 # This function is called when a link 225 # has been established with the server 226 def link_established(link): 227 # We store a reference to the link 228 # instance for later use 229 global server_link, client_identity 230 server_link = link 231 232 # Inform the user that the server is 233 # connected 234 RNS.log("Link established with server, identifying to remote peer...") 235 236 link.identify(client_identity) 237 238 # When a link is closed, we'll inform the 239 # user, and exit the program 240 def link_closed(link): 241 if link.teardown_reason == RNS.Link.TIMEOUT: 242 RNS.log("The link timed out, exiting now") 243 elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED: 244 RNS.log("The link was closed by the server, exiting now") 245 else: 246 RNS.log("Link closed, exiting now") 247 248 time.sleep(1.5) 249 sys.exit(0) 250 251 # When a packet is received over the link, we 252 # simply print out the data. 253 def client_packet_received(message, packet): 254 text = message.decode("utf-8") 255 RNS.log("Received data on the link: "+text) 256 print("> ", end=" ") 257 sys.stdout.flush() 258 259 260 ########################################################## 261 #### Program Startup ##################################### 262 ########################################################## 263 264 # This part of the program runs at startup, 265 # and parses input of from the user, and then 266 # starts up the desired program mode. 267 if __name__ == "__main__": 268 try: 269 parser = argparse.ArgumentParser(description="Simple link example") 270 271 parser.add_argument( 272 "-s", 273 "--server", 274 action="store_true", 275 help="wait for incoming link requests from clients" 276 ) 277 278 parser.add_argument( 279 "--config", 280 action="store", 281 default=None, 282 help="path to alternative Reticulum config directory", 283 type=str 284 ) 285 286 parser.add_argument( 287 "destination", 288 nargs="?", 289 default=None, 290 help="hexadecimal hash of the server destination", 291 type=str 292 ) 293 294 args = parser.parse_args() 295 296 if args.config: 297 configarg = args.config 298 else: 299 configarg = None 300 301 if args.server: 302 server(configarg) 303 else: 304 if (args.destination == None): 305 print("") 306 parser.print_help() 307 print("") 308 else: 309 client(args.destination, configarg) 310 311 except KeyboardInterrupt: 312 print("") 313 sys.exit(0)