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