Updated Echo example

This commit is contained in:
Mark Qvist 2018-04-25 00:20:57 +02:00
parent 0c49ca8458
commit 8082338657
4 changed files with 58 additions and 128 deletions

View File

@ -104,7 +104,7 @@ class Destination:
def proof_requested_callback(self, callback): def proof_requested_callback(self, callback):
self.callbacks.proof_requested = callback self.callbacks.proof_requested = callback
def setProofStrategy(self, proof_strategy): def set_proof_strategy(self, proof_strategy):
if not proof_strategy in Destination.proof_strategies: if not proof_strategy in Destination.proof_strategies:
raise TypeError("Unsupported proof strategy") raise TypeError("Unsupported proof strategy")
else: else:

View File

@ -99,13 +99,12 @@ class Identity:
announced_identity.loadPublicKey(public_key) announced_identity.loadPublicKey(public_key)
if announced_identity.pub != None and announced_identity.validate(signature, signed_data): if announced_identity.pub != None and announced_identity.validate(signature, signed_data):
RNS.log("Announce is valid", RNS.LOG_VERBOSE)
RNS.Identity.remember(RNS.Identity.fullHash(packet.raw), destination_hash, public_key) RNS.Identity.remember(RNS.Identity.fullHash(packet.raw), destination_hash, public_key)
RNS.log("Stored valid announce from "+RNS.prettyhexrep(destination_hash), RNS.LOG_INFO) RNS.log("Stored valid announce from "+RNS.prettyhexrep(destination_hash), RNS.LOG_INFO)
del announced_identity del announced_identity
return True return True
else: else:
RNS.log("Announce is invalid", RNS.LOG_VERBOSE) RNS.log("Received invalid announce", RNS.LOG_DEBUG)
del announced_identity del announced_identity
return False return False

View File

@ -253,6 +253,7 @@ class PacketReceipt:
if proof_valid: if proof_valid:
self.status = PacketReceipt.DELIVERED self.status = PacketReceipt.DELIVERED
self.proved = True self.proved = True
self.concluded_at = time.time()
if self.callbacks.delivery != None: if self.callbacks.delivery != None:
self.callbacks.delivery(self) self.callbacks.delivery(self)
return True return True
@ -267,6 +268,7 @@ class PacketReceipt:
if proof_valid: if proof_valid:
self.status = PacketReceipt.DELIVERED self.status = PacketReceipt.DELIVERED
self.proved = True self.proved = True
self.concluded_at = time.time()
if self.callbacks.delivery != None: if self.callbacks.delivery != None:
self.callbacks.delivery(self) self.callbacks.delivery(self)
return True return True
@ -275,6 +277,8 @@ class PacketReceipt:
else: else:
return False return False
def rtt(self):
return self.concluded_at - self.sent_at
def isTimedOut(self): def isTimedOut(self):
return (self.sent_at+self.timeout < time.time()) return (self.sent_at+self.timeout < time.time())

View File

