/ Examples / Identify.py
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)