/ Examples / Link.py
Link.py
  1  ##########################################################
  2  # This RNS example demonstrates how to set up a link to  #
  3  # a destination, and pass data back and forth over it.   #
  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          "linkexample"
 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 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      latest_client_link = link
 82  
 83  def client_disconnected(link):
 84      RNS.log("Client disconnected")
 85  
 86  def server_packet_received(message, packet):
 87      global latest_client_link
 88  
 89      # When data is received over any active link,
 90      # it will all be directed to the last client
 91      # that connected.
 92      text = message.decode("utf-8")
 93      RNS.log("Received data on the link: "+text)
 94      
 95      reply_text = "I received \""+text+"\" over the link"
 96      reply_data = reply_text.encode("utf-8")
 97      RNS.Packet(latest_client_link, reply_data).send()
 98  
 99  
100  ##########################################################
101  #### Client Part #########################################
102  ##########################################################
103  
104  # A reference to the server link
105  server_link = None
106  
107  # This initialisation is executed when the users chooses
108  # to run as a client
109  def client(destination_hexhash, configpath):
110      # We need a binary representation of the destination
111      # hash that was entered on the command line
112      try:
113          dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
114          if len(destination_hexhash) != dest_len:
115              raise ValueError(
116                  "Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)
117              )
118              
119          destination_hash = bytes.fromhex(destination_hexhash)
120      except:
121          RNS.log("Invalid destination entered. Check your input!\n")
122          sys.exit(0)
123  
124      # We must first initialise Reticulum
125      reticulum = RNS.Reticulum(configpath)
126  
127      # Check if we know a path to the destination
128      if not RNS.Transport.has_path(destination_hash):
129          RNS.log("Destination is not yet known. Requesting path and waiting for announce to arrive...")
130          RNS.Transport.request_path(destination_hash)
131          while not RNS.Transport.has_path(destination_hash):
132              time.sleep(0.1)
133  
134      # Recall the server identity
135      server_identity = RNS.Identity.recall(destination_hash)
136  
137      # Inform the user that we'll begin connecting
138      RNS.log("Establishing link with server...")
139  
140      # When the server identity is known, we set
141      # up a destination
142      server_destination = RNS.Destination(
143          server_identity,
144          RNS.Destination.OUT,
145          RNS.Destination.SINGLE,
146          APP_NAME,
147          "linkexample"
148      )
149  
150      # And create a link
151      link = RNS.Link(server_destination)
152  
153      # We set a callback that will get executed
154      # every time a packet is received over the
155      # link
156      link.set_packet_callback(client_packet_received)
157  
158      # We'll also set up functions to inform the
159      # user when the link is established or closed
160      link.set_link_established_callback(link_established)
161      link.set_link_closed_callback(link_closed)
162  
163      # Everything is set up, so let's enter a loop
164      # for the user to interact with the example
165      client_loop()
166  
167  def client_loop():
168      global server_link
169  
170      # Wait for the link to become active
171      while not server_link:
172          time.sleep(0.1)
173  
174      should_quit = False
175      while not should_quit:
176          try:
177              print("> ", end=" ")
178              text = input()
179  
180              # Check if we should quit the example
181              if text == "quit" or text == "q" or text == "exit":
182                  should_quit = True
183                  server_link.teardown()
184  
185              # If not, send the entered text over the link
186              if text != "":
187                  data = text.encode("utf-8")
188                  if len(data) <= RNS.Link.MDU:
189                      RNS.Packet(server_link, data).send()
190                  else:
191                      RNS.log(
192                          "Cannot send this packet, the data size of "+
193                          str(len(data))+" bytes exceeds the link packet MDU of "+
194                          str(RNS.Link.MDU)+" bytes",
195                          RNS.LOG_ERROR
196                      )
197  
198          except Exception as e:
199              RNS.log("Error while sending data over the link: "+str(e))
200              should_quit = True
201              server_link.teardown()
202  
203  # This function is called when a link
204  # has been established with the server
205  def link_established(link):
206      # We store a reference to the link
207      # instance for later use
208      global server_link
209      server_link = link
210  
211      # Inform the user that the server is
212      # connected
213      RNS.log("Link established with server, enter some text to send, or \"quit\" to quit")
214  
215  # When a link is closed, we'll inform the
216  # user, and exit the program
217  def link_closed(link):
218      if link.teardown_reason == RNS.Link.TIMEOUT:
219          RNS.log("The link timed out, exiting now")
220      elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED:
221          RNS.log("The link was closed by the server, exiting now")
222      else:
223          RNS.log("Link closed, exiting now")
224      
225      time.sleep(1.5)
226      sys.exit(0)
227  
228  # When a packet is received over the link, we
229  # simply print out the data.
230  def client_packet_received(message, packet):
231      text = message.decode("utf-8")
232      RNS.log("Received data on the link: "+text)
233      print("> ", end=" ")
234      sys.stdout.flush()
235  
236  
237  ##########################################################
238  #### Program Startup #####################################
239  ##########################################################
240  
241  # This part of the program runs at startup,
242  # and parses input of from the user, and then
243  # starts up the desired program mode.
244  if __name__ == "__main__":
245      try:
246          parser = argparse.ArgumentParser(description="Simple link example")
247  
248          parser.add_argument(
249              "-s",
250              "--server",
251              action="store_true",
252              help="wait for incoming link requests from clients"
253          )
254  
255          parser.add_argument(
256              "--config",
257              action="store",
258              default=None,
259              help="path to alternative Reticulum config directory",
260              type=str
261          )
262  
263          parser.add_argument(
264              "destination",
265              nargs="?",
266              default=None,
267              help="hexadecimal hash of the server destination",
268              type=str
269          )
270  
271          args = parser.parse_args()
272  
273          if args.config:
274              configarg = args.config
275          else:
276              configarg = None
277  
278          if args.server:
279              server(configarg)
280          else:
281              if (args.destination == None):
282                  print("")
283                  parser.print_help()
284                  print("")
285              else:
286                  client(args.destination, configarg)
287  
288      except KeyboardInterrupt:
289          print("")
290          sys.exit(0)