@ -1,5 +1,11 @@
##########################################################
# This RNS example demonstrates a simple client/server #
# echo utility. A client can send an echo request to the #
# server, and the server will respond by proving receipt #
# of the packet. #
##########################################################
import argparse import argparse
import time
import RNS import RNS
# Let's define an app name. We'll use this for all # Let's define an app name. We'll use this for all
@ -25,9 +31,16 @@ def server(configpath):
# to read it. # to read it.
echo_destination = RNS.Destination(server_identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "echo", "request") echo_destination = RNS.Destination(server_identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "echo", "request")
# We configure the destination to automatically prove all
# packets adressed to it. By doing this, RNS will automatically
# generate a proof for each incoming packet and transmit it
# back to the sender of that packet.
echo_destination.set_proof_strategy(RNS.Destination.PROVE_ALL)
# Tell the destination which function in our program to # Tell the destination which function in our program to
# run when a packet is received. # run when a packet is received. We do this so we can
echo_destination.setCallback(serverCallback) # print a log message when the server receives a request
echo_destination.packet_callback(server_callback)
# Everything's ready! # Everything's ready!
# Let's Wait for client requests or user input # Let's Wait for client requests or user input
@ -36,58 +49,26 @@ def server(configpath):
def announceLoop(destination): def announceLoop(destination):
# Let the user know that everything is ready # Let the user know that everything is ready
RNS.log("Echo server "+RNS.prettyhexrep(destination.hash)+" running, hit enter to send announce (Ctrl-C to quit)") RNS.log("Echo server "+RNS.prettyhexrep(destination.hash)+" running, hit enter to manually send an announce (Ctrl-C to quit)")
# We enter a loop that runs until the users exits. # We enter a loop that runs until the users exits.
# If the user just hits enter, we will announce our server # If the user hits enter, we will announce our server
# destination on the network, which will let clients know # destination on the network, which will let clients
# how to create messages directed towards it. # know how to create messages directed towards it.
while True: while True:
entered = raw_input() entered = raw_input()
destination.announce() destination.announce()
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash)) RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
def serverCallback(message, packet): def server_callback(message, packet):
# We have received am echo request from a client! When
# a client sends a request, it will include the hash of
# it's identity in the message. Since we know that the
# client has created a listening destination using this
# identity hash, we can construct an outgoing destination
# to direct our response to. The hash is sent in binary
# format, so we encode it as printable hexadecimal first,
# since aspect names need to in printable text.
client_identity_hexhash = message.encode("hex_codec")
# We can now create a destination that will let us reach
# the client which send the echo request.
reply_destination = RNS.Destination(None, RNS.Destination.OUT, RNS.Destination.PLAIN, APP_NAME, "echo", "reply", client_identity_hexhash)
# Let's encode the reply destination hash in a readable
# way, so we can output some info to the user.
reply_destination_hexhash = reply_destination.hash.encode("hex_codec")
# Tell the user that we received an echo request, and # Tell the user that we received an echo request, and
# that we are going to send a reply to the requester. # that we are going to send a reply to the requester.
RNS.log("Received packet from <"+reply_destination_hexhash+">, sending reply") # Sending the proof is handled automatically, since we
# set up the destination to prove all incoming packets.
# To let the client know that we got the echo request, RNS.log("Received packet from echo client, proof sent")
# we will use the "proof" functions of Reticulum. In most
# applications, the proving of packets will occur fully
# automatically, but in some cases like this, it can be
# beneficial to use the functions manually, since it
# neatly provides functionality that can unequivocally
# prove the receipt of the request to the client.
#
# Using the proof functionality is very simple, we just
# need to call the "prove" method on the packet we wish
# to prove, and specify which destination it should be
# directed to.
packet.prove(reply_destination)
# We need a global list to hold sent echo requests
sent_requests = []
# This initialisation is executed when the users chooses # This initialisation is executed when the users chooses
# to run as a client # to run as a client
def client(destination_hexhash, configpath): def client(destination_hexhash, configpath):
@ -104,36 +85,12 @@ def client(destination_hexhash, configpath):
# We must first initialise Reticulum # We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath) reticulum = RNS.Reticulum(configpath)
# Randomly create a new identity for our echo server # We override the loglevel to provide feedback when
client_identity = RNS.Identity() # an announce is received
RNS.loglevel = RNS.LOG_INFO
# Let's set up a destination for replies to our echo
# requests. This destination will be used by the server
# to direct replies to. We're going to use a "plain"
# destination, so the server can send replies back
# without knowing any public keys of the client. In this
# case, such a design is benificial, since any client
# can send echo requests directly to the server, without
# first having to announce it's destination, or include
# public keys in the echo request
#
# We will use the destination naming convention of:
# example_utilities.echo.reply.<IDENTITY_HASH>
# where the last part is a hex representation of the hash
# of our "client_identity". We need to include this to
# create a unique destination for the server to respond to.
# If we had used a "single" destination, something equivalent
# to this process would have happened automatically.
reply_destination = RNS.Destination(client_identity, RNS.Destination.IN, RNS.Destination.PLAIN, APP_NAME, "echo", "reply", client_identity.hexhash)
# Since we are only expecting packets of the "proof"
# type to reach our reply destination, we just set the
# proof callback (and in this case not the normal
# message callback)
reply_destination.setProofCallback(clientProofCallback)
# Tell the user that the client is ready! # Tell the user that the client is ready!
RNS.log("Echo client "+RNS.prettyhexrep(reply_destination.hash)+" ready, hit enter to send echo request (Ctrl-C to quit)") RNS.log("Echo client ready, hit enter to send echo request to "+destination_hexhash+" (Ctrl-C to quit)")
# We enter a loop that runs until the user exits. # We enter a loop that runs until the user exits.
# If the user hits enter, we will try to send an # If the user hits enter, we will try to send an
@ -153,55 +110,37 @@ def client(destination_hexhash, configpath):
# recall method, so let's create an outgoing # recall method, so let's create an outgoing
# destination. We use the naming convention: # destination. We use the naming convention:
# example_utilities.echo.request # example_utilities.echo.request
# Since this is a "single" destination, the identity # This matches the naming we specified in the
# hash will be automatically added to the end of # server part of the code.
# the name.
request_destination = RNS.Destination(server_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "echo", "request") request_destination = RNS.Destination(server_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "echo", "request")
# The destination is ready, so let's create a packet. # The destination is ready, so let's create a packet.
# We set the destination to the request_destination # We set the destination to the request_destination
# that was just created, and the only data we add # that was just created, and the only data we add
# is the identity hash of our client identity. # is a random hash.
# Including that information will let the server echo_request = RNS.Packet(request_destination, RNS.Identity.getRandomHash())
# create a destination to send replies to.
echo_request = RNS.Packet(request_destination, client_identity.hash)
# Send the packet! # Send the packet! If the packet is successfully
echo_request.send() # sent, it will return a PacketReceipt instance.
packet_receipt = echo_request.send()
# Add the request to our list of sent packets # We can then set a delivery callback on the receipt.
sent_requests.append(echo_request) # This will get automatically called when a proof for
# this specific packet is received from the destination.
packet_receipt.delivery_callback(packet_delivered)
# Tell the user that the echo request was sent # Tell the user that the echo request was sent
RNS.log("Sent echo request to "+RNS.prettyhexrep(request_destination.hash)) RNS.log("Sent echo request to "+RNS.prettyhexrep(request_destination.hash))
else: else:
# If we do not know this destination, tell the # If we do not know this destination, tell the
# user to wait for an announce to arrive. # user to wait for an announce to arrive.
RNS.log("Destination is not yet known. Wait for an announce to arrive.") RNS.log("Destination is not yet known. Wait for an announce to arrive and try again.")
# This method is called when our reply destination # This method is called when our reply destination
# receives a proof packet. # receives a proof packet.
def clientProofCallback(proof_packet): def packet_delivered(receipt):
# We save the current time so we can calculate if receipt.status == RNS.PacketReceipt.DELIVERED:
# round-trip time for the packet rtt = receipt.rtt()
now = time.time()
# Let's look through our list of sent requests,
# and see if we can find one that matches the
# proof we just received.
for unproven_packet in sent_requests:
try:
# Check that the proof hash matches the
# hash of the packet we sent earlier
if unproven_packet.packet_hash == proof_packet.data[:32]:
# We need to actually calidate the proof.
# This is simply done by calling the
# validateProofPacket method on the packet
# we sent earlier.
if unproven_packet.validateProofPacket(proof_packet):
# If the proof is valid, we will calculate
# the round-trip time, and inform the user.
rtt = now - unproven_packet.sent_at
if (rtt >= 1): if (rtt >= 1):
rtt = round(rtt, 3) rtt = round(rtt, 3)
rttstring = str(rtt)+" seconds" rttstring = str(rtt)+" seconds"
@ -209,20 +148,7 @@ def clientProofCallback(proof_packet):
rtt = round(rtt*1000, 3) rtt = round(rtt*1000, 3)
rttstring = str(rtt)+" milliseconds" rttstring = str(rtt)+" milliseconds"
RNS.log( RNS.log("Valid reply received from "+RNS.prettyhexrep(receipt.destination.hash)+", round-trip time is "+rttstring)
"Valid echo reply, proved by "+RNS.prettyhexrep(unproven_packet.destination.hash)+
", round-trip time was "+rttstring
)
# Perform some cleanup
sent_requests.remove(unproven_packet)
del unproven_packet
else:
# If the proof was invalid, we inform
# the user of this.
RNS.log("Echo reply received, but proof was invalid")
except:
RNS.log("Proof packet received, but packet contained invalid or unparsable data")
if __name__ == "__main__": if __name__ == "__main__":
@ -248,6 +174,7 @@ if __name__ == "__main__":
print("") print("")
parser.print_help() parser.print_help()
print("") print("")
else:
client(args.destination, configarg) client(args.destination, configarg)
except KeyboardInterrupt: except KeyboardInterrupt:
exit() exit()