mirror of
https://github.com/markqvist/Reticulum.git
synced 2024-11-22 13:40:19 +00:00
Indentation and formatting cleanup
This commit is contained in:
parent
e4dfd052e6
commit
bd098c338a
@ -15,52 +15,52 @@ APP_NAME = "example_utilitites"
|
|||||||
|
|
||||||
# This initialisation is executed when the program is started
|
# This initialisation is executed when the program is started
|
||||||
def program_setup(configpath, channel=None):
|
def program_setup(configpath, channel=None):
|
||||||
# We must first initialise Reticulum
|
# We must first initialise Reticulum
|
||||||
reticulum = RNS.Reticulum(configpath)
|
reticulum = RNS.Reticulum(configpath)
|
||||||
|
|
||||||
# If the user did not select a "channel" we use
|
# If the user did not select a "channel" we use
|
||||||
# a default one called "public_information".
|
# a default one called "public_information".
|
||||||
# This "channel" is added to the destination name-
|
# This "channel" is added to the destination name-
|
||||||
# space, so the user can select different broadcast
|
# space, so the user can select different broadcast
|
||||||
# channels.
|
# channels.
|
||||||
if channel == None:
|
if channel == None:
|
||||||
channel = "public_information"
|
channel = "public_information"
|
||||||
|
|
||||||
# We create a PLAIN destination. This is an uncencrypted endpoint
|
# We create a PLAIN destination. This is an uncencrypted endpoint
|
||||||
# that anyone can listen to and send information to.
|
# that anyone can listen to and send information to.
|
||||||
broadcast_destination = RNS.Destination(None, RNS.Destination.IN, RNS.Destination.PLAIN, APP_NAME, "broadcast", channel)
|
broadcast_destination = RNS.Destination(None, RNS.Destination.IN, RNS.Destination.PLAIN, APP_NAME, "broadcast", channel)
|
||||||
|
|
||||||
# We specify a callback that will get called every time
|
# We specify a callback that will get called every time
|
||||||
# the destination receives data.
|
# the destination receives data.
|
||||||
broadcast_destination.packet_callback(packet_callback)
|
broadcast_destination.packet_callback(packet_callback)
|
||||||
|
|
||||||
# Everything's ready!
|
# Everything's ready!
|
||||||
# Let's hand over control to the main loop
|
# Let's hand over control to the main loop
|
||||||
broadcastLoop(broadcast_destination)
|
broadcastLoop(broadcast_destination)
|
||||||
|
|
||||||
def packet_callback(data, packet):
|
def packet_callback(data, packet):
|
||||||
# Simply print out the received data
|
# Simply print out the received data
|
||||||
print("")
|
print("")
|
||||||
print("Received data: "+data.decode("utf-8")+"\r\n> ", end="")
|
print("Received data: "+data.decode("utf-8")+"\r\n> ", end="")
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
def broadcastLoop(destination):
|
def broadcastLoop(destination):
|
||||||
# Let the user know that everything is ready
|
# Let the user know that everything is ready
|
||||||
RNS.log("Broadcast example "+RNS.prettyhexrep(destination.hash)+" running, enter text and hit enter to broadcast (Ctrl-C to quit)")
|
RNS.log("Broadcast example "+RNS.prettyhexrep(destination.hash)+" running, enter text and hit enter to broadcast (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 hits enter, we will send the information
|
# If the user hits enter, we will send the information
|
||||||
# that the user entered into the prompt.
|
# that the user entered into the prompt.
|
||||||
while True:
|
while True:
|
||||||
print("> ", end="")
|
print("> ", end="")
|
||||||
entered = input()
|
entered = input()
|
||||||
|
|
||||||
|
if entered != "":
|
||||||
|
data = entered.encode("utf-8")
|
||||||
|
packet = RNS.Packet(destination, data)
|
||||||
|
packet.send()
|
||||||
|
|
||||||
if entered != "":
|
|
||||||
data = entered.encode("utf-8")
|
|
||||||
packet = RNS.Packet(destination, data)
|
|
||||||
packet.send()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##########################################################
|
##########################################################
|
||||||
#### Program Startup #####################################
|
#### Program Startup #####################################
|
||||||
@ -70,24 +70,24 @@ def broadcastLoop(destination):
|
|||||||
# and parses input from the user, and then starts
|
# and parses input from the user, and then starts
|
||||||
# the program.
|
# the program.
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
parser = argparse.ArgumentParser(description="Reticulum example that demonstrates sending and receiving unencrypted broadcasts")
|
parser = argparse.ArgumentParser(description="Reticulum example that demonstrates sending and receiving unencrypted broadcasts")
|
||||||
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
||||||
parser.add_argument("--channel", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
parser.add_argument("--channel", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.config:
|
if args.config:
|
||||||
configarg = args.config
|
configarg = args.config
|
||||||
else:
|
else:
|
||||||
configarg = None
|
configarg = None
|
||||||
|
|
||||||
if args.channel:
|
if args.channel:
|
||||||
channelarg = args.channel
|
channelarg = args.channel
|
||||||
else:
|
else:
|
||||||
channelarg = None
|
channelarg = None
|
||||||
|
|
||||||
program_setup(configarg, channelarg)
|
program_setup(configarg, channelarg)
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("")
|
print("")
|
||||||
exit()
|
exit()
|
296
Examples/Echo.py
296
Examples/Echo.py
@ -22,56 +22,56 @@ APP_NAME = "example_utilitites"
|
|||||||
# This initialisation is executed when the users chooses
|
# This initialisation is executed when the users chooses
|
||||||
# to run as a server
|
# to run as a server
|
||||||
def server(configpath):
|
def server(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
|
# Randomly create a new identity for our echo server
|
||||||
server_identity = RNS.Identity()
|
server_identity = RNS.Identity()
|
||||||
|
|
||||||
# We create a destination that clients can query. We want
|
# We create a destination that clients can query. We want
|
||||||
# to be able to verify echo replies to our clients, so we
|
# to be able to verify echo replies to our clients, so we
|
||||||
# create a "single" destination that can receive encrypted
|
# create a "single" destination that can receive encrypted
|
||||||
# messages. This way the client can send a request and be
|
# messages. This way the client can send a request and be
|
||||||
# certain that no-one else than this destination was able
|
# certain that no-one else than this destination was able
|
||||||
# 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
|
# We configure the destination to automatically prove all
|
||||||
# packets adressed to it. By doing this, RNS will automatically
|
# packets adressed to it. By doing this, RNS will automatically
|
||||||
# generate a proof for each incoming packet and transmit it
|
# generate a proof for each incoming packet and transmit it
|
||||||
# back to the sender of that packet.
|
# back to the sender of that packet.
|
||||||
echo_destination.set_proof_strategy(RNS.Destination.PROVE_ALL)
|
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. We do this so we can
|
# run when a packet is received. We do this so we can
|
||||||
# print a log message when the server receives a request
|
# print a log message when the server receives a request
|
||||||
echo_destination.packet_callback(server_callback)
|
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
|
||||||
announceLoop(echo_destination)
|
announceLoop(echo_destination)
|
||||||
|
|
||||||
|
|
||||||
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 manually send an 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 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
|
# destination on the network, which will let clients
|
||||||
# know how to create messages directed towards it.
|
# know how to create messages directed towards it.
|
||||||
while True:
|
while True:
|
||||||
entered = input()
|
entered = input()
|
||||||
destination.announce()
|
destination.announce()
|
||||||
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
|
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
|
||||||
|
|
||||||
|
|
||||||
def server_callback(message, packet):
|
def server_callback(message, packet):
|
||||||
# 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.
|
||||||
# Sending the proof is handled automatically, since we
|
# Sending the proof is handled automatically, since we
|
||||||
# set up the destination to prove all incoming packets.
|
# set up the destination to prove all incoming packets.
|
||||||
RNS.log("Received packet from echo client, proof sent")
|
RNS.log("Received packet from echo client, proof sent")
|
||||||
|
|
||||||
|
|
||||||
##########################################################
|
##########################################################
|
||||||
@ -81,103 +81,103 @@ def server_callback(message, packet):
|
|||||||
# 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, timeout=None):
|
def client(destination_hexhash, configpath, timeout=None):
|
||||||
# We need a binary representation of the destination
|
# We need a binary representation of the destination
|
||||||
# hash that was entered on the command line
|
# hash that was entered on the command line
|
||||||
try:
|
try:
|
||||||
if len(destination_hexhash) != 20:
|
if len(destination_hexhash) != 20:
|
||||||
raise ValueError("Destination length is invalid, must be 20 hexadecimal characters (10 bytes)")
|
raise ValueError("Destination length is invalid, must be 20 hexadecimal characters (10 bytes)")
|
||||||
destination_hash = bytes.fromhex(destination_hexhash)
|
destination_hash = bytes.fromhex(destination_hexhash)
|
||||||
except:
|
except:
|
||||||
RNS.log("Invalid destination entered. Check your input!\n")
|
RNS.log("Invalid destination entered. Check your input!\n")
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
# We must first initialise Reticulum
|
# We must first initialise Reticulum
|
||||||
reticulum = RNS.Reticulum(configpath)
|
reticulum = RNS.Reticulum(configpath)
|
||||||
|
|
||||||
# We override the loglevel to provide feedback when
|
# We override the loglevel to provide feedback when
|
||||||
# an announce is received
|
# an announce is received
|
||||||
if RNS.loglevel < RNS.LOG_INFO:
|
if RNS.loglevel < RNS.LOG_INFO:
|
||||||
RNS.loglevel = RNS.LOG_INFO
|
RNS.loglevel = RNS.LOG_INFO
|
||||||
|
|
||||||
# Tell the user that the client is ready!
|
# Tell the user that the client is ready!
|
||||||
RNS.log("Echo client ready, hit enter to send echo request to "+destination_hexhash+" (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
|
||||||
# echo request to the destination specified on the
|
# echo request to the destination specified on the
|
||||||
# command line.
|
# command line.
|
||||||
while True:
|
while True:
|
||||||
input()
|
input()
|
||||||
|
|
||||||
# Let's first check if RNS knows a path to the destination.
|
# Let's first check if RNS knows a path to the destination.
|
||||||
# If it does, we'll load the server identity and create a packet
|
# If it does, we'll load the server identity and create a packet
|
||||||
if RNS.Transport.hasPath(destination_hash):
|
if RNS.Transport.hasPath(destination_hash):
|
||||||
|
|
||||||
# To address the server, we need to know it's public
|
# To address the server, we need to know it's public
|
||||||
# key, so we check if Reticulum knows this destination.
|
# key, so we check if Reticulum knows this destination.
|
||||||
# This is done by calling the "recall" method of the
|
# This is done by calling the "recall" method of the
|
||||||
# Identity module. If the destination is known, it will
|
# Identity module. If the destination is known, it will
|
||||||
# return an Identity instance that can be used in
|
# return an Identity instance that can be used in
|
||||||
# outgoing destinations.
|
# outgoing destinations.
|
||||||
server_identity = RNS.Identity.recall(destination_hash)
|
server_identity = RNS.Identity.recall(destination_hash)
|
||||||
|
|
||||||
# We got the correct identity instance from the
|
# We got the correct identity instance from the
|
||||||
# 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
|
||||||
# This matches the naming we specified in the
|
# This matches the naming we specified in the
|
||||||
# server part of the code.
|
# server part of the code.
|
||||||
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 a random hash.
|
# is a random hash.
|
||||||
echo_request = RNS.Packet(request_destination, RNS.Identity.getRandomHash())
|
echo_request = RNS.Packet(request_destination, RNS.Identity.getRandomHash())
|
||||||
|
|
||||||
# Send the packet! If the packet is successfully
|
# Send the packet! If the packet is successfully
|
||||||
# sent, it will return a PacketReceipt instance.
|
# sent, it will return a PacketReceipt instance.
|
||||||
packet_receipt = echo_request.send()
|
packet_receipt = echo_request.send()
|
||||||
|
|
||||||
# If the user specified a timeout, we set this
|
# If the user specified a timeout, we set this
|
||||||
# timeout on the packet receipt, and configure
|
# timeout on the packet receipt, and configure
|
||||||
# a callback function, that will get called if
|
# a callback function, that will get called if
|
||||||
# the packet times out.
|
# the packet times out.
|
||||||
if timeout != None:
|
if timeout != None:
|
||||||
packet_receipt.set_timeout(timeout)
|
packet_receipt.set_timeout(timeout)
|
||||||
packet_receipt.timeout_callback(packet_timed_out)
|
packet_receipt.timeout_callback(packet_timed_out)
|
||||||
|
|
||||||
# We can then set a delivery callback on the receipt.
|
# We can then set a delivery callback on the receipt.
|
||||||
# This will get automatically called when a proof for
|
# This will get automatically called when a proof for
|
||||||
# this specific packet is received from the destination.
|
# this specific packet is received from the destination.
|
||||||
packet_receipt.delivery_callback(packet_delivered)
|
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. Requesting path...")
|
RNS.log("Destination is not yet known. Requesting path...")
|
||||||
RNS.Transport.requestPath(destination_hash)
|
RNS.Transport.requestPath(destination_hash)
|
||||||
|
|
||||||
# This function is called when our reply destination
|
# This function is called when our reply destination
|
||||||
# receives a proof packet.
|
# receives a proof packet.
|
||||||
def packet_delivered(receipt):
|
def packet_delivered(receipt):
|
||||||
if receipt.status == RNS.PacketReceipt.DELIVERED:
|
if receipt.status == RNS.PacketReceipt.DELIVERED:
|
||||||
rtt = receipt.rtt()
|
rtt = receipt.rtt()
|
||||||
if (rtt >= 1):
|
if (rtt >= 1):
|
||||||
rtt = round(rtt, 3)
|
rtt = round(rtt, 3)
|
||||||
rttstring = str(rtt)+" seconds"
|
rttstring = str(rtt)+" seconds"
|
||||||
else:
|
else:
|
||||||
rtt = round(rtt*1000, 3)
|
rtt = round(rtt*1000, 3)
|
||||||
rttstring = str(rtt)+" milliseconds"
|
rttstring = str(rtt)+" milliseconds"
|
||||||
|
|
||||||
RNS.log("Valid reply received from "+RNS.prettyhexrep(receipt.destination.hash)+", round-trip time is "+rttstring)
|
RNS.log("Valid reply received from "+RNS.prettyhexrep(receipt.destination.hash)+", round-trip time is "+rttstring)
|
||||||
|
|
||||||
# This function is called if a packet times out.
|
# This function is called if a packet times out.
|
||||||
def packet_timed_out(receipt):
|
def packet_timed_out(receipt):
|
||||||
if receipt.status == RNS.PacketReceipt.FAILED:
|
if receipt.status == RNS.PacketReceipt.FAILED:
|
||||||
RNS.log("Packet "+RNS.prettyhexrep(receipt.hash)+" timed out")
|
RNS.log("Packet "+RNS.prettyhexrep(receipt.hash)+" timed out")
|
||||||
|
|
||||||
|
|
||||||
##########################################################
|
##########################################################
|
||||||
@ -188,36 +188,36 @@ def packet_timed_out(receipt):
|
|||||||
# and parses input from the user, and then starts
|
# and parses input from the user, and then starts
|
||||||
# the desired program mode.
|
# the desired program mode.
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
parser = argparse.ArgumentParser(description="Simple echo server and client utility")
|
parser = argparse.ArgumentParser(description="Simple echo server and client utility")
|
||||||
parser.add_argument("-s", "--server", action="store_true", help="wait for incoming packets from clients")
|
parser.add_argument("-s", "--server", action="store_true", help="wait for incoming packets from clients")
|
||||||
parser.add_argument("-t", "--timeout", action="store", metavar="s", default=None, help="set a reply timeout in seconds", type=float)
|
parser.add_argument("-t", "--timeout", action="store", metavar="s", default=None, help="set a reply timeout in seconds", type=float)
|
||||||
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
||||||
parser.add_argument("destination", nargs="?", default=None, help="hexadecimal hash of the server destination", type=str)
|
parser.add_argument("destination", nargs="?", default=None, help="hexadecimal hash of the server destination", type=str)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.server:
|
if args.server:
|
||||||
configarg=None
|
configarg=None
|
||||||
if args.config:
|
if args.config:
|
||||||
configarg = args.config
|
configarg = args.config
|
||||||
server(configarg)
|
server(configarg)
|
||||||
else:
|
else:
|
||||||
if args.config:
|
if args.config:
|
||||||
configarg = args.config
|
configarg = args.config
|
||||||
else:
|
else:
|
||||||
configarg = None
|
configarg = None
|
||||||
|
|
||||||
if args.timeout:
|
if args.timeout:
|
||||||
timeoutarg = float(args.timeout)
|
timeoutarg = float(args.timeout)
|
||||||
else:
|
else:
|
||||||
timeoutarg = None
|
timeoutarg = None
|
||||||
|
|
||||||
if (args.destination == None):
|
if (args.destination == None):
|
||||||
print("")
|
print("")
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
print("")
|
print("")
|
||||||
else:
|
else:
|
||||||
client(args.destination, configarg, timeout=timeoutarg)
|
client(args.destination, configarg, timeout=timeoutarg)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("")
|
print("")
|
||||||
exit()
|
exit()
|
@ -42,132 +42,132 @@ serve_path = None
|
|||||||
# This initialisation is executed when the users chooses
|
# This initialisation is executed when the users chooses
|
||||||
# to run as a server
|
# to run as a server
|
||||||
def server(configpath, path):
|
def server(configpath, path):
|
||||||
# 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 file server
|
# Randomly create a new identity for our file server
|
||||||
server_identity = RNS.Identity()
|
server_identity = RNS.Identity()
|
||||||
|
|
||||||
global serve_path
|
global serve_path
|
||||||
serve_path = path
|
serve_path = path
|
||||||
|
|
||||||
# We create a destination that clients can connect to. We
|
# We create a destination that clients can connect to. We
|
||||||
# want clients to create links to this destination, so we
|
# want clients to create links to this destination, so we
|
||||||
# need to create a "single" destination type.
|
# need to create a "single" destination type.
|
||||||
server_destination = RNS.Destination(server_identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "filetransfer", "server")
|
server_destination = RNS.Destination(server_identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "filetransfer", "server")
|
||||||
|
|
||||||
# We configure a function that will get called every time
|
# We configure a function that will get called every time
|
||||||
# a new client creates a link to this destination.
|
# a new client creates a link to this destination.
|
||||||
server_destination.link_established_callback(client_connected)
|
server_destination.link_established_callback(client_connected)
|
||||||
|
|
||||||
# Everything's ready!
|
# Everything's ready!
|
||||||
# Let's Wait for client requests or user input
|
# Let's Wait for client requests or user input
|
||||||
announceLoop(server_destination)
|
announceLoop(server_destination)
|
||||||
|
|
||||||
def announceLoop(destination):
|
def announceLoop(destination):
|
||||||
# Let the user know that everything is ready
|
# Let the user know that everything is ready
|
||||||
RNS.log("File server "+RNS.prettyhexrep(destination.hash)+" running")
|
RNS.log("File server "+RNS.prettyhexrep(destination.hash)+" running")
|
||||||
RNS.log("Hit enter to manually send an announce (Ctrl-C to quit)")
|
RNS.log("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 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
|
# destination on the network, which will let clients
|
||||||
# know how to create messages directed towards it.
|
# know how to create messages directed towards it.
|
||||||
while True:
|
while True:
|
||||||
entered = input()
|
entered = input()
|
||||||
destination.announce()
|
destination.announce()
|
||||||
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
|
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
|
||||||
|
|
||||||
# Here's a convenience function for listing all files
|
# Here's a convenience function for listing all files
|
||||||
# in our served directory
|
# in our served directory
|
||||||
def list_files():
|
def list_files():
|
||||||
# We add all entries from the directory that are
|
# We add all entries from the directory that are
|
||||||
# actual files, and does not start with "."
|
# actual files, and does not start with "."
|
||||||
global serve_path
|
global serve_path
|
||||||
return [file for file in os.listdir(serve_path) if os.path.isfile(os.path.join(serve_path, file)) and file[:1] != "."]
|
return [file for file in os.listdir(serve_path) if os.path.isfile(os.path.join(serve_path, file)) and file[:1] != "."]
|
||||||
|
|
||||||
# When a client establishes a link to our server
|
# When a client establishes a link to our server
|
||||||
# destination, this function will be called with
|
# destination, this function will be called with
|
||||||
# a reference to the link. We then send the client
|
# a reference to the link. We then send the client
|
||||||
# a list of files hosted on the server.
|
# a list of files hosted on the server.
|
||||||
def client_connected(link):
|
def client_connected(link):
|
||||||
# Check if the served directory still exists
|
# Check if the served directory still exists
|
||||||
if os.path.isdir(serve_path):
|
if os.path.isdir(serve_path):
|
||||||
RNS.log("Client connected, sending file list...")
|
RNS.log("Client connected, sending file list...")
|
||||||
|
|
||||||
link.link_closed_callback(client_disconnected)
|
link.link_closed_callback(client_disconnected)
|
||||||
|
|
||||||
# We pack a list of files for sending in a packet
|
# We pack a list of files for sending in a packet
|
||||||
data = umsgpack.packb(list_files())
|
data = umsgpack.packb(list_files())
|
||||||
|
|
||||||
# Check the size of the packed data
|
# Check the size of the packed data
|
||||||
if len(data) <= RNS.Link.MDU:
|
if len(data) <= RNS.Link.MDU:
|
||||||
# If it fits in one packet, we will just
|
# If it fits in one packet, we will just
|
||||||
# send it as a single packet over the link.
|
# send it as a single packet over the link.
|
||||||
list_packet = RNS.Packet(link, data)
|
list_packet = RNS.Packet(link, data)
|
||||||
list_receipt = list_packet.send()
|
list_receipt = list_packet.send()
|
||||||
list_receipt.set_timeout(APP_TIMEOUT)
|
list_receipt.set_timeout(APP_TIMEOUT)
|
||||||
list_receipt.delivery_callback(list_delivered)
|
list_receipt.delivery_callback(list_delivered)
|
||||||
list_receipt.timeout_callback(list_timeout)
|
list_receipt.timeout_callback(list_timeout)
|
||||||
else:
|
else:
|
||||||
RNS.log("Too many files in served directory!", RNS.LOG_ERROR)
|
RNS.log("Too many files in served directory!", RNS.LOG_ERROR)
|
||||||
RNS.log("You should implement a function to split the filelist over multiple packets.", RNS.LOG_ERROR)
|
RNS.log("You should implement a function to split the filelist over multiple packets.", RNS.LOG_ERROR)
|
||||||
RNS.log("Hint: The client already supports it :)", RNS.LOG_ERROR)
|
RNS.log("Hint: The client already supports it :)", RNS.LOG_ERROR)
|
||||||
|
|
||||||
# After this, we're just going to keep the link
|
# After this, we're just going to keep the link
|
||||||
# open until the client requests a file. We'll
|
# open until the client requests a file. We'll
|
||||||
# configure a function that get's called when
|
# configure a function that get's called when
|
||||||
# the client sends a packet with a file request.
|
# the client sends a packet with a file request.
|
||||||
link.packet_callback(client_request)
|
link.packet_callback(client_request)
|
||||||
else:
|
else:
|
||||||
RNS.log("Client connected, but served path no longer exists!", RNS.LOG_ERROR)
|
RNS.log("Client connected, but served path no longer exists!", RNS.LOG_ERROR)
|
||||||
link.teardown()
|
link.teardown()
|
||||||
|
|
||||||
def client_disconnected(link):
|
def client_disconnected(link):
|
||||||
RNS.log("Client disconnected")
|
RNS.log("Client disconnected")
|
||||||
|
|
||||||
def client_request(message, packet):
|
def client_request(message, packet):
|
||||||
global serve_path
|
global serve_path
|
||||||
filename = message.decode("utf-8")
|
filename = message.decode("utf-8")
|
||||||
if filename in list_files():
|
if filename in list_files():
|
||||||
try:
|
try:
|
||||||
# If we have the requested file, we'll
|
# If we have the requested file, we'll
|
||||||
# read it and pack it as a resource
|
# read it and pack it as a resource
|
||||||
RNS.log("Client requested \""+filename+"\"")
|
RNS.log("Client requested \""+filename+"\"")
|
||||||
file = open(os.path.join(serve_path, filename), "rb")
|
file = open(os.path.join(serve_path, filename), "rb")
|
||||||
file_resource = RNS.Resource(file, packet.link, callback=resource_sending_concluded)
|
file_resource = RNS.Resource(file, packet.link, callback=resource_sending_concluded)
|
||||||
file_resource.filename = filename
|
file_resource.filename = filename
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# If somethign went wrong, we close
|
# If somethign went wrong, we close
|
||||||
# the link
|
# the link
|
||||||
RNS.log("Error while reading file \""+filename+"\"", RNS.LOG_ERROR)
|
RNS.log("Error while reading file \""+filename+"\"", RNS.LOG_ERROR)
|
||||||
packet.link.teardown()
|
packet.link.teardown()
|
||||||
raise e
|
raise e
|
||||||
else:
|
else:
|
||||||
# If we don't have it, we close the link
|
# If we don't have it, we close the link
|
||||||
RNS.log("Client requested an unknown file")
|
RNS.log("Client requested an unknown file")
|
||||||
packet.link.teardown()
|
packet.link.teardown()
|
||||||
|
|
||||||
# This function is called on the server when a
|
# This function is called on the server when a
|
||||||
# resource transfer concludes.
|
# resource transfer concludes.
|
||||||
def resource_sending_concluded(resource):
|
def resource_sending_concluded(resource):
|
||||||
if hasattr(resource, "filename"):
|
if hasattr(resource, "filename"):
|
||||||
name = resource.filename
|
name = resource.filename
|
||||||
else:
|
else:
|
||||||
name = "resource"
|
name = "resource"
|
||||||
|
|
||||||
if resource.status == RNS.Resource.COMPLETE:
|
if resource.status == RNS.Resource.COMPLETE:
|
||||||
RNS.log("Done sending \""+name+"\" to client")
|
RNS.log("Done sending \""+name+"\" to client")
|
||||||
elif resource.status == RNS.Resource.FAILED:
|
elif resource.status == RNS.Resource.FAILED:
|
||||||
RNS.log("Sending \""+name+"\" to client failed")
|
RNS.log("Sending \""+name+"\" to client failed")
|
||||||
|
|
||||||
def list_delivered(receipt):
|
def list_delivered(receipt):
|
||||||
RNS.log("The file list was received by the client")
|
RNS.log("The file list was received by the client")
|
||||||
|
|
||||||
def list_timeout(receipt):
|
def list_timeout(receipt):
|
||||||
RNS.log("Sending list to client timed out, closing this link")
|
RNS.log("Sending list to client timed out, closing this link")
|
||||||
link = receipt.destination
|
link = receipt.destination
|
||||||
link.teardown()
|
link.teardown()
|
||||||
|
|
||||||
##########################################################
|
##########################################################
|
||||||
#### Client Part #########################################
|
#### Client Part #########################################
|
||||||
@ -194,116 +194,116 @@ file_size = 0
|
|||||||
# 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):
|
||||||
# We need a binary representation of the destination
|
# We need a binary representation of the destination
|
||||||
# hash that was entered on the command line
|
# hash that was entered on the command line
|
||||||
try:
|
try:
|
||||||
if len(destination_hexhash) != 20:
|
if len(destination_hexhash) != 20:
|
||||||
raise ValueError("Destination length is invalid, must be 20 hexadecimal characters (10 bytes)")
|
raise ValueError("Destination length is invalid, must be 20 hexadecimal characters (10 bytes)")
|
||||||
destination_hash = bytes.fromhex(destination_hexhash)
|
destination_hash = bytes.fromhex(destination_hexhash)
|
||||||
except:
|
except:
|
||||||
RNS.log("Invalid destination entered. Check your input!\n")
|
RNS.log("Invalid destination entered. Check your input!\n")
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
# We must first initialise Reticulum
|
# We must first initialise Reticulum
|
||||||
reticulum = RNS.Reticulum(configpath)
|
reticulum = RNS.Reticulum(configpath)
|
||||||
|
|
||||||
|
|
||||||
# Check if we know a path to the destination
|
# Check if we know a path to the destination
|
||||||
if not RNS.Transport.hasPath(destination_hash):
|
if not RNS.Transport.hasPath(destination_hash):
|
||||||
RNS.log("Destination is not yet known. Requesting path and waiting for announce to arrive...")
|
RNS.log("Destination is not yet known. Requesting path and waiting for announce to arrive...")
|
||||||
RNS.Transport.requestPath(destination_hash)
|
RNS.Transport.requestPath(destination_hash)
|
||||||
while not RNS.Transport.hasPath(destination_hash):
|
while not RNS.Transport.hasPath(destination_hash):
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
# Recall the server identity
|
# Recall the server identity
|
||||||
server_identity = RNS.Identity.recall(destination_hash)
|
server_identity = RNS.Identity.recall(destination_hash)
|
||||||
|
|
||||||
# Inform the user that we'll begin connecting
|
# Inform the user that we'll begin connecting
|
||||||
RNS.log("Establishing link with server...")
|
RNS.log("Establishing link with server...")
|
||||||
|
|
||||||
# When the server identity is known, we set
|
# When the server identity is known, we set
|
||||||
# up a destination
|
# up a destination
|
||||||
server_destination = RNS.Destination(server_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "filetransfer", "server")
|
server_destination = RNS.Destination(server_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "filetransfer", "server")
|
||||||
|
|
||||||
# We also want to automatically prove incoming packets
|
# We also want to automatically prove incoming packets
|
||||||
server_destination.set_proof_strategy(RNS.Destination.PROVE_ALL)
|
server_destination.set_proof_strategy(RNS.Destination.PROVE_ALL)
|
||||||
|
|
||||||
# And create a link
|
# And create a link
|
||||||
link = RNS.Link(server_destination)
|
link = RNS.Link(server_destination)
|
||||||
|
|
||||||
# We expect any normal data packets on the link
|
# We expect any normal data packets on the link
|
||||||
# to contain a list of served files, so we set
|
# to contain a list of served files, so we set
|
||||||
# a callback accordingly
|
# a callback accordingly
|
||||||
link.packet_callback(filelist_received)
|
link.packet_callback(filelist_received)
|
||||||
|
|
||||||
# We'll also set up functions to inform the
|
# We'll also set up functions to inform the
|
||||||
# user when the link is established or closed
|
# user when the link is established or closed
|
||||||
link.link_established_callback(link_established)
|
link.link_established_callback(link_established)
|
||||||
link.link_closed_callback(link_closed)
|
link.link_closed_callback(link_closed)
|
||||||
|
|
||||||
# And set the link to automatically begin
|
# And set the link to automatically begin
|
||||||
# downloading advertised resources
|
# downloading advertised resources
|
||||||
link.set_resource_strategy(RNS.Link.ACCEPT_ALL)
|
link.set_resource_strategy(RNS.Link.ACCEPT_ALL)
|
||||||
link.resource_started_callback(download_began)
|
link.resource_started_callback(download_began)
|
||||||
link.resource_concluded_callback(download_concluded)
|
link.resource_concluded_callback(download_concluded)
|
||||||
|
|
||||||
menu()
|
menu()
|
||||||
|
|
||||||
# Requests the specified file from the server
|
# Requests the specified file from the server
|
||||||
def download(filename):
|
def download(filename):
|
||||||
global server_link, menu_mode, current_filename, transfer_size, download_started
|
global server_link, menu_mode, current_filename, transfer_size, download_started
|
||||||
current_filename = filename
|
current_filename = filename
|
||||||
download_started = 0
|
download_started = 0
|
||||||
transfer_size = 0
|
transfer_size = 0
|
||||||
|
|
||||||
# We just create a packet containing the
|
# We just create a packet containing the
|
||||||
# requested filename, and send it down the
|
# requested filename, and send it down the
|
||||||
# link. We also specify we don't need a
|
# link. We also specify we don't need a
|
||||||
# packet receipt.
|
# packet receipt.
|
||||||
request_packet = RNS.Packet(server_link, filename.encode("utf-8"), create_receipt=False)
|
request_packet = RNS.Packet(server_link, filename.encode("utf-8"), create_receipt=False)
|
||||||
request_packet.send()
|
request_packet.send()
|
||||||
|
|
||||||
print("")
|
print("")
|
||||||
print(("Requested \""+filename+"\" from server, waiting for download to begin..."))
|
print(("Requested \""+filename+"\" from server, waiting for download to begin..."))
|
||||||
menu_mode = "download_started"
|
menu_mode = "download_started"
|
||||||
|
|
||||||
# This function runs a simple menu for the user
|
# This function runs a simple menu for the user
|
||||||
# to select which files to download, or quit
|
# to select which files to download, or quit
|
||||||
menu_mode = None
|
menu_mode = None
|
||||||
def menu():
|
def menu():
|
||||||
global server_files, server_link
|
global server_files, server_link
|
||||||
# Wait until we have a filelist
|
# Wait until we have a filelist
|
||||||
while len(server_files) == 0:
|
while len(server_files) == 0:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
RNS.log("Ready!")
|
RNS.log("Ready!")
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
global menu_mode
|
global menu_mode
|
||||||
menu_mode = "main"
|
menu_mode = "main"
|
||||||
should_quit = False
|
should_quit = False
|
||||||
while (not should_quit):
|
while (not should_quit):
|
||||||
print_menu()
|
print_menu()
|
||||||
|
|
||||||
while not menu_mode == "main":
|
while not menu_mode == "main":
|
||||||
# Wait
|
# Wait
|
||||||
time.sleep(0.25)
|
time.sleep(0.25)
|
||||||
|
|
||||||
user_input = input()
|
user_input = input()
|
||||||
if user_input == "q" or user_input == "quit" or user_input == "exit":
|
if user_input == "q" or user_input == "quit" or user_input == "exit":
|
||||||
should_quit = True
|
should_quit = True
|
||||||
print("")
|
print("")
|
||||||
else:
|
else:
|
||||||
if user_input in server_files:
|
if user_input in server_files:
|
||||||
download(user_input)
|
download(user_input)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
if 0 <= int(user_input) < len(server_files):
|
if 0 <= int(user_input) < len(server_files):
|
||||||
download(server_files[int(user_input)])
|
download(server_files[int(user_input)])
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if should_quit:
|
if should_quit:
|
||||||
server_link.teardown()
|
server_link.teardown()
|
||||||
|
|
||||||
# Prints out menus or screens for the
|
# Prints out menus or screens for the
|
||||||
# various states of the client program.
|
# various states of the client program.
|
||||||
@ -311,145 +311,145 @@ def menu():
|
|||||||
# I won't go into detail here. Just
|
# I won't go into detail here. Just
|
||||||
# strings basically.
|
# strings basically.
|
||||||
def print_menu():
|
def print_menu():
|
||||||
global menu_mode, download_time, download_started, download_finished, transfer_size, file_size
|
global menu_mode, download_time, download_started, download_finished, transfer_size, file_size
|
||||||
|
|
||||||
if menu_mode == "main":
|
if menu_mode == "main":
|
||||||
clear_screen()
|
clear_screen()
|
||||||
print_filelist()
|
print_filelist()
|
||||||
print("")
|
print("")
|
||||||
print("Select a file to download by entering name or number, or q to quit")
|
print("Select a file to download by entering name or number, or q to quit")
|
||||||
print(("> "), end=' ')
|
print(("> "), end=' ')
|
||||||
elif menu_mode == "download_started":
|
elif menu_mode == "download_started":
|
||||||
download_began = time.time()
|
download_began = time.time()
|
||||||
while menu_mode == "download_started":
|
while menu_mode == "download_started":
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
if time.time() > download_began+APP_TIMEOUT:
|
if time.time() > download_began+APP_TIMEOUT:
|
||||||
print("The download timed out")
|
print("The download timed out")
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
server_link.teardown()
|
server_link.teardown()
|
||||||
|
|
||||||
if menu_mode == "downloading":
|
if menu_mode == "downloading":
|
||||||
print("Download started")
|
print("Download started")
|
||||||
print("")
|
print("")
|
||||||
while menu_mode == "downloading":
|
while menu_mode == "downloading":
|
||||||
global current_download
|
global current_download
|
||||||
percent = round(current_download.progress() * 100.0, 1)
|
percent = round(current_download.progress() * 100.0, 1)
|
||||||
print(("\rProgress: "+str(percent)+" % "), end=' ')
|
print(("\rProgress: "+str(percent)+" % "), end=' ')
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
if menu_mode == "save_error":
|
if menu_mode == "save_error":
|
||||||
print(("\rProgress: 100.0 %"), end=' ')
|
print(("\rProgress: 100.0 %"), end=' ')
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
print("")
|
print("")
|
||||||
print("Could not write downloaded file to disk")
|
print("Could not write downloaded file to disk")
|
||||||
current_download.status = RNS.Resource.FAILED
|
current_download.status = RNS.Resource.FAILED
|
||||||
menu_mode = "download_concluded"
|
menu_mode = "download_concluded"
|
||||||
|
|
||||||
if menu_mode == "download_concluded":
|
if menu_mode == "download_concluded":
|
||||||
if current_download.status == RNS.Resource.COMPLETE:
|
if current_download.status == RNS.Resource.COMPLETE:
|
||||||
print(("\rProgress: 100.0 %"), end=' ')
|
print(("\rProgress: 100.0 %"), end=' ')
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
# Print statistics
|
# Print statistics
|
||||||
hours, rem = divmod(download_time, 3600)
|
hours, rem = divmod(download_time, 3600)
|
||||||
minutes, seconds = divmod(rem, 60)
|
minutes, seconds = divmod(rem, 60)
|
||||||
timestring = "{:0>2}:{:0>2}:{:05.2f}".format(int(hours),int(minutes),seconds)
|
timestring = "{:0>2}:{:0>2}:{:05.2f}".format(int(hours),int(minutes),seconds)
|
||||||
print("")
|
print("")
|
||||||
print("")
|
print("")
|
||||||
print("--- Statistics -----")
|
print("--- Statistics -----")
|
||||||
print("\tTime taken : "+timestring)
|
print("\tTime taken : "+timestring)
|
||||||
print("\tFile size : "+size_str(file_size))
|
print("\tFile size : "+size_str(file_size))
|
||||||
print("\tData transferred : "+size_str(transfer_size))
|
print("\tData transferred : "+size_str(transfer_size))
|
||||||
print("\tEffective rate : "+size_str(file_size/download_time, suffix='b')+"/s")
|
print("\tEffective rate : "+size_str(file_size/download_time, suffix='b')+"/s")
|
||||||
print("\tTransfer rate : "+size_str(transfer_size/download_time, suffix='b')+"/s")
|
print("\tTransfer rate : "+size_str(transfer_size/download_time, suffix='b')+"/s")
|
||||||
print("")
|
print("")
|
||||||
print("The download completed! Press enter to return to the menu.")
|
print("The download completed! Press enter to return to the menu.")
|
||||||
print("")
|
print("")
|
||||||
input()
|
input()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print("")
|
print("")
|
||||||
print("The download failed! Press enter to return to the menu.")
|
print("The download failed! Press enter to return to the menu.")
|
||||||
input()
|
input()
|
||||||
|
|
||||||
current_download = None
|
current_download = None
|
||||||
menu_mode = "main"
|
menu_mode = "main"
|
||||||
print_menu()
|
print_menu()
|
||||||
|
|
||||||
# This function prints out a list of files
|
# This function prints out a list of files
|
||||||
# on the connected server.
|
# on the connected server.
|
||||||
def print_filelist():
|
def print_filelist():
|
||||||
global server_files
|
global server_files
|
||||||
print("Files on server:")
|
print("Files on server:")
|
||||||
for index,file in enumerate(server_files):
|
for index,file in enumerate(server_files):
|
||||||
print("\t("+str(index)+")\t"+file)
|
print("\t("+str(index)+")\t"+file)
|
||||||
|
|
||||||
def filelist_received(filelist_data, packet):
|
def filelist_received(filelist_data, packet):
|
||||||
global server_files, menu_mode
|
global server_files, menu_mode
|
||||||
try:
|
try:
|
||||||
# Unpack the list and extend our
|
# Unpack the list and extend our
|
||||||
# local list of available files
|
# local list of available files
|
||||||
filelist = umsgpack.unpackb(filelist_data)
|
filelist = umsgpack.unpackb(filelist_data)
|
||||||
for file in filelist:
|
for file in filelist:
|
||||||
if not file in server_files:
|
if not file in server_files:
|
||||||
server_files.append(file)
|
server_files.append(file)
|
||||||
|
|
||||||
# If the menu is already visible,
|
# If the menu is already visible,
|
||||||
# we'll update it with what was
|
# we'll update it with what was
|
||||||
# just received
|
# just received
|
||||||
if menu_mode == "main":
|
if menu_mode == "main":
|
||||||
print_menu()
|
print_menu()
|
||||||
except:
|
except:
|
||||||
RNS.log("Invalid file list data received, closing link")
|
RNS.log("Invalid file list data received, closing link")
|
||||||
packet.link.teardown()
|
packet.link.teardown()
|
||||||
|
|
||||||
# This function is called when a link
|
# This function is called when a link
|
||||||
# has been established with the server
|
# has been established with the server
|
||||||
def link_established(link):
|
def link_established(link):
|
||||||
# We store a reference to the link
|
# We store a reference to the link
|
||||||
# instance for later use
|
# instance for later use
|
||||||
global server_link
|
global server_link
|
||||||
server_link = link
|
server_link = link
|
||||||
|
|
||||||
# Inform the user that the server is
|
# Inform the user that the server is
|
||||||
# connected
|
# connected
|
||||||
RNS.log("Link established with server")
|
RNS.log("Link established with server")
|
||||||
RNS.log("Waiting for filelist...")
|
RNS.log("Waiting for filelist...")
|
||||||
|
|
||||||
# And set up a small job to check for
|
# And set up a small job to check for
|
||||||
# a potential timeout in receiving the
|
# a potential timeout in receiving the
|
||||||
# file list
|
# file list
|
||||||
thread = threading.Thread(target=filelist_timeout_job)
|
thread = threading.Thread(target=filelist_timeout_job)
|
||||||
thread.setDaemon(True)
|
thread.setDaemon(True)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
# This job just sleeps for the specified
|
# This job just sleeps for the specified
|
||||||
# time, and then checks if the file list
|
# time, and then checks if the file list
|
||||||
# was received. If not, the program will
|
# was received. If not, the program will
|
||||||
# exit.
|
# exit.
|
||||||
def filelist_timeout_job():
|
def filelist_timeout_job():
|
||||||
time.sleep(APP_TIMEOUT)
|
time.sleep(APP_TIMEOUT)
|
||||||
|
|
||||||
global server_files
|
global server_files
|
||||||
if len(server_files) == 0:
|
if len(server_files) == 0:
|
||||||
RNS.log("Timed out waiting for filelist, exiting")
|
RNS.log("Timed out waiting for filelist, exiting")
|
||||||
os._exit(0)
|
os._exit(0)
|
||||||
|
|
||||||
|
|
||||||
# When a link is closed, we'll inform the
|
# When a link is closed, we'll inform the
|
||||||
# user, and exit the program
|
# user, and exit the program
|
||||||
def link_closed(link):
|
def link_closed(link):
|
||||||
if link.teardown_reason == RNS.Link.TIMEOUT:
|
if link.teardown_reason == RNS.Link.TIMEOUT:
|
||||||
RNS.log("The link timed out, exiting now")
|
RNS.log("The link timed out, exiting now")
|
||||||
elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED:
|
elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED:
|
||||||
RNS.log("The link was closed by the server, exiting now")
|
RNS.log("The link was closed by the server, exiting now")
|
||||||
else:
|
else:
|
||||||
RNS.log("Link closed, exiting now")
|
RNS.log("Link closed, exiting now")
|
||||||
|
|
||||||
RNS.Reticulum.exit_handler()
|
RNS.Reticulum.exit_handler()
|
||||||
time.sleep(1.5)
|
time.sleep(1.5)
|
||||||
os._exit(0)
|
os._exit(0)
|
||||||
|
|
||||||
# When RNS detects that the download has
|
# When RNS detects that the download has
|
||||||
# started, we'll update our menu state
|
# started, we'll update our menu state
|
||||||
@ -471,45 +471,45 @@ def download_began(resource):
|
|||||||
# or not, we'll update our menu state and
|
# or not, we'll update our menu state and
|
||||||
# inform the user about how it all went.
|
# inform the user about how it all went.
|
||||||
def download_concluded(resource):
|
def download_concluded(resource):
|
||||||
global menu_mode, current_filename, download_started, download_finished, download_time
|
global menu_mode, current_filename, download_started, download_finished, download_time
|
||||||
download_finished = time.time()
|
download_finished = time.time()
|
||||||
download_time = download_finished - download_started
|
download_time = download_finished - download_started
|
||||||
|
|
||||||
saved_filename = current_filename
|
saved_filename = current_filename
|
||||||
|
|
||||||
|
|
||||||
if resource.status == RNS.Resource.COMPLETE:
|
if resource.status == RNS.Resource.COMPLETE:
|
||||||
counter = 0
|
counter = 0
|
||||||
while os.path.isfile(saved_filename):
|
while os.path.isfile(saved_filename):
|
||||||
counter += 1
|
counter += 1
|
||||||
saved_filename = current_filename+"."+str(counter)
|
saved_filename = current_filename+"."+str(counter)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
file = open(saved_filename, "wb")
|
file = open(saved_filename, "wb")
|
||||||
file.write(resource.data.read())
|
file.write(resource.data.read())
|
||||||
file.close()
|
file.close()
|
||||||
menu_mode = "download_concluded"
|
menu_mode = "download_concluded"
|
||||||
except:
|
except:
|
||||||
menu_mode = "save_error"
|
menu_mode = "save_error"
|
||||||
else:
|
else:
|
||||||
menu_mode = "download_concluded"
|
menu_mode = "download_concluded"
|
||||||
|
|
||||||
# A convenience function for printing a human-
|
# A convenience function for printing a human-
|
||||||
# readable file size
|
# readable file size
|
||||||
def size_str(num, suffix='B'):
|
def size_str(num, suffix='B'):
|
||||||
units = ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']
|
units = ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']
|
||||||
last_unit = 'Yi'
|
last_unit = 'Yi'
|
||||||
|
|
||||||
if suffix == 'b':
|
if suffix == 'b':
|
||||||
num *= 8
|
num *= 8
|
||||||
units = ['','K','M','G','T','P','E','Z']
|
units = ['','K','M','G','T','P','E','Z']
|
||||||
last_unit = 'Y'
|
last_unit = 'Y'
|
||||||
|
|
||||||
for unit in units:
|
for unit in units:
|
||||||
if abs(num) < 1024.0:
|
if abs(num) < 1024.0:
|
||||||
return "%3.2f %s%s" % (num, unit, suffix)
|
return "%3.2f %s%s" % (num, unit, suffix)
|
||||||
num /= 1024.0
|
num /= 1024.0
|
||||||
return "%.2f %s%s" % (num, last_unit, suffix)
|
return "%.2f %s%s" % (num, last_unit, suffix)
|
||||||
|
|
||||||
# A convenience function for clearing the screen
|
# A convenience function for clearing the screen
|
||||||
def clear_screen():
|
def clear_screen():
|
||||||
@ -523,31 +523,31 @@ def clear_screen():
|
|||||||
# and parses input of from the user, and then
|
# and parses input of from the user, and then
|
||||||
# starts up the desired program mode.
|
# starts up the desired program mode.
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
parser = argparse.ArgumentParser(description="Simple file transfer server and client utility")
|
parser = argparse.ArgumentParser(description="Simple file transfer server and client utility")
|
||||||
parser.add_argument("-s", "--serve", action="store", metavar="dir", help="serve a directory of files to clients")
|
parser.add_argument("-s", "--serve", action="store", metavar="dir", help="serve a directory of files to clients")
|
||||||
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
||||||
parser.add_argument("destination", nargs="?", default=None, help="hexadecimal hash of the server destination", type=str)
|
parser.add_argument("destination", nargs="?", default=None, help="hexadecimal hash of the server destination", type=str)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.config:
|
if args.config:
|
||||||
configarg = args.config
|
configarg = args.config
|
||||||
else:
|
else:
|
||||||
configarg = None
|
configarg = None
|
||||||
|
|
||||||
if args.serve:
|
if args.serve:
|
||||||
if os.path.isdir(args.serve):
|
if os.path.isdir(args.serve):
|
||||||
server(configarg, args.serve)
|
server(configarg, args.serve)
|
||||||
else:
|
else:
|
||||||
RNS.log("The specified directory does not exist")
|
RNS.log("The specified directory does not exist")
|
||||||
else:
|
else:
|
||||||
if (args.destination == None):
|
if (args.destination == None):
|
||||||
print("")
|
print("")
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
print("")
|
print("")
|
||||||
else:
|
else:
|
||||||
client(args.destination, configarg)
|
client(args.destination, configarg)
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("")
|
print("")
|
||||||
exit()
|
exit()
|
284
Examples/Link.py
284
Examples/Link.py
@ -25,65 +25,65 @@ latest_client_link = None
|
|||||||
# This initialisation is executed when the users chooses
|
# This initialisation is executed when the users chooses
|
||||||
# to run as a server
|
# to run as a server
|
||||||
def server(configpath):
|
def server(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 link example
|
# Randomly create a new identity for our link example
|
||||||
server_identity = RNS.Identity()
|
server_identity = RNS.Identity()
|
||||||
|
|
||||||
# We create a destination that clients can connect to. We
|
# We create a destination that clients can connect to. We
|
||||||
# want clients to create links to this destination, so we
|
# want clients to create links to this destination, so we
|
||||||
# need to create a "single" destination type.
|
# need to create a "single" destination type.
|
||||||
server_destination = RNS.Destination(server_identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "linkexample")
|
server_destination = RNS.Destination(server_identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "linkexample")
|
||||||
|
|
||||||
# We configure a function that will get called every time
|
# We configure a function that will get called every time
|
||||||
# a new client creates a link to this destination.
|
# a new client creates a link to this destination.
|
||||||
server_destination.link_established_callback(client_connected)
|
server_destination.link_established_callback(client_connected)
|
||||||
|
|
||||||
# Everything's ready!
|
# Everything's ready!
|
||||||
# Let's Wait for client requests or user input
|
# Let's Wait for client requests or user input
|
||||||
server_loop(server_destination)
|
server_loop(server_destination)
|
||||||
|
|
||||||
def server_loop(destination):
|
def server_loop(destination):
|
||||||
# Let the user know that everything is ready
|
# Let the user know that everything is ready
|
||||||
RNS.log("Link example "+RNS.prettyhexrep(destination.hash)+" running, waiting for a connection.")
|
RNS.log("Link example "+RNS.prettyhexrep(destination.hash)+" running, waiting for a connection.")
|
||||||
RNS.log("Hit enter to manually send an announce (Ctrl-C to quit)")
|
RNS.log("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 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
|
# destination on the network, which will let clients
|
||||||
# know how to create messages directed towards it.
|
# know how to create messages directed towards it.
|
||||||
while True:
|
while True:
|
||||||
entered = input()
|
entered = input()
|
||||||
destination.announce()
|
destination.announce()
|
||||||
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
|
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
|
||||||
|
|
||||||
# When a client establishes a link to our server
|
# When a client establishes a link to our server
|
||||||
# destination, this function will be called with
|
# destination, this function will be called with
|
||||||
# a reference to the link.
|
# a reference to the link.
|
||||||
def client_connected(link):
|
def client_connected(link):
|
||||||
global latest_client_link
|
global latest_client_link
|
||||||
|
|
||||||
RNS.log("Client connected")
|
RNS.log("Client connected")
|
||||||
link.link_closed_callback(client_disconnected)
|
link.link_closed_callback(client_disconnected)
|
||||||
link.packet_callback(server_packet_received)
|
link.packet_callback(server_packet_received)
|
||||||
latest_client_link = link
|
latest_client_link = link
|
||||||
|
|
||||||
def client_disconnected(link):
|
def client_disconnected(link):
|
||||||
RNS.log("Client disconnected")
|
RNS.log("Client disconnected")
|
||||||
|
|
||||||
def server_packet_received(message, packet):
|
def server_packet_received(message, packet):
|
||||||
global latest_client_link
|
global latest_client_link
|
||||||
|
|
||||||
# When data is received over any active link,
|
# When data is received over any active link,
|
||||||
# it will all be directed to the last client
|
# it will all be directed to the last client
|
||||||
# that connected.
|
# that connected.
|
||||||
text = message.decode("utf-8")
|
text = message.decode("utf-8")
|
||||||
RNS.log("Received data on the link: "+text)
|
RNS.log("Received data on the link: "+text)
|
||||||
|
|
||||||
reply_text = "I received \""+text+"\" over the link"
|
reply_text = "I received \""+text+"\" over the link"
|
||||||
reply_data = reply_text.encode("utf-8")
|
reply_data = reply_text.encode("utf-8")
|
||||||
RNS.Packet(latest_client_link, reply_data).send()
|
RNS.Packet(latest_client_link, reply_data).send()
|
||||||
|
|
||||||
|
|
||||||
##########################################################
|
##########################################################
|
||||||
@ -96,112 +96,112 @@ server_link = None
|
|||||||
# 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):
|
||||||
# We need a binary representation of the destination
|
# We need a binary representation of the destination
|
||||||
# hash that was entered on the command line
|
# hash that was entered on the command line
|
||||||
try:
|
try:
|
||||||
if len(destination_hexhash) != 20:
|
if len(destination_hexhash) != 20:
|
||||||
raise ValueError("Destination length is invalid, must be 20 hexadecimal characters (10 bytes)")
|
raise ValueError("Destination length is invalid, must be 20 hexadecimal characters (10 bytes)")
|
||||||
destination_hash = bytes.fromhex(destination_hexhash)
|
destination_hash = bytes.fromhex(destination_hexhash)
|
||||||
except:
|
except:
|
||||||
RNS.log("Invalid destination entered. Check your input!\n")
|
RNS.log("Invalid destination entered. Check your input!\n")
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
# We must first initialise Reticulum
|
# We must first initialise Reticulum
|
||||||
reticulum = RNS.Reticulum(configpath)
|
reticulum = RNS.Reticulum(configpath)
|
||||||
|
|
||||||
# Check if we know a path to the destination
|
# Check if we know a path to the destination
|
||||||
if not RNS.Transport.hasPath(destination_hash):
|
if not RNS.Transport.hasPath(destination_hash):
|
||||||
RNS.log("Destination is not yet known. Requesting path and waiting for announce to arrive...")
|
RNS.log("Destination is not yet known. Requesting path and waiting for announce to arrive...")
|
||||||
RNS.Transport.requestPath(destination_hash)
|
RNS.Transport.requestPath(destination_hash)
|
||||||
while not RNS.Transport.hasPath(destination_hash):
|
while not RNS.Transport.hasPath(destination_hash):
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
# Recall the server identity
|
# Recall the server identity
|
||||||
server_identity = RNS.Identity.recall(destination_hash)
|
server_identity = RNS.Identity.recall(destination_hash)
|
||||||
|
|
||||||
# Inform the user that we'll begin connecting
|
# Inform the user that we'll begin connecting
|
||||||
RNS.log("Establishing link with server...")
|
RNS.log("Establishing link with server...")
|
||||||
|
|
||||||
# When the server identity is known, we set
|
# When the server identity is known, we set
|
||||||
# up a destination
|
# up a destination
|
||||||
server_destination = RNS.Destination(server_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "linkexample")
|
server_destination = RNS.Destination(server_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "linkexample")
|
||||||
|
|
||||||
# And create a link
|
# And create a link
|
||||||
link = RNS.Link(server_destination)
|
link = RNS.Link(server_destination)
|
||||||
|
|
||||||
# We set a callback that will get executed
|
# We set a callback that will get executed
|
||||||
# every time a packet is received over the
|
# every time a packet is received over the
|
||||||
# link
|
# link
|
||||||
link.packet_callback(client_packet_received)
|
link.packet_callback(client_packet_received)
|
||||||
|
|
||||||
# We'll also set up functions to inform the
|
# We'll also set up functions to inform the
|
||||||
# user when the link is established or closed
|
# user when the link is established or closed
|
||||||
link.link_established_callback(link_established)
|
link.link_established_callback(link_established)
|
||||||
link.link_closed_callback(link_closed)
|
link.link_closed_callback(link_closed)
|
||||||
|
|
||||||
# Everything is set up, so let's enter a loop
|
# Everything is set up, so let's enter a loop
|
||||||
# for the user to interact with the example
|
# for the user to interact with the example
|
||||||
client_loop()
|
client_loop()
|
||||||
|
|
||||||
def client_loop():
|
def client_loop():
|
||||||
global server_link
|
global server_link
|
||||||
|
|
||||||
# Wait for the link to become active
|
# Wait for the link to become active
|
||||||
while not server_link:
|
while not server_link:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
should_quit = False
|
should_quit = False
|
||||||
while not should_quit:
|
while not should_quit:
|
||||||
try:
|
try:
|
||||||
print("> ", end=" ")
|
print("> ", end=" ")
|
||||||
text = input()
|
text = input()
|
||||||
|
|
||||||
# Check if we should quit the example
|
# Check if we should quit the example
|
||||||
if text == "quit" or text == "q" or text == "exit":
|
if text == "quit" or text == "q" or text == "exit":
|
||||||
should_quit = True
|
should_quit = True
|
||||||
server_link.teardown()
|
server_link.teardown()
|
||||||
|
|
||||||
# If not, send the entered text over the link
|
# If not, send the entered text over the link
|
||||||
if text != "":
|
if text != "":
|
||||||
data = text.encode("utf-8")
|
data = text.encode("utf-8")
|
||||||
RNS.Packet(server_link, data).send()
|
RNS.Packet(server_link, data).send()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
should_quit = True
|
should_quit = True
|
||||||
server_link.teardown()
|
server_link.teardown()
|
||||||
|
|
||||||
# This function is called when a link
|
# This function is called when a link
|
||||||
# has been established with the server
|
# has been established with the server
|
||||||
def link_established(link):
|
def link_established(link):
|
||||||
# We store a reference to the link
|
# We store a reference to the link
|
||||||
# instance for later use
|
# instance for later use
|
||||||
global server_link
|
global server_link
|
||||||
server_link = link
|
server_link = link
|
||||||
|
|
||||||
# Inform the user that the server is
|
# Inform the user that the server is
|
||||||
# connected
|
# connected
|
||||||
RNS.log("Link established with server, enter some text to send, or \"quit\" to quit")
|
RNS.log("Link established with server, enter some text to send, or \"quit\" to quit")
|
||||||
|
|
||||||
# When a link is closed, we'll inform the
|
# When a link is closed, we'll inform the
|
||||||
# user, and exit the program
|
# user, and exit the program
|
||||||
def link_closed(link):
|
def link_closed(link):
|
||||||
if link.teardown_reason == RNS.Link.TIMEOUT:
|
if link.teardown_reason == RNS.Link.TIMEOUT:
|
||||||
RNS.log("The link timed out, exiting now")
|
RNS.log("The link timed out, exiting now")
|
||||||
elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED:
|
elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED:
|
||||||
RNS.log("The link was closed by the server, exiting now")
|
RNS.log("The link was closed by the server, exiting now")
|
||||||
else:
|
else:
|
||||||
RNS.log("Link closed, exiting now")
|
RNS.log("Link closed, exiting now")
|
||||||
|
|
||||||
RNS.Reticulum.exit_handler()
|
RNS.Reticulum.exit_handler()
|
||||||
time.sleep(1.5)
|
time.sleep(1.5)
|
||||||
os._exit(0)
|
os._exit(0)
|
||||||
|
|
||||||
# When a packet is received over the link, we
|
# When a packet is received over the link, we
|
||||||
# simply print out the data.
|
# simply print out the data.
|
||||||
def client_packet_received(message, packet):
|
def client_packet_received(message, packet):
|
||||||
text = message.decode("utf-8")
|
text = message.decode("utf-8")
|
||||||
RNS.log("Received data on the link: "+text)
|
RNS.log("Received data on the link: "+text)
|
||||||
print("> ", end=" ")
|
print("> ", end=" ")
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
##########################################################
|
##########################################################
|
||||||
@ -212,28 +212,28 @@ def client_packet_received(message, packet):
|
|||||||
# and parses input of from the user, and then
|
# and parses input of from the user, and then
|
||||||
# starts up the desired program mode.
|
# starts up the desired program mode.
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
parser = argparse.ArgumentParser(description="Simple link example")
|
parser = argparse.ArgumentParser(description="Simple link example")
|
||||||
parser.add_argument("-s", "--server", action="store_true", help="wait for incoming link requests from clients")
|
parser.add_argument("-s", "--server", action="store_true", help="wait for incoming link requests from clients")
|
||||||
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
||||||
parser.add_argument("destination", nargs="?", default=None, help="hexadecimal hash of the server destination", type=str)
|
parser.add_argument("destination", nargs="?", default=None, help="hexadecimal hash of the server destination", type=str)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.config:
|
if args.config:
|
||||||
configarg = args.config
|
configarg = args.config
|
||||||
else:
|
else:
|
||||||
configarg = None
|
configarg = None
|
||||||
|
|
||||||
if args.server:
|
if args.server:
|
||||||
server(configarg)
|
server(configarg)
|
||||||
else:
|
else:
|
||||||
if (args.destination == None):
|
if (args.destination == None):
|
||||||
print("")
|
print("")
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
print("")
|
print("")
|
||||||
else:
|
else:
|
||||||
client(args.destination, configarg)
|
client(args.destination, configarg)
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("")
|
print("")
|
||||||
exit()
|
exit()
|
@ -15,45 +15,45 @@ APP_NAME = "example_utilitites"
|
|||||||
|
|
||||||
# This initialisation is executed when the program is started
|
# This initialisation is executed when the program is started
|
||||||
def program_setup(configpath):
|
def program_setup(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 example
|
# Randomly create a new identity for our example
|
||||||
identity = RNS.Identity()
|
identity = RNS.Identity()
|
||||||
|
|
||||||
# Using the identity we just created, we create a destination.
|
# Using the identity we just created, we create a destination.
|
||||||
# Destinations are endpoints in Reticulum, that can be addressed
|
# Destinations are endpoints in Reticulum, that can be addressed
|
||||||
# and communicated with. Destinations can also announce their
|
# and communicated with. Destinations can also announce their
|
||||||
# existence, which will let the network know they are reachable
|
# existence, which will let the network know they are reachable
|
||||||
# and autoomatically create paths to them, from anywhere else
|
# and autoomatically create paths to them, from anywhere else
|
||||||
# in the network.
|
# in the network.
|
||||||
destination = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "minimalsample")
|
destination = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "minimalsample")
|
||||||
|
|
||||||
# We configure the destination to automatically prove all
|
# We configure the destination to automatically prove all
|
||||||
# packets adressed to it. By doing this, RNS will automatically
|
# packets adressed to it. By doing this, RNS will automatically
|
||||||
# generate a proof for each incoming packet and transmit it
|
# generate a proof for each incoming packet and transmit it
|
||||||
# back to the sender of that packet. This will let anyone that
|
# back to the sender of that packet. This will let anyone that
|
||||||
# tries to communicate with the destination know whether their
|
# tries to communicate with the destination know whether their
|
||||||
# communication was received correctly.
|
# communication was received correctly.
|
||||||
destination.set_proof_strategy(RNS.Destination.PROVE_ALL)
|
destination.set_proof_strategy(RNS.Destination.PROVE_ALL)
|
||||||
|
|
||||||
# Everything's ready!
|
# Everything's ready!
|
||||||
# Let's hand over control to the announce loop
|
# Let's hand over control to the announce loop
|
||||||
announceLoop(destination)
|
announceLoop(destination)
|
||||||
|
|
||||||
|
|
||||||
def announceLoop(destination):
|
def announceLoop(destination):
|
||||||
# Let the user know that everything is ready
|
# Let the user know that everything is ready
|
||||||
RNS.log("Minimal example "+RNS.prettyhexrep(destination.hash)+" running, hit enter to manually send an announce (Ctrl-C to quit)")
|
RNS.log("Minimal example "+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 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
|
# destination on the network, which will let clients
|
||||||
# know how to create messages directed towards it.
|
# know how to create messages directed towards it.
|
||||||
while True:
|
while True:
|
||||||
entered = input()
|
entered = input()
|
||||||
destination.announce()
|
destination.announce()
|
||||||
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
|
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
|
||||||
|
|
||||||
|
|
||||||
##########################################################
|
##########################################################
|
||||||
@ -64,18 +64,18 @@ def announceLoop(destination):
|
|||||||
# and parses input from the user, and then starts
|
# and parses input from the user, and then starts
|
||||||
# the desired program mode.
|
# the desired program mode.
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
parser = argparse.ArgumentParser(description="Bare minimum example to start Reticulum and create a destination")
|
parser = argparse.ArgumentParser(description="Bare minimum example to start Reticulum and create a destination")
|
||||||
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.config:
|
if args.config:
|
||||||
configarg = args.config
|
configarg = args.config
|
||||||
else:
|
else:
|
||||||
configarg = None
|
configarg = None
|
||||||
|
|
||||||
program_setup(configarg)
|
program_setup(configarg)
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("")
|
print("")
|
||||||
exit()
|
exit()
|
@ -10,228 +10,228 @@ from cryptography.hazmat.primitives.asymmetric import rsa
|
|||||||
from cryptography.hazmat.primitives.asymmetric import padding
|
from cryptography.hazmat.primitives.asymmetric import padding
|
||||||
|
|
||||||
class Callbacks:
|
class Callbacks:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.link_established = None
|
self.link_established = None
|
||||||
self.packet = None
|
self.packet = None
|
||||||
self.proof_requested = None
|
self.proof_requested = None
|
||||||
|
|
||||||
class Destination:
|
class Destination:
|
||||||
KEYSIZE = RNS.Identity.KEYSIZE;
|
KEYSIZE = RNS.Identity.KEYSIZE;
|
||||||
PADDINGSIZE= RNS.Identity.PADDINGSIZE;
|
PADDINGSIZE= RNS.Identity.PADDINGSIZE;
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
SINGLE = 0x00
|
SINGLE = 0x00
|
||||||
GROUP = 0x01
|
GROUP = 0x01
|
||||||
PLAIN = 0x02
|
PLAIN = 0x02
|
||||||
LINK = 0x03
|
LINK = 0x03
|
||||||
types = [SINGLE, GROUP, PLAIN, LINK]
|
types = [SINGLE, GROUP, PLAIN, LINK]
|
||||||
|
|
||||||
PROVE_NONE = 0x21
|
PROVE_NONE = 0x21
|
||||||
PROVE_APP = 0x22
|
PROVE_APP = 0x22
|
||||||
PROVE_ALL = 0x23
|
PROVE_ALL = 0x23
|
||||||
proof_strategies = [PROVE_NONE, PROVE_APP, PROVE_ALL]
|
proof_strategies = [PROVE_NONE, PROVE_APP, PROVE_ALL]
|
||||||
|
|
||||||
IN = 0x11;
|
IN = 0x11;
|
||||||
OUT = 0x12;
|
OUT = 0x12;
|
||||||
directions = [IN, OUT]
|
directions = [IN, OUT]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getDestinationName(app_name, *aspects):
|
def getDestinationName(app_name, *aspects):
|
||||||
# Check input values and build name string
|
# Check input values and build name string
|
||||||
if "." in app_name: raise ValueError("Dots can't be used in app names")
|
if "." in app_name: raise ValueError("Dots can't be used in app names")
|
||||||
|
|
||||||
name = app_name
|
name = app_name
|
||||||
for aspect in aspects:
|
for aspect in aspects:
|
||||||
if "." in aspect: raise ValueError("Dots can't be used in aspects")
|
if "." in aspect: raise ValueError("Dots can't be used in aspects")
|
||||||
name = name + "." + aspect
|
name = name + "." + aspect
|
||||||
|
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getDestinationHash(app_name, *aspects):
|
def getDestinationHash(app_name, *aspects):
|
||||||
name = Destination.getDestinationName(app_name, *aspects)
|
name = Destination.getDestinationName(app_name, *aspects)
|
||||||
|
|
||||||
# Create a digest for the destination
|
# Create a digest for the destination
|
||||||
digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
|
digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
|
||||||
digest.update(name.encode("UTF-8"))
|
digest.update(name.encode("UTF-8"))
|
||||||
|
|
||||||
return digest.finalize()[:10]
|
return digest.finalize()[:10]
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, identity, direction, type, app_name, *aspects):
|
def __init__(self, identity, direction, type, app_name, *aspects):
|
||||||
# Check input values and build name string
|
# Check input values and build name string
|
||||||
if "." in app_name: raise ValueError("Dots can't be used in app names")
|
if "." in app_name: raise ValueError("Dots can't be used in app names")
|
||||||
if not type in Destination.types: raise ValueError("Unknown destination type")
|
if not type in Destination.types: raise ValueError("Unknown destination type")
|
||||||
if not direction in Destination.directions: raise ValueError("Unknown destination direction")
|
if not direction in Destination.directions: raise ValueError("Unknown destination direction")
|
||||||
self.callbacks = Callbacks()
|
self.callbacks = Callbacks()
|
||||||
self.type = type
|
self.type = type
|
||||||
self.direction = direction
|
self.direction = direction
|
||||||
self.proof_strategy = Destination.PROVE_NONE
|
self.proof_strategy = Destination.PROVE_NONE
|
||||||
self.mtu = 0
|
self.mtu = 0
|
||||||
|
|
||||||
self.links = []
|
self.links = []
|
||||||
|
|
||||||
if identity != None and type == Destination.SINGLE:
|
if identity != None and type == Destination.SINGLE:
|
||||||
aspects = aspects+(identity.hexhash,)
|
aspects = aspects+(identity.hexhash,)
|
||||||
|
|
||||||
if identity == None and direction == Destination.IN and self.type != Destination.PLAIN:
|
if identity == None and direction == Destination.IN and self.type != Destination.PLAIN:
|
||||||
identity = RNS.Identity()
|
identity = RNS.Identity()
|
||||||
aspects = aspects+(identity.hexhash,)
|
aspects = aspects+(identity.hexhash,)
|
||||||
|
|
||||||
self.identity = identity
|
self.identity = identity
|
||||||
|
|
||||||
self.name = Destination.getDestinationName(app_name, *aspects)
|
self.name = Destination.getDestinationName(app_name, *aspects)
|
||||||
self.hash = Destination.getDestinationHash(app_name, *aspects)
|
self.hash = Destination.getDestinationHash(app_name, *aspects)
|
||||||
self.hexhash = self.hash.hex()
|
self.hexhash = self.hash.hex()
|
||||||
|
|
||||||
self.callback = None
|
self.callback = None
|
||||||
self.proofcallback = None
|
self.proofcallback = None
|
||||||
|
|
||||||
RNS.Transport.registerDestination(self)
|
RNS.Transport.registerDestination(self)
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "<"+self.name+"/"+self.hexhash+">"
|
return "<"+self.name+"/"+self.hexhash+">"
|
||||||
|
|
||||||
|
|
||||||
def link_established_callback(self, callback):
|
def link_established_callback(self, callback):
|
||||||
self.callbacks.link_established = callback
|
self.callbacks.link_established = callback
|
||||||
|
|
||||||
def packet_callback(self, callback):
|
def packet_callback(self, callback):
|
||||||
self.callbacks.packet = callback
|
self.callbacks.packet = callback
|
||||||
|
|
||||||
def proof_requested_callback(self, callback):
|
def proof_requested_callback(self, callback):
|
||||||
self.callbacks.proof_requested = callback
|
self.callbacks.proof_requested = callback
|
||||||
|
|
||||||
def set_proof_strategy(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:
|
||||||
self.proof_strategy = proof_strategy
|
self.proof_strategy = proof_strategy
|
||||||
|
|
||||||
def receive(self, packet):
|
def receive(self, packet):
|
||||||
plaintext = self.decrypt(packet.data)
|
plaintext = self.decrypt(packet.data)
|
||||||
if plaintext != None:
|
if plaintext != None:
|
||||||
if packet.packet_type == RNS.Packet.LINKREQUEST:
|
if packet.packet_type == RNS.Packet.LINKREQUEST:
|
||||||
self.incomingLinkRequest(plaintext, packet)
|
self.incomingLinkRequest(plaintext, packet)
|
||||||
|
|
||||||
if packet.packet_type == RNS.Packet.DATA:
|
if packet.packet_type == RNS.Packet.DATA:
|
||||||
if self.callbacks.packet != None:
|
if self.callbacks.packet != None:
|
||||||
self.callbacks.packet(plaintext, packet)
|
self.callbacks.packet(plaintext, packet)
|
||||||
|
|
||||||
def incomingLinkRequest(self, data, packet):
|
def incomingLinkRequest(self, data, packet):
|
||||||
link = RNS.Link.validateRequest(self, data, packet)
|
link = RNS.Link.validateRequest(self, data, packet)
|
||||||
if link != None:
|
if link != None:
|
||||||
self.links.append(link)
|
self.links.append(link)
|
||||||
|
|
||||||
def createKeys(self):
|
def createKeys(self):
|
||||||
if self.type == Destination.PLAIN:
|
if self.type == Destination.PLAIN:
|
||||||
raise TypeError("A plain destination does not hold any keys")
|
raise TypeError("A plain destination does not hold any keys")
|
||||||
|
|
||||||
if self.type == Destination.SINGLE:
|
if self.type == Destination.SINGLE:
|
||||||
raise TypeError("A single destination holds keys through an Identity instance")
|
raise TypeError("A single destination holds keys through an Identity instance")
|
||||||
|
|
||||||
if self.type == Destination.GROUP:
|
if self.type == Destination.GROUP:
|
||||||
self.prv_bytes = Fernet.generate_key()
|
self.prv_bytes = Fernet.generate_key()
|
||||||
self.prv = Fernet(self.prv_bytes)
|
self.prv = Fernet(self.prv_bytes)
|
||||||
|
|
||||||
|
|
||||||
def getPrivateKey(self):
|
def getPrivateKey(self):
|
||||||
if self.type == Destination.PLAIN:
|
if self.type == Destination.PLAIN:
|
||||||
raise TypeError("A plain destination does not hold any keys")
|
raise TypeError("A plain destination does not hold any keys")
|
||||||
elif self.type == Destination.SINGLE:
|
elif self.type == Destination.SINGLE:
|
||||||
raise TypeError("A single destination holds keys through an Identity instance")
|
raise TypeError("A single destination holds keys through an Identity instance")
|
||||||
else:
|
else:
|
||||||
return self.prv_bytes
|
return self.prv_bytes
|
||||||
|
|
||||||
|
|
||||||
def loadPrivateKey(self, key):
|
def loadPrivateKey(self, key):
|
||||||
if self.type == Destination.PLAIN:
|
if self.type == Destination.PLAIN:
|
||||||
raise TypeError("A plain destination does not hold any keys")
|
raise TypeError("A plain destination does not hold any keys")
|
||||||
|
|
||||||
if self.type == Destination.SINGLE:
|
if self.type == Destination.SINGLE:
|
||||||
raise TypeError("A single destination holds keys through an Identity instance")
|
raise TypeError("A single destination holds keys through an Identity instance")
|
||||||
|
|
||||||
if self.type == Destination.GROUP:
|
if self.type == Destination.GROUP:
|
||||||
self.prv_bytes = key
|
self.prv_bytes = key
|
||||||
self.prv = Fernet(self.prv_bytes)
|
self.prv = Fernet(self.prv_bytes)
|
||||||
|
|
||||||
def loadPublicKey(self, key):
|
def loadPublicKey(self, key):
|
||||||
if self.type != Destination.SINGLE:
|
if self.type != Destination.SINGLE:
|
||||||
raise TypeError("Only the \"single\" destination type can hold a public key")
|
raise TypeError("Only the \"single\" destination type can hold a public key")
|
||||||
else:
|
else:
|
||||||
raise TypeError("A single destination holds keys through an Identity instance")
|
raise TypeError("A single destination holds keys through an Identity instance")
|
||||||
|
|
||||||
|
|
||||||
def encrypt(self, plaintext):
|
def encrypt(self, plaintext):
|
||||||
if self.type == Destination.PLAIN:
|
if self.type == Destination.PLAIN:
|
||||||
return plaintext
|
return plaintext
|
||||||
|
|
||||||
if self.type == Destination.SINGLE and self.identity != None:
|
if self.type == Destination.SINGLE and self.identity != None:
|
||||||
return self.identity.encrypt(plaintext)
|
return self.identity.encrypt(plaintext)
|
||||||
|
|
||||||
if self.type == Destination.GROUP:
|
if self.type == Destination.GROUP:
|
||||||
if hasattr(self, "prv") and self.prv != None:
|
if hasattr(self, "prv") and self.prv != None:
|
||||||
try:
|
try:
|
||||||
return base64.urlsafe_b64decode(self.prv.encrypt(plaintext))
|
return base64.urlsafe_b64decode(self.prv.encrypt(plaintext))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("The GROUP destination could not encrypt data", RNS.LOG_ERROR)
|
RNS.log("The GROUP destination could not encrypt data", RNS.LOG_ERROR)
|
||||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
else:
|
else:
|
||||||
raise ValueError("No private key held by GROUP destination. Did you create or load one?")
|
raise ValueError("No private key held by GROUP destination. Did you create or load one?")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def decrypt(self, ciphertext):
|
def decrypt(self, ciphertext):
|
||||||
if self.type == Destination.PLAIN:
|
if self.type == Destination.PLAIN:
|
||||||
return ciphertext
|
return ciphertext
|
||||||
|
|
||||||
if self.type == Destination.SINGLE and self.identity != None:
|
if self.type == Destination.SINGLE and self.identity != None:
|
||||||
return self.identity.decrypt(ciphertext)
|
return self.identity.decrypt(ciphertext)
|
||||||
|
|
||||||
if self.type == Destination.GROUP:
|
if self.type == Destination.GROUP:
|
||||||
if hasattr(self, "prv") and self.prv != None:
|
if hasattr(self, "prv") and self.prv != None:
|
||||||
try:
|
try:
|
||||||
return self.prv.decrypt(base64.urlsafe_b64encode(ciphertext))
|
return self.prv.decrypt(base64.urlsafe_b64encode(ciphertext))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("The GROUP destination could not decrypt data", RNS.LOG_ERROR)
|
RNS.log("The GROUP destination could not decrypt data", RNS.LOG_ERROR)
|
||||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
else:
|
else:
|
||||||
raise ValueError("No private key held by GROUP destination. Did you create or load one?")
|
raise ValueError("No private key held by GROUP destination. Did you create or load one?")
|
||||||
|
|
||||||
|
|
||||||
def sign(self, message):
|
def sign(self, message):
|
||||||
if self.type == Destination.SINGLE and self.identity != None:
|
if self.type == Destination.SINGLE and self.identity != None:
|
||||||
return self.identity.sign(message)
|
return self.identity.sign(message)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
# Creates an announce packet for this destination.
|
# Creates an announce packet for this destination.
|
||||||
# Application specific data can be added to the announce.
|
# Application specific data can be added to the announce.
|
||||||
def announce(self, app_data=None, path_response=False):
|
def announce(self, app_data=None, path_response=False):
|
||||||
destination_hash = self.hash
|
destination_hash = self.hash
|
||||||
random_hash = RNS.Identity.getRandomHash()
|
random_hash = RNS.Identity.getRandomHash()
|
||||||
|
|
||||||
signed_data = self.hash+self.identity.getPublicKey()+random_hash
|
signed_data = self.hash+self.identity.getPublicKey()+random_hash
|
||||||
if app_data != None:
|
if app_data != None:
|
||||||
signed_data += app_data
|
signed_data += app_data
|
||||||
|
|
||||||
signature = self.identity.sign(signed_data)
|
signature = self.identity.sign(signed_data)
|
||||||
|
|
||||||
# TODO: Check if this could be optimised by only
|
# TODO: Check if this could be optimised by only
|
||||||
# carrying the hash in the destination field, not
|
# carrying the hash in the destination field, not
|
||||||
# also redundantly inside the signed blob as here
|
# also redundantly inside the signed blob as here
|
||||||
announce_data = self.hash+self.identity.getPublicKey()+random_hash+signature
|
announce_data = self.hash+self.identity.getPublicKey()+random_hash+signature
|
||||||
|
|
||||||
if app_data != None:
|
if app_data != None:
|
||||||
announce_data += app_data
|
announce_data += app_data
|
||||||
|
|
||||||
if path_response:
|
if path_response:
|
||||||
announce_context = RNS.Packet.PATH_RESPONSE
|
announce_context = RNS.Packet.PATH_RESPONSE
|
||||||
else:
|
else:
|
||||||
announce_context = RNS.Packet.NONE
|
announce_context = RNS.Packet.NONE
|
||||||
|
|
||||||
RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE, context = announce_context).send()
|
RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE, context = announce_context).send()
|
||||||
|
|
||||||
|
524
RNS/Identity.py
524
RNS/Identity.py
@ -15,313 +15,313 @@ from cryptography.hazmat.primitives.asymmetric import padding
|
|||||||
|
|
||||||
class Identity:
|
class Identity:
|
||||||
#KEYSIZE = 1536
|
#KEYSIZE = 1536
|
||||||
KEYSIZE = 1024
|
KEYSIZE = 1024
|
||||||
DERKEYSIZE = KEYSIZE+272
|
DERKEYSIZE = KEYSIZE+272
|
||||||
|
|
||||||
# Non-configurable constants
|
# Non-configurable constants
|
||||||
PADDINGSIZE = 336 # In bits
|
PADDINGSIZE = 336 # In bits
|
||||||
HASHLENGTH = 256 # In bits
|
HASHLENGTH = 256 # In bits
|
||||||
SIGLENGTH = KEYSIZE
|
SIGLENGTH = KEYSIZE
|
||||||
|
|
||||||
ENCRYPT_CHUNKSIZE = (KEYSIZE-PADDINGSIZE)//8
|
ENCRYPT_CHUNKSIZE = (KEYSIZE-PADDINGSIZE)//8
|
||||||
DECRYPT_CHUNKSIZE = KEYSIZE//8
|
DECRYPT_CHUNKSIZE = KEYSIZE//8
|
||||||
|
|
||||||
TRUNCATED_HASHLENGTH = 80 # In bits
|
TRUNCATED_HASHLENGTH = 80 # In bits
|
||||||
|
|
||||||
# Storage
|
# Storage
|
||||||
known_destinations = {}
|
known_destinations = {}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def remember(packet_hash, destination_hash, public_key, app_data = None):
|
def remember(packet_hash, destination_hash, public_key, app_data = None):
|
||||||
Identity.known_destinations[destination_hash] = [time.time(), packet_hash, public_key, app_data]
|
Identity.known_destinations[destination_hash] = [time.time(), packet_hash, public_key, app_data]
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def recall(destination_hash):
|
def recall(destination_hash):
|
||||||
RNS.log("Searching for "+RNS.prettyhexrep(destination_hash)+"...", RNS.LOG_EXTREME)
|
RNS.log("Searching for "+RNS.prettyhexrep(destination_hash)+"...", RNS.LOG_EXTREME)
|
||||||
if destination_hash in Identity.known_destinations:
|
if destination_hash in Identity.known_destinations:
|
||||||
identity_data = Identity.known_destinations[destination_hash]
|
identity_data = Identity.known_destinations[destination_hash]
|
||||||
identity = Identity(public_only=True)
|
identity = Identity(public_only=True)
|
||||||
identity.loadPublicKey(identity_data[2])
|
identity.loadPublicKey(identity_data[2])
|
||||||
RNS.log("Found "+RNS.prettyhexrep(destination_hash)+" in known destinations", RNS.LOG_EXTREME)
|
RNS.log("Found "+RNS.prettyhexrep(destination_hash)+" in known destinations", RNS.LOG_EXTREME)
|
||||||
return identity
|
return identity
|
||||||
else:
|
else:
|
||||||
RNS.log("Could not find "+RNS.prettyhexrep(destination_hash)+" in known destinations", RNS.LOG_EXTREME)
|
RNS.log("Could not find "+RNS.prettyhexrep(destination_hash)+" in known destinations", RNS.LOG_EXTREME)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def saveKnownDestinations():
|
def saveKnownDestinations():
|
||||||
RNS.log("Saving known destinations to storage...", RNS.LOG_VERBOSE)
|
RNS.log("Saving known destinations to storage...", RNS.LOG_VERBOSE)
|
||||||
file = open(RNS.Reticulum.storagepath+"/known_destinations","wb")
|
file = open(RNS.Reticulum.storagepath+"/known_destinations","wb")
|
||||||
umsgpack.dump(Identity.known_destinations, file)
|
umsgpack.dump(Identity.known_destinations, file)
|
||||||
file.close()
|
file.close()
|
||||||
RNS.log("Done saving known destinations to storage", RNS.LOG_VERBOSE)
|
RNS.log("Done saving known destinations to storage", RNS.LOG_VERBOSE)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def loadKnownDestinations():
|
def loadKnownDestinations():
|
||||||
if os.path.isfile(RNS.Reticulum.storagepath+"/known_destinations"):
|
if os.path.isfile(RNS.Reticulum.storagepath+"/known_destinations"):
|
||||||
try:
|
try:
|
||||||
file = open(RNS.Reticulum.storagepath+"/known_destinations","rb")
|
file = open(RNS.Reticulum.storagepath+"/known_destinations","rb")
|
||||||
Identity.known_destinations = umsgpack.load(file)
|
Identity.known_destinations = umsgpack.load(file)
|
||||||
file.close()
|
file.close()
|
||||||
RNS.log("Loaded "+str(len(Identity.known_destinations))+" known destination from storage", RNS.LOG_VERBOSE)
|
RNS.log("Loaded "+str(len(Identity.known_destinations))+" known destination from storage", RNS.LOG_VERBOSE)
|
||||||
except:
|
except:
|
||||||
RNS.log("Error loading known destinations from disk, file will be recreated on exit", RNS.LOG_ERROR)
|
RNS.log("Error loading known destinations from disk, file will be recreated on exit", RNS.LOG_ERROR)
|
||||||
else:
|
else:
|
||||||
RNS.log("Destinations file does not exist, so no known destinations loaded", RNS.LOG_VERBOSE)
|
RNS.log("Destinations file does not exist, so no known destinations loaded", RNS.LOG_VERBOSE)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fullHash(data):
|
def fullHash(data):
|
||||||
digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
|
digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
|
||||||
digest.update(data)
|
digest.update(data)
|
||||||
|
|
||||||
return digest.finalize()
|
return digest.finalize()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def truncatedHash(data):
|
def truncatedHash(data):
|
||||||
# TODO: Remove
|
# TODO: Remove
|
||||||
# digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
|
# digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
|
||||||
# digest.update(data)
|
# digest.update(data)
|
||||||
|
|
||||||
return Identity.fullHash(data)[:(Identity.TRUNCATED_HASHLENGTH//8)]
|
return Identity.fullHash(data)[:(Identity.TRUNCATED_HASHLENGTH//8)]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getRandomHash():
|
def getRandomHash():
|
||||||
return Identity.truncatedHash(os.urandom(10))
|
return Identity.truncatedHash(os.urandom(10))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validateAnnounce(packet):
|
def validateAnnounce(packet):
|
||||||
if packet.packet_type == RNS.Packet.ANNOUNCE:
|
if packet.packet_type == RNS.Packet.ANNOUNCE:
|
||||||
RNS.log("Validating announce from "+RNS.prettyhexrep(packet.destination_hash), RNS.LOG_DEBUG)
|
RNS.log("Validating announce from "+RNS.prettyhexrep(packet.destination_hash), RNS.LOG_DEBUG)
|
||||||
destination_hash = packet.destination_hash
|
destination_hash = packet.destination_hash
|
||||||
public_key = packet.data[10:Identity.DERKEYSIZE//8+10]
|
public_key = packet.data[10:Identity.DERKEYSIZE//8+10]
|
||||||
random_hash = packet.data[Identity.DERKEYSIZE//8+10:Identity.DERKEYSIZE//8+20]
|
random_hash = packet.data[Identity.DERKEYSIZE//8+10:Identity.DERKEYSIZE//8+20]
|
||||||
signature = packet.data[Identity.DERKEYSIZE//8+20:Identity.DERKEYSIZE//8+20+Identity.KEYSIZE//8]
|
signature = packet.data[Identity.DERKEYSIZE//8+20:Identity.DERKEYSIZE//8+20+Identity.KEYSIZE//8]
|
||||||
app_data = b""
|
app_data = b""
|
||||||
if len(packet.data) > Identity.DERKEYSIZE//8+20+Identity.KEYSIZE//8:
|
if len(packet.data) > Identity.DERKEYSIZE//8+20+Identity.KEYSIZE//8:
|
||||||
app_data = packet.data[Identity.DERKEYSIZE//8+20+Identity.KEYSIZE//8:]
|
app_data = packet.data[Identity.DERKEYSIZE//8+20+Identity.KEYSIZE//8:]
|
||||||
|
|
||||||
signed_data = destination_hash+public_key+random_hash+app_data
|
signed_data = destination_hash+public_key+random_hash+app_data
|
||||||
|
|
||||||
announced_identity = Identity(public_only=True)
|
announced_identity = Identity(public_only=True)
|
||||||
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.Identity.remember(packet.getHash(), destination_hash, public_key)
|
RNS.Identity.remember(packet.getHash(), destination_hash, public_key)
|
||||||
RNS.log("Stored valid announce from "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG)
|
RNS.log("Stored valid announce from "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG)
|
||||||
del announced_identity
|
del announced_identity
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
RNS.log("Received invalid announce", RNS.LOG_DEBUG)
|
RNS.log("Received invalid announce", RNS.LOG_DEBUG)
|
||||||
del announced_identity
|
del announced_identity
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def exitHandler():
|
def exitHandler():
|
||||||
Identity.saveKnownDestinations()
|
Identity.saveKnownDestinations()
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_file(path):
|
def from_file(path):
|
||||||
identity = Identity(public_only=True)
|
identity = Identity(public_only=True)
|
||||||
if identity.load(path):
|
if identity.load(path):
|
||||||
return identity
|
return identity
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def __init__(self,public_only=False):
|
def __init__(self,public_only=False):
|
||||||
# Initialize keys to none
|
# Initialize keys to none
|
||||||
self.prv = None
|
self.prv = None
|
||||||
self.pub = None
|
self.pub = None
|
||||||
self.prv_bytes = None
|
self.prv_bytes = None
|
||||||
self.pub_bytes = None
|
self.pub_bytes = None
|
||||||
self.hash = None
|
self.hash = None
|
||||||
self.hexhash = None
|
self.hexhash = None
|
||||||
|
|
||||||
if not public_only:
|
if not public_only:
|
||||||
self.createKeys()
|
self.createKeys()
|
||||||
|
|
||||||
def createKeys(self):
|
def createKeys(self):
|
||||||
self.prv = rsa.generate_private_key(
|
self.prv = rsa.generate_private_key(
|
||||||
public_exponent=65337,
|
public_exponent=65337,
|
||||||
key_size=Identity.KEYSIZE,
|
key_size=Identity.KEYSIZE,
|
||||||
backend=default_backend()
|
backend=default_backend()
|
||||||
)
|
)
|
||||||
self.prv_bytes = self.prv.private_bytes(
|
self.prv_bytes = self.prv.private_bytes(
|
||||||
encoding=serialization.Encoding.DER,
|
encoding=serialization.Encoding.DER,
|
||||||
format=serialization.PrivateFormat.PKCS8,
|
format=serialization.PrivateFormat.PKCS8,
|
||||||
encryption_algorithm=serialization.NoEncryption()
|
encryption_algorithm=serialization.NoEncryption()
|
||||||
)
|
)
|
||||||
self.pub = self.prv.public_key()
|
self.pub = self.prv.public_key()
|
||||||
self.pub_bytes = self.pub.public_bytes(
|
self.pub_bytes = self.pub.public_bytes(
|
||||||
encoding=serialization.Encoding.DER,
|
encoding=serialization.Encoding.DER,
|
||||||
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||||
)
|
)
|
||||||
|
|
||||||
self.updateHashes()
|
self.updateHashes()
|
||||||
|
|
||||||
RNS.log("Identity keys created for "+RNS.prettyhexrep(self.hash), RNS.LOG_VERBOSE)
|
RNS.log("Identity keys created for "+RNS.prettyhexrep(self.hash), RNS.LOG_VERBOSE)
|
||||||
|
|
||||||
def getPrivateKey(self):
|
def getPrivateKey(self):
|
||||||
return self.prv_bytes
|
return self.prv_bytes
|
||||||
|
|
||||||
def getPublicKey(self):
|
def getPublicKey(self):
|
||||||
return self.pub_bytes
|
return self.pub_bytes
|
||||||
|
|
||||||
def loadPrivateKey(self, prv_bytes):
|
def loadPrivateKey(self, prv_bytes):
|
||||||
try:
|
try:
|
||||||
self.prv_bytes = prv_bytes
|
self.prv_bytes = prv_bytes
|
||||||
self.prv = serialization.load_der_private_key(
|
self.prv = serialization.load_der_private_key(
|
||||||
self.prv_bytes,
|
self.prv_bytes,
|
||||||
password=None,
|
password=None,
|
||||||
backend=default_backend()
|
backend=default_backend()
|
||||||
)
|
)
|
||||||
self.pub = self.prv.public_key()
|
self.pub = self.prv.public_key()
|
||||||
self.pub_bytes = self.pub.public_bytes(
|
self.pub_bytes = self.pub.public_bytes(
|
||||||
encoding=serialization.Encoding.DER,
|
encoding=serialization.Encoding.DER,
|
||||||
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||||
)
|
)
|
||||||
self.updateHashes()
|
self.updateHashes()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Failed to load identity key", RNS.LOG_ERROR)
|
RNS.log("Failed to load identity key", RNS.LOG_ERROR)
|
||||||
RNS.log("The contained exception was: "+str(e))
|
RNS.log("The contained exception was: "+str(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def loadPublicKey(self, key):
|
def loadPublicKey(self, key):
|
||||||
try:
|
try:
|
||||||
self.pub_bytes = key
|
self.pub_bytes = key
|
||||||
self.pub = load_der_public_key(self.pub_bytes, backend=default_backend())
|
self.pub = load_der_public_key(self.pub_bytes, backend=default_backend())
|
||||||
self.updateHashes()
|
self.updateHashes()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Error while loading public key, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Error while loading public key, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
def updateHashes(self):
|
def updateHashes(self):
|
||||||
self.hash = Identity.truncatedHash(self.pub_bytes)
|
self.hash = Identity.truncatedHash(self.pub_bytes)
|
||||||
self.hexhash = self.hash.hex()
|
self.hexhash = self.hash.hex()
|
||||||
|
|
||||||
def save(self, path):
|
def save(self, path):
|
||||||
try:
|
try:
|
||||||
with open(path, "wb") as key_file:
|
with open(path, "wb") as key_file:
|
||||||
key_file.write(self.prv_bytes)
|
key_file.write(self.prv_bytes)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Error while saving identity to "+str(path), RNS.LOG_ERROR)
|
RNS.log("Error while saving identity to "+str(path), RNS.LOG_ERROR)
|
||||||
RNS.log("The contained exception was: "+str(e))
|
RNS.log("The contained exception was: "+str(e))
|
||||||
|
|
||||||
def load(self, path):
|
def load(self, path):
|
||||||
try:
|
try:
|
||||||
with open(path, "rb") as key_file:
|
with open(path, "rb") as key_file:
|
||||||
prv_bytes = key_file.read()
|
prv_bytes = key_file.read()
|
||||||
return self.loadPrivateKey(prv_bytes)
|
return self.loadPrivateKey(prv_bytes)
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Error while loading identity from "+str(path), RNS.LOG_ERROR)
|
RNS.log("Error while loading identity from "+str(path), RNS.LOG_ERROR)
|
||||||
RNS.log("The contained exception was: "+str(e))
|
RNS.log("The contained exception was: "+str(e))
|
||||||
|
|
||||||
def encrypt(self, plaintext):
|
def encrypt(self, plaintext):
|
||||||
if self.pub != None:
|
if self.pub != None:
|
||||||
chunksize = Identity.ENCRYPT_CHUNKSIZE
|
chunksize = Identity.ENCRYPT_CHUNKSIZE
|
||||||
chunks = int(math.ceil(len(plaintext)/(float(chunksize))))
|
chunks = int(math.ceil(len(plaintext)/(float(chunksize))))
|
||||||
|
|
||||||
ciphertext = b"";
|
ciphertext = b"";
|
||||||
for chunk in range(chunks):
|
for chunk in range(chunks):
|
||||||
start = chunk*chunksize
|
start = chunk*chunksize
|
||||||
end = (chunk+1)*chunksize
|
end = (chunk+1)*chunksize
|
||||||
if (chunk+1)*chunksize > len(plaintext):
|
if (chunk+1)*chunksize > len(plaintext):
|
||||||
end = len(plaintext)
|
end = len(plaintext)
|
||||||
|
|
||||||
ciphertext += self.pub.encrypt(
|
ciphertext += self.pub.encrypt(
|
||||||
plaintext[start:end],
|
plaintext[start:end],
|
||||||
padding.OAEP(
|
padding.OAEP(
|
||||||
mgf=padding.MGF1(algorithm=hashes.SHA1()),
|
mgf=padding.MGF1(algorithm=hashes.SHA1()),
|
||||||
algorithm=hashes.SHA1(),
|
algorithm=hashes.SHA1(),
|
||||||
label=None
|
label=None
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return ciphertext
|
return ciphertext
|
||||||
else:
|
else:
|
||||||
raise KeyError("Encryption failed because identity does not hold a public key")
|
raise KeyError("Encryption failed because identity does not hold a public key")
|
||||||
|
|
||||||
|
|
||||||
def decrypt(self, ciphertext):
|
def decrypt(self, ciphertext):
|
||||||
if self.prv != None:
|
if self.prv != None:
|
||||||
plaintext = None
|
plaintext = None
|
||||||
try:
|
try:
|
||||||
chunksize = Identity.DECRYPT_CHUNKSIZE
|
chunksize = Identity.DECRYPT_CHUNKSIZE
|
||||||
chunks = int(math.ceil(len(ciphertext)/(float(chunksize))))
|
chunks = int(math.ceil(len(ciphertext)/(float(chunksize))))
|
||||||
|
|
||||||
plaintext = b"";
|
plaintext = b"";
|
||||||
for chunk in range(chunks):
|
for chunk in range(chunks):
|
||||||
start = chunk*chunksize
|
start = chunk*chunksize
|
||||||
end = (chunk+1)*chunksize
|
end = (chunk+1)*chunksize
|
||||||
if (chunk+1)*chunksize > len(ciphertext):
|
if (chunk+1)*chunksize > len(ciphertext):
|
||||||
end = len(ciphertext)
|
end = len(ciphertext)
|
||||||
|
|
||||||
plaintext += self.prv.decrypt(
|
plaintext += self.prv.decrypt(
|
||||||
ciphertext[start:end],
|
ciphertext[start:end],
|
||||||
padding.OAEP(
|
padding.OAEP(
|
||||||
mgf=padding.MGF1(algorithm=hashes.SHA1()),
|
mgf=padding.MGF1(algorithm=hashes.SHA1()),
|
||||||
algorithm=hashes.SHA1(),
|
algorithm=hashes.SHA1(),
|
||||||
label=None
|
label=None
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except:
|
except:
|
||||||
RNS.log("Decryption by "+RNS.prettyhexrep(self.hash)+" failed", RNS.LOG_VERBOSE)
|
RNS.log("Decryption by "+RNS.prettyhexrep(self.hash)+" failed", RNS.LOG_VERBOSE)
|
||||||
|
|
||||||
return plaintext;
|
return plaintext;
|
||||||
else:
|
else:
|
||||||
raise KeyError("Decryption failed because identity does not hold a private key")
|
raise KeyError("Decryption failed because identity does not hold a private key")
|
||||||
|
|
||||||
|
|
||||||
def sign(self, message):
|
def sign(self, message):
|
||||||
if self.prv != None:
|
if self.prv != None:
|
||||||
signature = self.prv.sign(
|
signature = self.prv.sign(
|
||||||
message,
|
message,
|
||||||
padding.PSS(
|
padding.PSS(
|
||||||
mgf=padding.MGF1(hashes.SHA256()),
|
mgf=padding.MGF1(hashes.SHA256()),
|
||||||
salt_length=padding.PSS.MAX_LENGTH
|
salt_length=padding.PSS.MAX_LENGTH
|
||||||
),
|
),
|
||||||
hashes.SHA256()
|
hashes.SHA256()
|
||||||
)
|
)
|
||||||
return signature
|
return signature
|
||||||
else:
|
else:
|
||||||
raise KeyError("Signing failed because identity does not hold a private key")
|
raise KeyError("Signing failed because identity does not hold a private key")
|
||||||
|
|
||||||
def validate(self, signature, message):
|
def validate(self, signature, message):
|
||||||
if self.pub != None:
|
if self.pub != None:
|
||||||
try:
|
try:
|
||||||
self.pub.verify(
|
self.pub.verify(
|
||||||
signature,
|
signature,
|
||||||
message,
|
message,
|
||||||
padding.PSS(
|
padding.PSS(
|
||||||
mgf=padding.MGF1(hashes.SHA256()),
|
mgf=padding.MGF1(hashes.SHA256()),
|
||||||
salt_length=padding.PSS.MAX_LENGTH
|
salt_length=padding.PSS.MAX_LENGTH
|
||||||
),
|
),
|
||||||
hashes.SHA256()
|
hashes.SHA256()
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
raise KeyError("Signature validation failed because identity does not hold a public key")
|
raise KeyError("Signature validation failed because identity does not hold a public key")
|
||||||
|
|
||||||
def prove(self, packet, destination=None):
|
def prove(self, packet, destination=None):
|
||||||
signature = self.sign(packet.packet_hash)
|
signature = self.sign(packet.packet_hash)
|
||||||
if RNS.Reticulum.should_use_implicit_proof():
|
if RNS.Reticulum.should_use_implicit_proof():
|
||||||
proof_data = signature
|
proof_data = signature
|
||||||
else:
|
else:
|
||||||
proof_data = packet.packet_hash + signature
|
proof_data = packet.packet_hash + signature
|
||||||
|
|
||||||
if destination == None:
|
if destination == None:
|
||||||
destination = packet.generateProofDestination()
|
destination = packet.generateProofDestination()
|
||||||
|
|
||||||
proof = RNS.Packet(destination, proof_data, RNS.Packet.PROOF, attached_interface = packet.receiving_interface)
|
proof = RNS.Packet(destination, proof_data, RNS.Packet.PROOF, attached_interface = packet.receiving_interface)
|
||||||
proof.send()
|
proof.send()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return RNS.prettyhexrep(self.hash)
|
return RNS.prettyhexrep(self.hash)
|
||||||
|
@ -8,298 +8,298 @@ import time
|
|||||||
import RNS
|
import RNS
|
||||||
|
|
||||||
class KISS():
|
class KISS():
|
||||||
FEND = 0xC0
|
FEND = 0xC0
|
||||||
FESC = 0xDB
|
FESC = 0xDB
|
||||||
TFEND = 0xDC
|
TFEND = 0xDC
|
||||||
TFESC = 0xDD
|
TFESC = 0xDD
|
||||||
CMD_UNKNOWN = 0xFE
|
CMD_UNKNOWN = 0xFE
|
||||||
CMD_DATA = 0x00
|
CMD_DATA = 0x00
|
||||||
CMD_TXDELAY = 0x01
|
CMD_TXDELAY = 0x01
|
||||||
CMD_P = 0x02
|
CMD_P = 0x02
|
||||||
CMD_SLOTTIME = 0x03
|
CMD_SLOTTIME = 0x03
|
||||||
CMD_TXTAIL = 0x04
|
CMD_TXTAIL = 0x04
|
||||||
CMD_FULLDUPLEX = 0x05
|
CMD_FULLDUPLEX = 0x05
|
||||||
CMD_SETHARDWARE = 0x06
|
CMD_SETHARDWARE = 0x06
|
||||||
CMD_READY = 0x0F
|
CMD_READY = 0x0F
|
||||||
CMD_RETURN = 0xFF
|
CMD_RETURN = 0xFF
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def escape(data):
|
def escape(data):
|
||||||
data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd]))
|
data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd]))
|
||||||
data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc]))
|
data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc]))
|
||||||
return data
|
return data
|
||||||
|
|
||||||
class AX25():
|
class AX25():
|
||||||
PID_NOLAYER3 = 0xF0
|
PID_NOLAYER3 = 0xF0
|
||||||
CTRL_UI = 0x03
|
CTRL_UI = 0x03
|
||||||
CRC_CORRECT = bytes([0xF0])+bytes([0xB8])
|
CRC_CORRECT = bytes([0xF0])+bytes([0xB8])
|
||||||
HEADER_SIZE = 16
|
HEADER_SIZE = 16
|
||||||
|
|
||||||
|
|
||||||
class AX25KISSInterface(Interface):
|
class AX25KISSInterface(Interface):
|
||||||
MAX_CHUNK = 32768
|
MAX_CHUNK = 32768
|
||||||
|
|
||||||
owner = None
|
owner = None
|
||||||
port = None
|
port = None
|
||||||
speed = None
|
speed = None
|
||||||
databits = None
|
databits = None
|
||||||
parity = None
|
parity = None
|
||||||
stopbits = None
|
stopbits = None
|
||||||
serial = None
|
serial = None
|
||||||
|
|
||||||
def __init__(self, owner, name, callsign, ssid, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime, flow_control):
|
def __init__(self, owner, name, callsign, ssid, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime, flow_control):
|
||||||
self.serial = None
|
self.serial = None
|
||||||
self.owner = owner
|
self.owner = owner
|
||||||
self.name = name
|
self.name = name
|
||||||
self.src_call = callsign.upper().encode("ascii")
|
self.src_call = callsign.upper().encode("ascii")
|
||||||
self.src_ssid = ssid
|
self.src_ssid = ssid
|
||||||
self.dst_call = "APZRNS".encode("ascii")
|
self.dst_call = "APZRNS".encode("ascii")
|
||||||
self.dst_ssid = 0
|
self.dst_ssid = 0
|
||||||
self.port = port
|
self.port = port
|
||||||
self.speed = speed
|
self.speed = speed
|
||||||
self.databits = databits
|
self.databits = databits
|
||||||
self.parity = serial.PARITY_NONE
|
self.parity = serial.PARITY_NONE
|
||||||
self.stopbits = stopbits
|
self.stopbits = stopbits
|
||||||
self.timeout = 100
|
self.timeout = 100
|
||||||
self.online = False
|
self.online = False
|
||||||
# TODO: Sane default and make this configurable
|
# TODO: Sane default and make this configurable
|
||||||
# TODO: Changed to 25ms instead of 100ms, check it
|
# TODO: Changed to 25ms instead of 100ms, check it
|
||||||
self.txdelay = 0.025
|
self.txdelay = 0.025
|
||||||
|
|
||||||
self.packet_queue = []
|
self.packet_queue = []
|
||||||
self.flow_control = flow_control
|
self.flow_control = flow_control
|
||||||
self.interface_ready = False
|
self.interface_ready = False
|
||||||
|
|
||||||
if (len(self.src_call) < 3 or len(self.src_call) > 6):
|
if (len(self.src_call) < 3 or len(self.src_call) > 6):
|
||||||
raise ValueError("Invalid callsign for "+str(self))
|
raise ValueError("Invalid callsign for "+str(self))
|
||||||
|
|
||||||
if (self.src_ssid < 0 or self.src_ssid > 15):
|
if (self.src_ssid < 0 or self.src_ssid > 15):
|
||||||
raise ValueError("Invalid SSID for "+str(self))
|
raise ValueError("Invalid SSID for "+str(self))
|
||||||
|
|
||||||
self.preamble = preamble if preamble != None else 350;
|
self.preamble = preamble if preamble != None else 350;
|
||||||
self.txtail = txtail if txtail != None else 20;
|
self.txtail = txtail if txtail != None else 20;
|
||||||
self.persistence = persistence if persistence != None else 64;
|
self.persistence = persistence if persistence != None else 64;
|
||||||
self.slottime = slottime if slottime != None else 20;
|
self.slottime = slottime if slottime != None else 20;
|
||||||
|
|
||||||
if parity.lower() == "e" or parity.lower() == "even":
|
if parity.lower() == "e" or parity.lower() == "even":
|
||||||
self.parity = serial.PARITY_EVEN
|
self.parity = serial.PARITY_EVEN
|
||||||
|
|
||||||
if parity.lower() == "o" or parity.lower() == "odd":
|
if parity.lower() == "o" or parity.lower() == "odd":
|
||||||
self.parity = serial.PARITY_ODD
|
self.parity = serial.PARITY_ODD
|
||||||
|
|
||||||
try:
|
try:
|
||||||
RNS.log("Opening serial port "+self.port+"...")
|
RNS.log("Opening serial port "+self.port+"...")
|
||||||
self.serial = serial.Serial(
|
self.serial = serial.Serial(
|
||||||
port = self.port,
|
port = self.port,
|
||||||
baudrate = self.speed,
|
baudrate = self.speed,
|
||||||
bytesize = self.databits,
|
bytesize = self.databits,
|
||||||
parity = self.parity,
|
parity = self.parity,
|
||||||
stopbits = self.stopbits,
|
stopbits = self.stopbits,
|
||||||
xonxoff = False,
|
xonxoff = False,
|
||||||
rtscts = False,
|
rtscts = False,
|
||||||
timeout = 0,
|
timeout = 0,
|
||||||
inter_byte_timeout = None,
|
inter_byte_timeout = None,
|
||||||
write_timeout = None,
|
write_timeout = None,
|
||||||
dsrdtr = False,
|
dsrdtr = False,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
|
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
if self.serial.is_open:
|
if self.serial.is_open:
|
||||||
# Allow time for interface to initialise before config
|
# Allow time for interface to initialise before config
|
||||||
sleep(2.0)
|
sleep(2.0)
|
||||||
thread = threading.Thread(target=self.readLoop)
|
thread = threading.Thread(target=self.readLoop)
|
||||||
thread.setDaemon(True)
|
thread.setDaemon(True)
|
||||||
thread.start()
|
thread.start()
|
||||||
self.online = True
|
self.online = True
|
||||||
RNS.log("Serial port "+self.port+" is now open")
|
RNS.log("Serial port "+self.port+" is now open")
|
||||||
RNS.log("Configuring AX.25 KISS interface parameters...")
|
RNS.log("Configuring AX.25 KISS interface parameters...")
|
||||||
self.setPreamble(self.preamble)
|
self.setPreamble(self.preamble)
|
||||||
self.setTxTail(self.txtail)
|
self.setTxTail(self.txtail)
|
||||||
self.setPersistence(self.persistence)
|
self.setPersistence(self.persistence)
|
||||||
self.setSlotTime(self.slottime)
|
self.setSlotTime(self.slottime)
|
||||||
self.setFlowControl(self.flow_control)
|
self.setFlowControl(self.flow_control)
|
||||||
self.interface_ready = True
|
self.interface_ready = True
|
||||||
RNS.log("AX.25 KISS interface configured")
|
RNS.log("AX.25 KISS interface configured")
|
||||||
sleep(2)
|
sleep(2)
|
||||||
else:
|
else:
|
||||||
raise IOError("Could not open serial port")
|
raise IOError("Could not open serial port")
|
||||||
|
|
||||||
|
|
||||||
def setPreamble(self, preamble):
|
def setPreamble(self, preamble):
|
||||||
preamble_ms = preamble
|
preamble_ms = preamble
|
||||||
preamble = int(preamble_ms / 10)
|
preamble = int(preamble_ms / 10)
|
||||||
if preamble < 0:
|
if preamble < 0:
|
||||||
preamble = 0
|
preamble = 0
|
||||||
if preamble > 255:
|
if preamble > 255:
|
||||||
preamble = 255
|
preamble = 255
|
||||||
|
|
||||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXDELAY])+bytes([preamble])+bytes([KISS.FEND])
|
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXDELAY])+bytes([preamble])+bytes([KISS.FEND])
|
||||||
written = self.serial.write(kiss_command)
|
written = self.serial.write(kiss_command)
|
||||||
if written != len(kiss_command):
|
if written != len(kiss_command):
|
||||||
raise IOError("Could not configure AX.25 KISS interface preamble to "+str(preamble_ms)+" (command value "+str(preamble)+")")
|
raise IOError("Could not configure AX.25 KISS interface preamble to "+str(preamble_ms)+" (command value "+str(preamble)+")")
|
||||||
|
|
||||||
def setTxTail(self, txtail):
|
def setTxTail(self, txtail):
|
||||||
txtail_ms = txtail
|
txtail_ms = txtail
|
||||||
txtail = int(txtail_ms / 10)
|
txtail = int(txtail_ms / 10)
|
||||||
if txtail < 0:
|
if txtail < 0:
|
||||||
txtail = 0
|
txtail = 0
|
||||||
if txtail > 255:
|
if txtail > 255:
|
||||||
txtail = 255
|
txtail = 255
|
||||||
|
|
||||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXTAIL])+bytes([txtail])+bytes([KISS.FEND])
|
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXTAIL])+bytes([txtail])+bytes([KISS.FEND])
|
||||||
written = self.serial.write(kiss_command)
|
written = self.serial.write(kiss_command)
|
||||||
if written != len(kiss_command):
|
if written != len(kiss_command):
|
||||||
raise IOError("Could not configure AX.25 KISS interface TX tail to "+str(txtail_ms)+" (command value "+str(txtail)+")")
|
raise IOError("Could not configure AX.25 KISS interface TX tail to "+str(txtail_ms)+" (command value "+str(txtail)+")")
|
||||||
|
|
||||||
def setPersistence(self, persistence):
|
def setPersistence(self, persistence):
|
||||||
if persistence < 0:
|
if persistence < 0:
|
||||||
persistence = 0
|
persistence = 0
|
||||||
if persistence > 255:
|
if persistence > 255:
|
||||||
persistence = 255
|
persistence = 255
|
||||||
|
|
||||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_P])+bytes([persistence])+bytes([KISS.FEND])
|
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_P])+bytes([persistence])+bytes([KISS.FEND])
|
||||||
written = self.serial.write(kiss_command)
|
written = self.serial.write(kiss_command)
|
||||||
if written != len(kiss_command):
|
if written != len(kiss_command):
|
||||||
raise IOError("Could not configure AX.25 KISS interface persistence to "+str(persistence))
|
raise IOError("Could not configure AX.25 KISS interface persistence to "+str(persistence))
|
||||||
|
|
||||||
def setSlotTime(self, slottime):
|
def setSlotTime(self, slottime):
|
||||||
slottime_ms = slottime
|
slottime_ms = slottime
|
||||||
slottime = int(slottime_ms / 10)
|
slottime = int(slottime_ms / 10)
|
||||||
if slottime < 0:
|
if slottime < 0:
|
||||||
slottime = 0
|
slottime = 0
|
||||||
if slottime > 255:
|
if slottime > 255:
|
||||||
slottime = 255
|
slottime = 255
|
||||||
|
|
||||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_SLOTTIME])+bytes([slottime])+bytes([KISS.FEND])
|
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_SLOTTIME])+bytes([slottime])+bytes([KISS.FEND])
|
||||||
written = self.serial.write(kiss_command)
|
written = self.serial.write(kiss_command)
|
||||||
if written != len(kiss_command):
|
if written != len(kiss_command):
|
||||||
raise IOError("Could not configure AX.25 KISS interface slot time to "+str(slottime_ms)+" (command value "+str(slottime)+")")
|
raise IOError("Could not configure AX.25 KISS interface slot time to "+str(slottime_ms)+" (command value "+str(slottime)+")")
|
||||||
|
|
||||||
def setFlowControl(self, flow_control):
|
def setFlowControl(self, flow_control):
|
||||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_READY])+bytes([0x01])+bytes([KISS.FEND])
|
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_READY])+bytes([0x01])+bytes([KISS.FEND])
|
||||||
written = self.serial.write(kiss_command)
|
written = self.serial.write(kiss_command)
|
||||||
if written != len(kiss_command):
|
if written != len(kiss_command):
|
||||||
if (flow_control):
|
if (flow_control):
|
||||||
raise IOError("Could not enable AX.25 KISS interface flow control")
|
raise IOError("Could not enable AX.25 KISS interface flow control")
|
||||||
else:
|
else:
|
||||||
raise IOError("Could not enable AX.25 KISS interface flow control")
|
raise IOError("Could not enable AX.25 KISS interface flow control")
|
||||||
|
|
||||||
|
|
||||||
def processIncoming(self, data):
|
def processIncoming(self, data):
|
||||||
if (len(data) > AX25.HEADER_SIZE):
|
if (len(data) > AX25.HEADER_SIZE):
|
||||||
self.owner.inbound(data[AX25.HEADER_SIZE:], self)
|
self.owner.inbound(data[AX25.HEADER_SIZE:], self)
|
||||||
|
|
||||||
|
|
||||||
def processOutgoing(self,data):
|
def processOutgoing(self,data):
|
||||||
if self.online:
|
if self.online:
|
||||||
if self.interface_ready:
|
if self.interface_ready:
|
||||||
if self.flow_control:
|
if self.flow_control:
|
||||||
self.interface_ready = False
|
self.interface_ready = False
|
||||||
|
|
||||||
encoded_dst_ssid = bytes([0x60 | (self.dst_ssid << 1)])
|
encoded_dst_ssid = bytes([0x60 | (self.dst_ssid << 1)])
|
||||||
encoded_src_ssid = bytes([0x60 | (self.src_ssid << 1) | 0x01])
|
encoded_src_ssid = bytes([0x60 | (self.src_ssid << 1) | 0x01])
|
||||||
|
|
||||||
addr = b""
|
addr = b""
|
||||||
|
|
||||||
for i in range(0,6):
|
for i in range(0,6):
|
||||||
if (i < len(self.dst_call)):
|
if (i < len(self.dst_call)):
|
||||||
addr += bytes([self.dst_call[i]<<1])
|
addr += bytes([self.dst_call[i]<<1])
|
||||||
else:
|
else:
|
||||||
addr += bytes([0x20])
|
addr += bytes([0x20])
|
||||||
addr += encoded_dst_ssid
|
addr += encoded_dst_ssid
|
||||||
|
|
||||||
for i in range(0,6):
|
for i in range(0,6):
|
||||||
if (i < len(self.src_call)):
|
if (i < len(self.src_call)):
|
||||||
addr += bytes([self.src_call[i]<<1])
|
addr += bytes([self.src_call[i]<<1])
|
||||||
else:
|
else:
|
||||||
addr += bytes([0x20])
|
addr += bytes([0x20])
|
||||||
addr += encoded_src_ssid
|
addr += encoded_src_ssid
|
||||||
|
|
||||||
data = addr+bytes([AX25.CTRL_UI])+bytes([AX25.PID_NOLAYER3])+data
|
data = addr+bytes([AX25.CTRL_UI])+bytes([AX25.PID_NOLAYER3])+data
|
||||||
|
|
||||||
data = data.replace(bytes([0xdb]), bytes([0xdb])+bytes([0xdd]))
|
data = data.replace(bytes([0xdb]), bytes([0xdb])+bytes([0xdd]))
|
||||||
data = data.replace(bytes([0xc0]), bytes([0xdb])+bytes([0xdc]))
|
data = data.replace(bytes([0xc0]), bytes([0xdb])+bytes([0xdc]))
|
||||||
kiss_frame = bytes([KISS.FEND])+bytes([0x00])+data+bytes([KISS.FEND])
|
kiss_frame = bytes([KISS.FEND])+bytes([0x00])+data+bytes([KISS.FEND])
|
||||||
|
|
||||||
if (self.txdelay > 0):
|
if (self.txdelay > 0):
|
||||||
RNS.log(str(self.name)+" delaying TX for "+str(self.txdelay)+" seconds", RNS.LOG_EXTREME)
|
RNS.log(str(self.name)+" delaying TX for "+str(self.txdelay)+" seconds", RNS.LOG_EXTREME)
|
||||||
sleep(self.txdelay)
|
sleep(self.txdelay)
|
||||||
|
|
||||||
written = self.serial.write(kiss_frame)
|
written = self.serial.write(kiss_frame)
|
||||||
if written != len(kiss_frame):
|
if written != len(kiss_frame):
|
||||||
if self.flow_control:
|
if self.flow_control:
|
||||||
self.interface_ready = True
|
self.interface_ready = True
|
||||||
raise IOError("AX.25 interface only wrote "+str(written)+" bytes of "+str(len(kiss_frame)))
|
raise IOError("AX.25 interface only wrote "+str(written)+" bytes of "+str(len(kiss_frame)))
|
||||||
else:
|
else:
|
||||||
self.queue(data)
|
self.queue(data)
|
||||||
|
|
||||||
def queue(self, data):
|
def queue(self, data):
|
||||||
self.packet_queue.append(data)
|
self.packet_queue.append(data)
|
||||||
|
|
||||||
def process_queue(self):
|
def process_queue(self):
|
||||||
if len(self.packet_queue) > 0:
|
if len(self.packet_queue) > 0:
|
||||||
data = self.packet_queue.pop(0)
|
data = self.packet_queue.pop(0)
|
||||||
self.interface_ready = True
|
self.interface_ready = True
|
||||||
self.processOutgoing(data)
|
self.processOutgoing(data)
|
||||||
elif len(self.packet_queue) == 0:
|
elif len(self.packet_queue) == 0:
|
||||||
self.interface_ready = True
|
self.interface_ready = True
|
||||||
|
|
||||||
def readLoop(self):
|
def readLoop(self):
|
||||||
try:
|
try:
|
||||||
in_frame = False
|
in_frame = False
|
||||||
escape = False
|
escape = False
|
||||||
command = KISS.CMD_UNKNOWN
|
command = KISS.CMD_UNKNOWN
|
||||||
data_buffer = b""
|
data_buffer = b""
|
||||||
last_read_ms = int(time.time()*1000)
|
last_read_ms = int(time.time()*1000)
|
||||||
|
|
||||||
while self.serial.is_open:
|
while self.serial.is_open:
|
||||||
if self.serial.in_waiting:
|
if self.serial.in_waiting:
|
||||||
byte = ord(self.serial.read(1))
|
byte = ord(self.serial.read(1))
|
||||||
last_read_ms = int(time.time()*1000)
|
last_read_ms = int(time.time()*1000)
|
||||||
|
|
||||||
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
|
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
|
||||||
in_frame = False
|
in_frame = False
|
||||||
self.processIncoming(data_buffer)
|
self.processIncoming(data_buffer)
|
||||||
elif (byte == KISS.FEND):
|
elif (byte == KISS.FEND):
|
||||||
in_frame = True
|
in_frame = True
|
||||||
command = KISS.CMD_UNKNOWN
|
command = KISS.CMD_UNKNOWN
|
||||||
data_buffer = b""
|
data_buffer = b""
|
||||||
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU+AX25.HEADER_SIZE):
|
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU+AX25.HEADER_SIZE):
|
||||||
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
|
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
|
||||||
# We only support one HDLC port for now, so
|
# We only support one HDLC port for now, so
|
||||||
# strip off the port nibble
|
# strip off the port nibble
|
||||||
byte = byte & 0x0F
|
byte = byte & 0x0F
|
||||||
command = byte
|
command = byte
|
||||||
elif (command == KISS.CMD_DATA):
|
elif (command == KISS.CMD_DATA):
|
||||||
if (byte == KISS.FESC):
|
if (byte == KISS.FESC):
|
||||||
escape = True
|
escape = True
|
||||||
else:
|
else:
|
||||||
if (escape):
|
if (escape):
|
||||||
if (byte == KISS.TFEND):
|
if (byte == KISS.TFEND):
|
||||||
byte = KISS.FEND
|
byte = KISS.FEND
|
||||||
if (byte == KISS.TFESC):
|
if (byte == KISS.TFESC):
|
||||||
byte = KISS.FESC
|
byte = KISS.FESC
|
||||||
escape = False
|
escape = False
|
||||||
data_buffer = data_buffer+bytes([byte])
|
data_buffer = data_buffer+bytes([byte])
|
||||||
elif (command == KISS.CMD_READY):
|
elif (command == KISS.CMD_READY):
|
||||||
# TODO: add timeout and reset if ready
|
# TODO: add timeout and reset if ready
|
||||||
# command never arrives
|
# command never arrives
|
||||||
self.process_queue()
|
self.process_queue()
|
||||||
else:
|
else:
|
||||||
time_since_last = int(time.time()*1000) - last_read_ms
|
time_since_last = int(time.time()*1000) - last_read_ms
|
||||||
if len(data_buffer) > 0 and time_since_last > self.timeout:
|
if len(data_buffer) > 0 and time_since_last > self.timeout:
|
||||||
data_buffer = b""
|
data_buffer = b""
|
||||||
in_frame = False
|
in_frame = False
|
||||||
command = KISS.CMD_UNKNOWN
|
command = KISS.CMD_UNKNOWN
|
||||||
escape = False
|
escape = False
|
||||||
sleep(0.08)
|
sleep(0.08)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.online = False
|
self.online = False
|
||||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
|
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "AX25KISSInterface["+self.name+"]"
|
return "AX25KISSInterface["+self.name+"]"
|
@ -11,5 +11,5 @@ class Interface:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def get_hash(self):
|
def get_hash(self):
|
||||||
# TODO: Maybe expand this to something more unique
|
# TODO: Maybe expand this to something more unique
|
||||||
return RNS.Identity.fullHash(str(self).encode("utf-8"))
|
return RNS.Identity.fullHash(str(self).encode("utf-8"))
|
@ -7,250 +7,250 @@ import time
|
|||||||
import RNS
|
import RNS
|
||||||
|
|
||||||
class KISS():
|
class KISS():
|
||||||
FEND = 0xC0
|
FEND = 0xC0
|
||||||
FESC = 0xDB
|
FESC = 0xDB
|
||||||
TFEND = 0xDC
|
TFEND = 0xDC
|
||||||
TFESC = 0xDD
|
TFESC = 0xDD
|
||||||
CMD_UNKNOWN = 0xFE
|
CMD_UNKNOWN = 0xFE
|
||||||
CMD_DATA = 0x00
|
CMD_DATA = 0x00
|
||||||
CMD_TXDELAY = 0x01
|
CMD_TXDELAY = 0x01
|
||||||
CMD_P = 0x02
|
CMD_P = 0x02
|
||||||
CMD_SLOTTIME = 0x03
|
CMD_SLOTTIME = 0x03
|
||||||
CMD_TXTAIL = 0x04
|
CMD_TXTAIL = 0x04
|
||||||
CMD_FULLDUPLEX = 0x05
|
CMD_FULLDUPLEX = 0x05
|
||||||
CMD_SETHARDWARE = 0x06
|
CMD_SETHARDWARE = 0x06
|
||||||
CMD_READY = 0x0F
|
CMD_READY = 0x0F
|
||||||
CMD_RETURN = 0xFF
|
CMD_RETURN = 0xFF
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def escape(data):
|
def escape(data):
|
||||||
data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd]))
|
data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd]))
|
||||||
data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc]))
|
data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc]))
|
||||||
return data
|
return data
|
||||||
|
|
||||||
class KISSInterface(Interface):
|
class KISSInterface(Interface):
|
||||||
MAX_CHUNK = 32768
|
MAX_CHUNK = 32768
|
||||||
|
|
||||||
owner = None
|
owner = None
|
||||||
port = None
|
port = None
|
||||||
speed = None
|
speed = None
|
||||||
databits = None
|
databits = None
|
||||||
parity = None
|
parity = None
|
||||||
stopbits = None
|
stopbits = None
|
||||||
serial = None
|
serial = None
|
||||||
|
|
||||||
def __init__(self, owner, name, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime, flow_control):
|
def __init__(self, owner, name, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime, flow_control):
|
||||||
self.serial = None
|
self.serial = None
|
||||||
self.owner = owner
|
self.owner = owner
|
||||||
self.name = name
|
self.name = name
|
||||||
self.port = port
|
self.port = port
|
||||||
self.speed = speed
|
self.speed = speed
|
||||||
self.databits = databits
|
self.databits = databits
|
||||||
self.parity = serial.PARITY_NONE
|
self.parity = serial.PARITY_NONE
|
||||||
self.stopbits = stopbits
|
self.stopbits = stopbits
|
||||||
self.timeout = 100
|
self.timeout = 100
|
||||||
self.online = False
|
self.online = False
|
||||||
|
|
||||||
self.packet_queue = []
|
self.packet_queue = []
|
||||||
self.flow_control = flow_control
|
self.flow_control = flow_control
|
||||||
self.interface_ready = False
|
self.interface_ready = False
|
||||||
|
|
||||||
self.preamble = preamble if preamble != None else 350;
|
self.preamble = preamble if preamble != None else 350;
|
||||||
self.txtail = txtail if txtail != None else 20;
|
self.txtail = txtail if txtail != None else 20;
|
||||||
self.persistence = persistence if persistence != None else 64;
|
self.persistence = persistence if persistence != None else 64;
|
||||||
self.slottime = slottime if slottime != None else 20;
|
self.slottime = slottime if slottime != None else 20;
|
||||||
|
|
||||||
if parity.lower() == "e" or parity.lower() == "even":
|
if parity.lower() == "e" or parity.lower() == "even":
|
||||||
self.parity = serial.PARITY_EVEN
|
self.parity = serial.PARITY_EVEN
|
||||||
|
|
||||||
if parity.lower() == "o" or parity.lower() == "odd":
|
if parity.lower() == "o" or parity.lower() == "odd":
|
||||||
self.parity = serial.PARITY_ODD
|
self.parity = serial.PARITY_ODD
|
||||||
|
|
||||||
try:
|
try:
|
||||||
RNS.log("Opening serial port "+self.port+"...")
|
RNS.log("Opening serial port "+self.port+"...")
|
||||||
self.serial = serial.Serial(
|
self.serial = serial.Serial(
|
||||||
port = self.port,
|
port = self.port,
|
||||||
baudrate = self.speed,
|
baudrate = self.speed,
|
||||||
bytesize = self.databits,
|
bytesize = self.databits,
|
||||||
parity = self.parity,
|
parity = self.parity,
|
||||||
stopbits = self.stopbits,
|
stopbits = self.stopbits,
|
||||||
xonxoff = False,
|
xonxoff = False,
|
||||||
rtscts = False,
|
rtscts = False,
|
||||||
timeout = 0,
|
timeout = 0,
|
||||||
inter_byte_timeout = None,
|
inter_byte_timeout = None,
|
||||||
write_timeout = None,
|
write_timeout = None,
|
||||||
dsrdtr = False,
|
dsrdtr = False,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Could not open serial port "+self.port, RNS.LOG_ERROR)
|
RNS.log("Could not open serial port "+self.port, RNS.LOG_ERROR)
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
if self.serial.is_open:
|
if self.serial.is_open:
|
||||||
# Allow time for interface to initialise before config
|
# Allow time for interface to initialise before config
|
||||||
sleep(2.0)
|
sleep(2.0)
|
||||||
thread = threading.Thread(target=self.readLoop)
|
thread = threading.Thread(target=self.readLoop)
|
||||||
thread.setDaemon(True)
|
thread.setDaemon(True)
|
||||||
thread.start()
|
thread.start()
|
||||||
self.online = True
|
self.online = True
|
||||||
RNS.log("Serial port "+self.port+" is now open")
|
RNS.log("Serial port "+self.port+" is now open")
|
||||||
RNS.log("Configuring KISS interface parameters...")
|
RNS.log("Configuring KISS interface parameters...")
|
||||||
self.setPreamble(self.preamble)
|
self.setPreamble(self.preamble)
|
||||||
self.setTxTail(self.txtail)
|
self.setTxTail(self.txtail)
|
||||||
self.setPersistence(self.persistence)
|
self.setPersistence(self.persistence)
|
||||||
self.setSlotTime(self.slottime)
|
self.setSlotTime(self.slottime)
|
||||||
self.setFlowControl(self.flow_control)
|
self.setFlowControl(self.flow_control)
|
||||||
self.interface_ready = True
|
self.interface_ready = True
|
||||||
RNS.log("KISS interface configured")
|
RNS.log("KISS interface configured")
|
||||||
else:
|
else:
|
||||||
raise IOError("Could not open serial port")
|
raise IOError("Could not open serial port")
|
||||||
|
|
||||||
|
|
||||||
def setPreamble(self, preamble):
|
def setPreamble(self, preamble):
|
||||||
preamble_ms = preamble
|
preamble_ms = preamble
|
||||||
preamble = int(preamble_ms / 10)
|
preamble = int(preamble_ms / 10)
|
||||||
if preamble < 0:
|
if preamble < 0:
|
||||||
preamble = 0
|
preamble = 0
|
||||||
if preamble > 255:
|
if preamble > 255:
|
||||||
preamble = 255
|
preamble = 255
|
||||||
|
|
||||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXDELAY])+bytes([preamble])+bytes([KISS.FEND])
|
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXDELAY])+bytes([preamble])+bytes([KISS.FEND])
|
||||||
written = self.serial.write(kiss_command)
|
written = self.serial.write(kiss_command)
|
||||||
if written != len(kiss_command):
|
if written != len(kiss_command):
|
||||||
raise IOError("Could not configure KISS interface preamble to "+str(preamble_ms)+" (command value "+str(preamble)+")")
|
raise IOError("Could not configure KISS interface preamble to "+str(preamble_ms)+" (command value "+str(preamble)+")")
|
||||||
|
|
||||||
def setTxTail(self, txtail):
|
def setTxTail(self, txtail):
|
||||||
txtail_ms = txtail
|
txtail_ms = txtail
|
||||||
txtail = int(txtail_ms / 10)
|
txtail = int(txtail_ms / 10)
|
||||||
if txtail < 0:
|
if txtail < 0:
|
||||||
txtail = 0
|
txtail = 0
|
||||||
if txtail > 255:
|
if txtail > 255:
|
||||||
txtail = 255
|
txtail = 255
|
||||||
|
|
||||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXTAIL])+bytes([txtail])+bytes([KISS.FEND])
|
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXTAIL])+bytes([txtail])+bytes([KISS.FEND])
|
||||||
written = self.serial.write(kiss_command)
|
written = self.serial.write(kiss_command)
|
||||||
if written != len(kiss_command):
|
if written != len(kiss_command):
|
||||||
raise IOError("Could not configure KISS interface TX tail to "+str(txtail_ms)+" (command value "+str(txtail)+")")
|
raise IOError("Could not configure KISS interface TX tail to "+str(txtail_ms)+" (command value "+str(txtail)+")")
|
||||||
|
|
||||||
def setPersistence(self, persistence):
|
def setPersistence(self, persistence):
|
||||||
if persistence < 0:
|
if persistence < 0:
|
||||||
persistence = 0
|
persistence = 0
|
||||||
if persistence > 255:
|
if persistence > 255:
|
||||||
persistence = 255
|
persistence = 255
|
||||||
|
|
||||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_P])+bytes([persistence])+bytes([KISS.FEND])
|
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_P])+bytes([persistence])+bytes([KISS.FEND])
|
||||||
written = self.serial.write(kiss_command)
|
written = self.serial.write(kiss_command)
|
||||||
if written != len(kiss_command):
|
if written != len(kiss_command):
|
||||||
raise IOError("Could not configure KISS interface persistence to "+str(persistence))
|
raise IOError("Could not configure KISS interface persistence to "+str(persistence))
|
||||||
|
|
||||||
def setSlotTime(self, slottime):
|
def setSlotTime(self, slottime):
|
||||||
slottime_ms = slottime
|
slottime_ms = slottime
|
||||||
slottime = int(slottime_ms / 10)
|
slottime = int(slottime_ms / 10)
|
||||||
if slottime < 0:
|
if slottime < 0:
|
||||||
slottime = 0
|
slottime = 0
|
||||||
if slottime > 255:
|
if slottime > 255:
|
||||||
slottime = 255
|
slottime = 255
|
||||||
|
|
||||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_SLOTTIME])+bytes([slottime])+bytes([KISS.FEND])
|
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_SLOTTIME])+bytes([slottime])+bytes([KISS.FEND])
|
||||||
written = self.serial.write(kiss_command)
|
written = self.serial.write(kiss_command)
|
||||||
if written != len(kiss_command):
|
if written != len(kiss_command):
|
||||||
raise IOError("Could not configure KISS interface slot time to "+str(slottime_ms)+" (command value "+str(slottime)+")")
|
raise IOError("Could not configure KISS interface slot time to "+str(slottime_ms)+" (command value "+str(slottime)+")")
|
||||||
|
|
||||||
def setFlowControl(self, flow_control):
|
def setFlowControl(self, flow_control):
|
||||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_READY])+bytes([0x01])+bytes([KISS.FEND])
|
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_READY])+bytes([0x01])+bytes([KISS.FEND])
|
||||||
written = self.serial.write(kiss_command)
|
written = self.serial.write(kiss_command)
|
||||||
if written != len(kiss_command):
|
if written != len(kiss_command):
|
||||||
if (flow_control):
|
if (flow_control):
|
||||||
raise IOError("Could not enable KISS interface flow control")
|
raise IOError("Could not enable KISS interface flow control")
|
||||||
else:
|
else:
|
||||||
raise IOError("Could not enable KISS interface flow control")
|
raise IOError("Could not enable KISS interface flow control")
|
||||||
|
|
||||||
|
|
||||||
def processIncoming(self, data):
|
def processIncoming(self, data):
|
||||||
self.owner.inbound(data, self)
|
self.owner.inbound(data, self)
|
||||||
|
|
||||||
|
|
||||||
def processOutgoing(self,data):
|
def processOutgoing(self,data):
|
||||||
if self.online:
|
if self.online:
|
||||||
if self.interface_ready:
|
if self.interface_ready:
|
||||||
if self.flow_control:
|
if self.flow_control:
|
||||||
self.interface_ready = False
|
self.interface_ready = False
|
||||||
|
|
||||||
data = data.replace(bytes([0xdb]), bytes([0xdb])+bytes([0xdd]))
|
data = data.replace(bytes([0xdb]), bytes([0xdb])+bytes([0xdd]))
|
||||||
data = data.replace(bytes([0xc0]), bytes([0xdb])+bytes([0xdc]))
|
data = data.replace(bytes([0xc0]), bytes([0xdb])+bytes([0xdc]))
|
||||||
frame = bytes([KISS.FEND])+bytes([0x00])+data+bytes([KISS.FEND])
|
frame = bytes([KISS.FEND])+bytes([0x00])+data+bytes([KISS.FEND])
|
||||||
|
|
||||||
written = self.serial.write(frame)
|
written = self.serial.write(frame)
|
||||||
if written != len(frame):
|
if written != len(frame):
|
||||||
raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
|
raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.queue(data)
|
self.queue(data)
|
||||||
|
|
||||||
def queue(self, data):
|
def queue(self, data):
|
||||||
self.packet_queue.append(data)
|
self.packet_queue.append(data)
|
||||||
|
|
||||||
def process_queue(self):
|
def process_queue(self):
|
||||||
if len(self.packet_queue) > 0:
|
if len(self.packet_queue) > 0:
|
||||||
data = self.packet_queue.pop(0)
|
data = self.packet_queue.pop(0)
|
||||||
self.interface_ready = True
|
self.interface_ready = True
|
||||||
self.processOutgoing(data)
|
self.processOutgoing(data)
|
||||||
elif len(self.packet_queue) == 0:
|
elif len(self.packet_queue) == 0:
|
||||||
self.interface_ready = True
|
self.interface_ready = True
|
||||||
|
|
||||||
def readLoop(self):
|
def readLoop(self):
|
||||||
try:
|
try:
|
||||||
in_frame = False
|
in_frame = False
|
||||||
escape = False
|
escape = False
|
||||||
command = KISS.CMD_UNKNOWN
|
command = KISS.CMD_UNKNOWN
|
||||||
data_buffer = b""
|
data_buffer = b""
|
||||||
last_read_ms = int(time.time()*1000)
|
last_read_ms = int(time.time()*1000)
|
||||||
|
|
||||||
while self.serial.is_open:
|
while self.serial.is_open:
|
||||||
if self.serial.in_waiting:
|
if self.serial.in_waiting:
|
||||||
byte = ord(self.serial.read(1))
|
byte = ord(self.serial.read(1))
|
||||||
last_read_ms = int(time.time()*1000)
|
last_read_ms = int(time.time()*1000)
|
||||||
|
|
||||||
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
|
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
|
||||||
in_frame = False
|
in_frame = False
|
||||||
self.processIncoming(data_buffer)
|
self.processIncoming(data_buffer)
|
||||||
elif (byte == KISS.FEND):
|
elif (byte == KISS.FEND):
|
||||||
in_frame = True
|
in_frame = True
|
||||||
command = KISS.CMD_UNKNOWN
|
command = KISS.CMD_UNKNOWN
|
||||||
data_buffer = b""
|
data_buffer = b""
|
||||||
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
|
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
|
||||||
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
|
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
|
||||||
# We only support one HDLC port for now, so
|
# We only support one HDLC port for now, so
|
||||||
# strip off the port nibble
|
# strip off the port nibble
|
||||||
byte = byte & 0x0F
|
byte = byte & 0x0F
|
||||||
command = byte
|
command = byte
|
||||||
elif (command == KISS.CMD_DATA):
|
elif (command == KISS.CMD_DATA):
|
||||||
if (byte == KISS.FESC):
|
if (byte == KISS.FESC):
|
||||||
escape = True
|
escape = True
|
||||||
else:
|
else:
|
||||||
if (escape):
|
if (escape):
|
||||||
if (byte == KISS.TFEND):
|
if (byte == KISS.TFEND):
|
||||||
byte = KISS.FEND
|
byte = KISS.FEND
|
||||||
if (byte == KISS.TFESC):
|
if (byte == KISS.TFESC):
|
||||||
byte = KISS.FESC
|
byte = KISS.FESC
|
||||||
escape = False
|
escape = False
|
||||||
data_buffer = data_buffer+bytes([byte])
|
data_buffer = data_buffer+bytes([byte])
|
||||||
elif (command == KISS.CMD_READY):
|
elif (command == KISS.CMD_READY):
|
||||||
# TODO: add timeout and reset if ready
|
# TODO: add timeout and reset if ready
|
||||||
# command never arrives
|
# command never arrives
|
||||||
self.process_queue()
|
self.process_queue()
|
||||||
else:
|
else:
|
||||||
time_since_last = int(time.time()*1000) - last_read_ms
|
time_since_last = int(time.time()*1000) - last_read_ms
|
||||||
if len(data_buffer) > 0 and time_since_last > self.timeout:
|
if len(data_buffer) > 0 and time_since_last > self.timeout:
|
||||||
data_buffer = b""
|
data_buffer = b""
|
||||||
in_frame = False
|
in_frame = False
|
||||||
command = KISS.CMD_UNKNOWN
|
command = KISS.CMD_UNKNOWN
|
||||||
escape = False
|
escape = False
|
||||||
sleep(0.08)
|
sleep(0.08)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.online = False
|
self.online = False
|
||||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
|
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "KISSInterface["+self.name+"]"
|
return "KISSInterface["+self.name+"]"
|
@ -9,454 +9,454 @@ import math
|
|||||||
import RNS
|
import RNS
|
||||||
|
|
||||||
class KISS():
|
class KISS():
|
||||||
FEND = 0xC0
|
FEND = 0xC0
|
||||||
FESC = 0xDB
|
FESC = 0xDB
|
||||||
TFEND = 0xDC
|
TFEND = 0xDC
|
||||||
TFESC = 0xDD
|
TFESC = 0xDD
|
||||||
|
|
||||||
CMD_UNKNOWN = 0xFE
|
CMD_UNKNOWN = 0xFE
|
||||||
CMD_DATA = 0x00
|
CMD_DATA = 0x00
|
||||||
CMD_FREQUENCY = 0x01
|
CMD_FREQUENCY = 0x01
|
||||||
CMD_BANDWIDTH = 0x02
|
CMD_BANDWIDTH = 0x02
|
||||||
CMD_TXPOWER = 0x03
|
CMD_TXPOWER = 0x03
|
||||||
CMD_SF = 0x04
|
CMD_SF = 0x04
|
||||||
CMD_CR = 0x05
|
CMD_CR = 0x05
|
||||||
CMD_RADIO_STATE = 0x06
|
CMD_RADIO_STATE = 0x06
|
||||||
CMD_RADIO_LOCK = 0x07
|
CMD_RADIO_LOCK = 0x07
|
||||||
CMD_DETECT = 0x08
|
CMD_DETECT = 0x08
|
||||||
CMD_READY = 0x0F
|
CMD_READY = 0x0F
|
||||||
CMD_STAT_RX = 0x21
|
CMD_STAT_RX = 0x21
|
||||||
CMD_STAT_TX = 0x22
|
CMD_STAT_TX = 0x22
|
||||||
CMD_STAT_RSSI = 0x23
|
CMD_STAT_RSSI = 0x23
|
||||||
CMD_STAT_SNR = 0x24
|
CMD_STAT_SNR = 0x24
|
||||||
CMD_BLINK = 0x30
|
CMD_BLINK = 0x30
|
||||||
CMD_RANDOM = 0x40
|
CMD_RANDOM = 0x40
|
||||||
CMD_FW_VERSION = 0x50
|
CMD_FW_VERSION = 0x50
|
||||||
CMD_ROM_READ = 0x51
|
CMD_ROM_READ = 0x51
|
||||||
|
|
||||||
DETECT_REQ = 0x73
|
DETECT_REQ = 0x73
|
||||||
DETECT_RESP = 0x46
|
DETECT_RESP = 0x46
|
||||||
|
|
||||||
RADIO_STATE_OFF = 0x00
|
RADIO_STATE_OFF = 0x00
|
||||||
RADIO_STATE_ON = 0x01
|
RADIO_STATE_ON = 0x01
|
||||||
RADIO_STATE_ASK = 0xFF
|
RADIO_STATE_ASK = 0xFF
|
||||||
|
|
||||||
CMD_ERROR = 0x90
|
CMD_ERROR = 0x90
|
||||||
ERROR_INITRADIO = 0x01
|
ERROR_INITRADIO = 0x01
|
||||||
ERROR_TXFAILED = 0x02
|
ERROR_TXFAILED = 0x02
|
||||||
ERROR_EEPROM_LOCKED = 0x03
|
ERROR_EEPROM_LOCKED = 0x03
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def escape(data):
|
def escape(data):
|
||||||
data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd]))
|
data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd]))
|
||||||
data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc]))
|
data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc]))
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class RNodeInterface(Interface):
|
class RNodeInterface(Interface):
|
||||||
MAX_CHUNK = 32768
|
MAX_CHUNK = 32768
|
||||||
|
|
||||||
owner = None
|
owner = None
|
||||||
port = None
|
port = None
|
||||||
speed = None
|
speed = None
|
||||||
databits = None
|
databits = None
|
||||||
parity = None
|
parity = None
|
||||||
stopbits = None
|
stopbits = None
|
||||||
serial = None
|
serial = None
|
||||||
|
|
||||||
FREQ_MIN = 137000000
|
FREQ_MIN = 137000000
|
||||||
FREQ_MAX = 1020000000
|
FREQ_MAX = 1020000000
|
||||||
|
|
||||||
RSSI_OFFSET = 157
|
RSSI_OFFSET = 157
|
||||||
|
|
||||||
CALLSIGN_MAX_LEN = 32
|
CALLSIGN_MAX_LEN = 32
|
||||||
|
|
||||||
def __init__(self, owner, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, cr = None, flow_control = False, id_interval = None, id_callsign = None):
|
def __init__(self, owner, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, cr = None, flow_control = False, id_interval = None, id_callsign = None):
|
||||||
self.serial = None
|
self.serial = None
|
||||||
self.owner = owner
|
self.owner = owner
|
||||||
self.name = name
|
self.name = name
|
||||||
self.port = port
|
self.port = port
|
||||||
self.speed = 115200
|
self.speed = 115200
|
||||||
self.databits = 8
|
self.databits = 8
|
||||||
self.parity = serial.PARITY_NONE
|
self.parity = serial.PARITY_NONE
|
||||||
self.stopbits = 1
|
self.stopbits = 1
|
||||||
self.timeout = 100
|
self.timeout = 100
|
||||||
self.online = False
|
self.online = False
|
||||||
|
|
||||||
self.frequency = frequency
|
self.frequency = frequency
|
||||||
self.bandwidth = bandwidth
|
self.bandwidth = bandwidth
|
||||||
self.txpower = txpower
|
self.txpower = txpower
|
||||||
self.sf = sf
|
self.sf = sf
|
||||||
self.cr = cr
|
self.cr = cr
|
||||||
self.state = KISS.RADIO_STATE_OFF
|
self.state = KISS.RADIO_STATE_OFF
|
||||||
self.bitrate = 0
|
self.bitrate = 0
|
||||||
|
|
||||||
self.last_id = 0
|
self.last_id = 0
|
||||||
|
|
||||||
self.r_frequency = None
|
self.r_frequency = None
|
||||||
self.r_bandwidth = None
|
self.r_bandwidth = None
|
||||||
self.r_txpower = None
|
self.r_txpower = None
|
||||||
self.r_sf = None
|
self.r_sf = None
|
||||||
self.r_cr = None
|
self.r_cr = None
|
||||||
self.r_state = None
|
self.r_state = None
|
||||||
self.r_lock = None
|
self.r_lock = None
|
||||||
self.r_stat_rx = None
|
self.r_stat_rx = None
|
||||||
self.r_stat_tx = None
|
self.r_stat_tx = None
|
||||||
self.r_stat_rssi = None
|
self.r_stat_rssi = None
|
||||||
self.r_random = None
|
self.r_random = None
|
||||||
|
|
||||||
self.packet_queue = []
|
self.packet_queue = []
|
||||||
self.flow_control = flow_control
|
self.flow_control = flow_control
|
||||||
self.interface_ready = False
|
self.interface_ready = False
|
||||||
|
|
||||||
self.validcfg = True
|
self.validcfg = True
|
||||||
if (self.frequency < RNodeInterface.FREQ_MIN or self.frequency > RNodeInterface.FREQ_MAX):
|
if (self.frequency < RNodeInterface.FREQ_MIN or self.frequency > RNodeInterface.FREQ_MAX):
|
||||||
RNS.log("Invalid frequency configured for "+str(self), RNS.LOG_ERROR)
|
RNS.log("Invalid frequency configured for "+str(self), RNS.LOG_ERROR)
|
||||||
self.validcfg = False
|
self.validcfg = False
|
||||||
|
|
||||||
if (self.txpower < 0 or self.txpower > 17):
|
if (self.txpower < 0 or self.txpower > 17):
|
||||||
RNS.log("Invalid TX power configured for "+str(self), RNS.LOG_ERROR)
|
RNS.log("Invalid TX power configured for "+str(self), RNS.LOG_ERROR)
|
||||||
self.validcfg = False
|
self.validcfg = False
|
||||||
|
|
||||||
if (self.bandwidth < 7800 or self.bandwidth > 500000):
|
if (self.bandwidth < 7800 or self.bandwidth > 500000):
|
||||||
RNS.log("Invalid bandwidth configured for "+str(self), RNS.LOG_ERROR)
|
RNS.log("Invalid bandwidth configured for "+str(self), RNS.LOG_ERROR)
|
||||||
self.validcfg = False
|
self.validcfg = False
|
||||||
|
|
||||||
if (self.sf < 7 or self.sf > 12):
|
if (self.sf < 7 or self.sf > 12):
|
||||||
RNS.log("Invalid spreading factor configured for "+str(self), RNS.LOG_ERROR)
|
RNS.log("Invalid spreading factor configured for "+str(self), RNS.LOG_ERROR)
|
||||||
self.validcfg = False
|
self.validcfg = False
|
||||||
|
|
||||||
if (self.cr < 5 or self.cr > 8):
|
if (self.cr < 5 or self.cr > 8):
|
||||||
RNS.log("Invalid coding rate configured for "+str(self), RNS.LOG_ERROR)
|
RNS.log("Invalid coding rate configured for "+str(self), RNS.LOG_ERROR)
|
||||||
self.validcfg = False
|
self.validcfg = False
|
||||||
|
|
||||||
if id_interval != None and id_callsign != None:
|
if id_interval != None and id_callsign != None:
|
||||||
if (len(id_callsign.encode("utf-8")) <= RNodeInterface.CALLSIGN_MAX_LEN):
|
if (len(id_callsign.encode("utf-8")) <= RNodeInterface.CALLSIGN_MAX_LEN):
|
||||||
self.should_id = True
|
self.should_id = True
|
||||||
self.id_callsign = id_callsign
|
self.id_callsign = id_callsign
|
||||||
self.id_interval = id_interval
|
self.id_interval = id_interval
|
||||||
else:
|
else:
|
||||||
RNS.log("The encoded ID callsign for "+str(self)+" exceeds the max length of "+str(RNodeInterface.CALLSIGN_MAX_LEN)+" bytes.", RNS.LOG_ERROR)
|
RNS.log("The encoded ID callsign for "+str(self)+" exceeds the max length of "+str(RNodeInterface.CALLSIGN_MAX_LEN)+" bytes.", RNS.LOG_ERROR)
|
||||||
self.validcfg = False
|
self.validcfg = False
|
||||||
else:
|
else:
|
||||||
self.id_interval = None
|
self.id_interval = None
|
||||||
self.id_callsign = None
|
self.id_callsign = None
|
||||||
|
|
||||||
if (not self.validcfg):
|
if (not self.validcfg):
|
||||||
raise ValueError("The configuration for "+str(self)+" contains errors, interface is offline")
|
raise ValueError("The configuration for "+str(self)+" contains errors, interface is offline")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
RNS.log("Opening serial port "+self.port+"...")
|
RNS.log("Opening serial port "+self.port+"...")
|
||||||
self.serial = serial.Serial(
|
self.serial = serial.Serial(
|
||||||
port = self.port,
|
port = self.port,
|
||||||
baudrate = self.speed,
|
baudrate = self.speed,
|
||||||
bytesize = self.databits,
|
bytesize = self.databits,
|
||||||
parity = self.parity,
|
parity = self.parity,
|
||||||
stopbits = self.stopbits,
|
stopbits = self.stopbits,
|
||||||
xonxoff = False,
|
xonxoff = False,
|
||||||
rtscts = False,
|
rtscts = False,
|
||||||
timeout = 0,
|
timeout = 0,
|
||||||
inter_byte_timeout = None,
|
inter_byte_timeout = None,
|
||||||
write_timeout = None,
|
write_timeout = None,
|
||||||
dsrdtr = False,
|
dsrdtr = False,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
|
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
if self.serial.is_open:
|
if self.serial.is_open:
|
||||||
sleep(2.0)
|
sleep(2.0)
|
||||||
thread = threading.Thread(target=self.readLoop)
|
thread = threading.Thread(target=self.readLoop)
|
||||||
thread.setDaemon(True)
|
thread.setDaemon(True)
|
||||||
thread.start()
|
thread.start()
|
||||||
self.online = True
|
self.online = True
|
||||||
RNS.log("Serial port "+self.port+" is now open")
|
RNS.log("Serial port "+self.port+" is now open")
|
||||||
RNS.log("Configuring RNode interface...", RNS.LOG_VERBOSE)
|
RNS.log("Configuring RNode interface...", RNS.LOG_VERBOSE)
|
||||||
self.initRadio()
|
self.initRadio()
|
||||||
if (self.validateRadioState()):
|
if (self.validateRadioState()):
|
||||||
self.interface_ready = True
|
self.interface_ready = True
|
||||||
RNS.log(str(self)+" is configured and powered up")
|
RNS.log(str(self)+" is configured and powered up")
|
||||||
sleep(1.0)
|
sleep(1.0)
|
||||||
else:
|
else:
|
||||||
RNS.log("After configuring "+str(self)+", the reported radio parameters did not match your configuration.", RNS.LOG_ERROR)
|
RNS.log("After configuring "+str(self)+", the reported radio parameters did not match your configuration.", RNS.LOG_ERROR)
|
||||||
RNS.log("Make sure that your hardware actually supports the parameters specified in the configuration", RNS.LOG_ERROR)
|
RNS.log("Make sure that your hardware actually supports the parameters specified in the configuration", RNS.LOG_ERROR)
|
||||||
RNS.log("Aborting RNode startup", RNS.LOG_ERROR)
|
RNS.log("Aborting RNode startup", RNS.LOG_ERROR)
|
||||||
self.serial.close()
|
self.serial.close()
|
||||||
raise IOError("RNode interface did not pass validation")
|
raise IOError("RNode interface did not pass validation")
|
||||||
else:
|
else:
|
||||||
raise IOError("Could not open serial port")
|
raise IOError("Could not open serial port")
|
||||||
|
|
||||||
|
|
||||||
def initRadio(self):
|
def initRadio(self):
|
||||||
self.setFrequency()
|
self.setFrequency()
|
||||||
self.setBandwidth()
|
self.setBandwidth()
|
||||||
self.setTXPower()
|
self.setTXPower()
|
||||||
self.setSpreadingFactor()
|
self.setSpreadingFactor()
|
||||||
self.setCodingRate()
|
self.setCodingRate()
|
||||||
self.setRadioState(KISS.RADIO_STATE_ON)
|
self.setRadioState(KISS.RADIO_STATE_ON)
|
||||||
|
|
||||||
def setFrequency(self):
|
def setFrequency(self):
|
||||||
c1 = self.frequency >> 24
|
c1 = self.frequency >> 24
|
||||||
c2 = self.frequency >> 16 & 0xFF
|
c2 = self.frequency >> 16 & 0xFF
|
||||||
c3 = self.frequency >> 8 & 0xFF
|
c3 = self.frequency >> 8 & 0xFF
|
||||||
c4 = self.frequency & 0xFF
|
c4 = self.frequency & 0xFF
|
||||||
data = KISS.escape(bytes([c1])+bytes([c2])+bytes([c3])+bytes([c4]))
|
data = KISS.escape(bytes([c1])+bytes([c2])+bytes([c3])+bytes([c4]))
|
||||||
|
|
||||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_FREQUENCY])+data+bytes([KISS.FEND])
|
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_FREQUENCY])+data+bytes([KISS.FEND])
|
||||||
written = self.serial.write(kiss_command)
|
written = self.serial.write(kiss_command)
|
||||||
if written != len(kiss_command):
|
if written != len(kiss_command):
|
||||||
raise IOError("An IO error occurred while configuring frequency for "+self(str))
|
raise IOError("An IO error occurred while configuring frequency for "+self(str))
|
||||||
|
|
||||||
def setBandwidth(self):
|
def setBandwidth(self):
|
||||||
c1 = self.bandwidth >> 24
|
c1 = self.bandwidth >> 24
|
||||||
c2 = self.bandwidth >> 16 & 0xFF
|
c2 = self.bandwidth >> 16 & 0xFF
|
||||||
c3 = self.bandwidth >> 8 & 0xFF
|
c3 = self.bandwidth >> 8 & 0xFF
|
||||||
c4 = self.bandwidth & 0xFF
|
c4 = self.bandwidth & 0xFF
|
||||||
data = KISS.escape(bytes([c1])+bytes([c2])+bytes([c3])+bytes([c4]))
|
data = KISS.escape(bytes([c1])+bytes([c2])+bytes([c3])+bytes([c4]))
|
||||||
|
|
||||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_BANDWIDTH])+data+bytes([KISS.FEND])
|
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_BANDWIDTH])+data+bytes([KISS.FEND])
|
||||||
written = self.serial.write(kiss_command)
|
written = self.serial.write(kiss_command)
|
||||||
if written != len(kiss_command):
|
if written != len(kiss_command):
|
||||||
raise IOError("An IO error occurred while configuring bandwidth for "+self(str))
|
raise IOError("An IO error occurred while configuring bandwidth for "+self(str))
|
||||||
|
|
||||||
def setTXPower(self):
|
def setTXPower(self):
|
||||||
txp = bytes([self.txpower])
|
txp = bytes([self.txpower])
|
||||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXPOWER])+txp+bytes([KISS.FEND])
|
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXPOWER])+txp+bytes([KISS.FEND])
|
||||||
written = self.serial.write(kiss_command)
|
written = self.serial.write(kiss_command)
|
||||||
if written != len(kiss_command):
|
if written != len(kiss_command):
|
||||||
raise IOError("An IO error occurred while configuring TX power for "+self(str))
|
raise IOError("An IO error occurred while configuring TX power for "+self(str))
|
||||||
|
|
||||||
def setSpreadingFactor(self):
|
def setSpreadingFactor(self):
|
||||||
sf = bytes([self.sf])
|
sf = bytes([self.sf])
|
||||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_SF])+sf+bytes([KISS.FEND])
|
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_SF])+sf+bytes([KISS.FEND])
|
||||||
written = self.serial.write(kiss_command)
|
written = self.serial.write(kiss_command)
|
||||||
if written != len(kiss_command):
|
if written != len(kiss_command):
|
||||||
raise IOError("An IO error occurred while configuring spreading factor for "+self(str))
|
raise IOError("An IO error occurred while configuring spreading factor for "+self(str))
|
||||||
|
|
||||||
def setCodingRate(self):
|
def setCodingRate(self):
|
||||||
cr = bytes([self.cr])
|
cr = bytes([self.cr])
|
||||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_CR])+cr+bytes([KISS.FEND])
|
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_CR])+cr+bytes([KISS.FEND])
|
||||||
written = self.serial.write(kiss_command)
|
written = self.serial.write(kiss_command)
|
||||||
if written != len(kiss_command):
|
if written != len(kiss_command):
|
||||||
raise IOError("An IO error occurred while configuring coding rate for "+self(str))
|
raise IOError("An IO error occurred while configuring coding rate for "+self(str))
|
||||||
|
|
||||||
def setRadioState(self, state):
|
def setRadioState(self, state):
|
||||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_RADIO_STATE])+bytes([state])+bytes([KISS.FEND])
|
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_RADIO_STATE])+bytes([state])+bytes([KISS.FEND])
|
||||||
written = self.serial.write(kiss_command)
|
written = self.serial.write(kiss_command)
|
||||||
if written != len(kiss_command):
|
if written != len(kiss_command):
|
||||||
raise IOError("An IO error occurred while configuring radio state for "+self(str))
|
raise IOError("An IO error occurred while configuring radio state for "+self(str))
|
||||||
|
|
||||||
def validateRadioState(self):
|
def validateRadioState(self):
|
||||||
RNS.log("Validating radio configuration for "+str(self)+"...", RNS.LOG_VERBOSE)
|
RNS.log("Validating radio configuration for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||||
sleep(0.25);
|
sleep(0.25);
|
||||||
if (self.frequency != self.r_frequency):
|
if (self.frequency != self.r_frequency):
|
||||||
RNS.log("Frequency mismatch", RNS.LOG_ERROR)
|
RNS.log("Frequency mismatch", RNS.LOG_ERROR)
|
||||||
self.validcfg = False
|
self.validcfg = False
|
||||||
if (self.bandwidth != self.r_bandwidth):
|
if (self.bandwidth != self.r_bandwidth):
|
||||||
RNS.log("Bandwidth mismatch", RNS.LOG_ERROR)
|
RNS.log("Bandwidth mismatch", RNS.LOG_ERROR)
|
||||||
self.validcfg = False
|
self.validcfg = False
|
||||||
if (self.txpower != self.r_txpower):
|
if (self.txpower != self.r_txpower):
|
||||||
RNS.log("TX power mismatch", RNS.LOG_ERROR)
|
RNS.log("TX power mismatch", RNS.LOG_ERROR)
|
||||||
self.validcfg = False
|
self.validcfg = False
|
||||||
if (self.sf != self.r_sf):
|
if (self.sf != self.r_sf):
|
||||||
RNS.log("Spreading factor mismatch", RNS.LOG_ERROR)
|
RNS.log("Spreading factor mismatch", RNS.LOG_ERROR)
|
||||||
self.validcfg = False
|
self.validcfg = False
|
||||||
|
|
||||||
if (self.validcfg):
|
if (self.validcfg):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def updateBitrate(self):
|
def updateBitrate(self):
|
||||||
try:
|
try:
|
||||||
self.bitrate = self.r_sf * ( (4.0/self.r_cr) / (math.pow(2,self.r_sf)/(self.r_bandwidth/1000)) ) * 1000
|
self.bitrate = self.r_sf * ( (4.0/self.r_cr) / (math.pow(2,self.r_sf)/(self.r_bandwidth/1000)) ) * 1000
|
||||||
self.bitrate_kbps = round(self.bitrate/1000.0, 2)
|
self.bitrate_kbps = round(self.bitrate/1000.0, 2)
|
||||||
RNS.log(str(self)+" On-air bitrate is now "+str(self.bitrate_kbps)+ " kbps", RNS.LOG_INFO)
|
RNS.log(str(self)+" On-air bitrate is now "+str(self.bitrate_kbps)+ " kbps", RNS.LOG_INFO)
|
||||||
except:
|
except:
|
||||||
self.bitrate = 0
|
self.bitrate = 0
|
||||||
|
|
||||||
def processIncoming(self, data):
|
def processIncoming(self, data):
|
||||||
self.owner.inbound(data, self)
|
self.owner.inbound(data, self)
|
||||||
|
|
||||||
|
|
||||||
def processOutgoing(self,data):
|
def processOutgoing(self,data):
|
||||||
if self.online:
|
if self.online:
|
||||||
if self.interface_ready:
|
if self.interface_ready:
|
||||||
if self.flow_control:
|
if self.flow_control:
|
||||||
self.interface_ready = False
|
self.interface_ready = False
|
||||||
|
|
||||||
frame = b""
|
frame = b""
|
||||||
|
|
||||||
if self.id_interval != None and self.id_callsign != None:
|
if self.id_interval != None and self.id_callsign != None:
|
||||||
if self.last_id + self.id_interval < time.time():
|
if self.last_id + self.id_interval < time.time():
|
||||||
self.last_id = time.time()
|
self.last_id = time.time()
|
||||||
frame = bytes([0xc0])+bytes([0x00])+KISS.escape(self.id_callsign.encode("utf-8"))+bytes([0xc0])
|
frame = bytes([0xc0])+bytes([0x00])+KISS.escape(self.id_callsign.encode("utf-8"))+bytes([0xc0])
|
||||||
|
|
||||||
data = KISS.escape(data)
|
data = KISS.escape(data)
|
||||||
frame += bytes([0xc0])+bytes([0x00])+data+bytes([0xc0])
|
frame += bytes([0xc0])+bytes([0x00])+data+bytes([0xc0])
|
||||||
written = self.serial.write(frame)
|
written = self.serial.write(frame)
|
||||||
|
|
||||||
if written != len(frame):
|
if written != len(frame):
|
||||||
raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
|
raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
|
||||||
else:
|
else:
|
||||||
self.queue(data)
|
self.queue(data)
|
||||||
|
|
||||||
def queue(self, data):
|
def queue(self, data):
|
||||||
self.packet_queue.append(data)
|
self.packet_queue.append(data)
|
||||||
|
|
||||||
def process_queue(self):
|
def process_queue(self):
|
||||||
if len(self.packet_queue) > 0:
|
if len(self.packet_queue) > 0:
|
||||||
data = self.packet_queue.pop(0)
|
data = self.packet_queue.pop(0)
|
||||||
self.interface_ready = True
|
self.interface_ready = True
|
||||||
self.processOutgoing(data)
|
self.processOutgoing(data)
|
||||||
elif len(self.packet_queue) == 0:
|
elif len(self.packet_queue) == 0:
|
||||||
self.interface_ready = True
|
self.interface_ready = True
|
||||||
|
|
||||||
def readLoop(self):
|
def readLoop(self):
|
||||||
try:
|
try:
|
||||||
in_frame = False
|
in_frame = False
|
||||||
escape = False
|
escape = False
|
||||||
command = KISS.CMD_UNKNOWN
|
command = KISS.CMD_UNKNOWN
|
||||||
data_buffer = b""
|
data_buffer = b""
|
||||||
command_buffer = b""
|
command_buffer = b""
|
||||||
last_read_ms = int(time.time()*1000)
|
last_read_ms = int(time.time()*1000)
|
||||||
|
|
||||||
while self.serial.is_open:
|
while self.serial.is_open:
|
||||||
if self.serial.in_waiting:
|
if self.serial.in_waiting:
|
||||||
byte = ord(self.serial.read(1))
|
byte = ord(self.serial.read(1))
|
||||||
last_read_ms = int(time.time()*1000)
|
last_read_ms = int(time.time()*1000)
|
||||||
|
|
||||||
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
|
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
|
||||||
in_frame = False
|
in_frame = False
|
||||||
self.processIncoming(data_buffer)
|
self.processIncoming(data_buffer)
|
||||||
data_buffer = b""
|
data_buffer = b""
|
||||||
command_buffer = b""
|
command_buffer = b""
|
||||||
elif (byte == KISS.FEND):
|
elif (byte == KISS.FEND):
|
||||||
in_frame = True
|
in_frame = True
|
||||||
command = KISS.CMD_UNKNOWN
|
command = KISS.CMD_UNKNOWN
|
||||||
data_buffer = b""
|
data_buffer = b""
|
||||||
command_buffer = b""
|
command_buffer = b""
|
||||||
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
|
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
|
||||||
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
|
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
|
||||||
command = byte
|
command = byte
|
||||||
elif (command == KISS.CMD_DATA):
|
elif (command == KISS.CMD_DATA):
|
||||||
if (byte == KISS.FESC):
|
if (byte == KISS.FESC):
|
||||||
escape = True
|
escape = True
|
||||||
else:
|
else:
|
||||||
if (escape):
|
if (escape):
|
||||||
if (byte == KISS.TFEND):
|
if (byte == KISS.TFEND):
|
||||||
byte = KISS.FEND
|
byte = KISS.FEND
|
||||||
if (byte == KISS.TFESC):
|
if (byte == KISS.TFESC):
|
||||||
byte = KISS.FESC
|
byte = KISS.FESC
|
||||||
escape = False
|
escape = False
|
||||||
data_buffer = data_buffer+bytes([byte])
|
data_buffer = data_buffer+bytes([byte])
|
||||||
elif (command == KISS.CMD_FREQUENCY):
|
elif (command == KISS.CMD_FREQUENCY):
|
||||||
if (byte == KISS.FESC):
|
if (byte == KISS.FESC):
|
||||||
escape = True
|
escape = True
|
||||||
else:
|
else:
|
||||||
if (escape):
|
if (escape):
|
||||||
if (byte == KISS.TFEND):
|
if (byte == KISS.TFEND):
|
||||||
byte = KISS.FEND
|
byte = KISS.FEND
|
||||||
if (byte == KISS.TFESC):
|
if (byte == KISS.TFESC):
|
||||||
byte = KISS.FESC
|
byte = KISS.FESC
|
||||||
escape = False
|
escape = False
|
||||||
command_buffer = command_buffer+bytes([byte])
|
command_buffer = command_buffer+bytes([byte])
|
||||||
if (len(command_buffer) == 4):
|
if (len(command_buffer) == 4):
|
||||||
self.r_frequency = command_buffer[0] << 24 | command_buffer[1] << 16 | command_buffer[2] << 8 | command_buffer[3]
|
self.r_frequency = command_buffer[0] << 24 | command_buffer[1] << 16 | command_buffer[2] << 8 | command_buffer[3]
|
||||||
RNS.log(str(self)+" Radio reporting frequency is "+str(self.r_frequency/1000000.0)+" MHz", RNS.LOG_DEBUG)
|
RNS.log(str(self)+" Radio reporting frequency is "+str(self.r_frequency/1000000.0)+" MHz", RNS.LOG_DEBUG)
|
||||||
self.updateBitrate()
|
self.updateBitrate()
|
||||||
|
|
||||||
elif (command == KISS.CMD_BANDWIDTH):
|
elif (command == KISS.CMD_BANDWIDTH):
|
||||||
if (byte == KISS.FESC):
|
if (byte == KISS.FESC):
|
||||||
escape = True
|
escape = True
|
||||||
else:
|
else:
|
||||||
if (escape):
|
if (escape):
|
||||||
if (byte == KISS.TFEND):
|
if (byte == KISS.TFEND):
|
||||||
byte = KISS.FEND
|
byte = KISS.FEND
|
||||||
if (byte == KISS.TFESC):
|
if (byte == KISS.TFESC):
|
||||||
byte = KISS.FESC
|
byte = KISS.FESC
|
||||||
escape = False
|
escape = False
|
||||||
command_buffer = command_buffer+bytes([byte])
|
command_buffer = command_buffer+bytes([byte])
|
||||||
if (len(command_buffer) == 4):
|
if (len(command_buffer) == 4):
|
||||||
self.r_bandwidth = command_buffer[0] << 24 | command_buffer[1] << 16 | command_buffer[2] << 8 | command_buffer[3]
|
self.r_bandwidth = command_buffer[0] << 24 | command_buffer[1] << 16 | command_buffer[2] << 8 | command_buffer[3]
|
||||||
RNS.log(str(self)+" Radio reporting bandwidth is "+str(self.r_bandwidth/1000.0)+" KHz", RNS.LOG_DEBUG)
|
RNS.log(str(self)+" Radio reporting bandwidth is "+str(self.r_bandwidth/1000.0)+" KHz", RNS.LOG_DEBUG)
|
||||||
self.updateBitrate()
|
self.updateBitrate()
|
||||||
|
|
||||||
elif (command == KISS.CMD_TXPOWER):
|
elif (command == KISS.CMD_TXPOWER):
|
||||||
self.r_txpower = byte
|
self.r_txpower = byte
|
||||||
RNS.log(str(self)+" Radio reporting TX power is "+str(self.r_txpower)+" dBm", RNS.LOG_DEBUG)
|
RNS.log(str(self)+" Radio reporting TX power is "+str(self.r_txpower)+" dBm", RNS.LOG_DEBUG)
|
||||||
elif (command == KISS.CMD_SF):
|
elif (command == KISS.CMD_SF):
|
||||||
self.r_sf = byte
|
self.r_sf = byte
|
||||||
RNS.log(str(self)+" Radio reporting spreading factor is "+str(self.r_sf), RNS.LOG_DEBUG)
|
RNS.log(str(self)+" Radio reporting spreading factor is "+str(self.r_sf), RNS.LOG_DEBUG)
|
||||||
self.updateBitrate()
|
self.updateBitrate()
|
||||||
elif (command == KISS.CMD_CR):
|
elif (command == KISS.CMD_CR):
|
||||||
self.r_cr = byte
|
self.r_cr = byte
|
||||||
RNS.log(str(self)+" Radio reporting coding rate is "+str(self.r_cr), RNS.LOG_DEBUG)
|
RNS.log(str(self)+" Radio reporting coding rate is "+str(self.r_cr), RNS.LOG_DEBUG)
|
||||||
self.updateBitrate()
|
self.updateBitrate()
|
||||||
elif (command == KISS.CMD_RADIO_STATE):
|
elif (command == KISS.CMD_RADIO_STATE):
|
||||||
self.r_state = byte
|
self.r_state = byte
|
||||||
elif (command == KISS.CMD_RADIO_LOCK):
|
elif (command == KISS.CMD_RADIO_LOCK):
|
||||||
self.r_lock = byte
|
self.r_lock = byte
|
||||||
elif (command == KISS.CMD_STAT_RX):
|
elif (command == KISS.CMD_STAT_RX):
|
||||||
if (byte == KISS.FESC):
|
if (byte == KISS.FESC):
|
||||||
escape = True
|
escape = True
|
||||||
else:
|
else:
|
||||||
if (escape):
|
if (escape):
|
||||||
if (byte == KISS.TFEND):
|
if (byte == KISS.TFEND):
|
||||||
byte = KISS.FEND
|
byte = KISS.FEND
|
||||||
if (byte == KISS.TFESC):
|
if (byte == KISS.TFESC):
|
||||||
byte = KISS.FESC
|
byte = KISS.FESC
|
||||||
escape = False
|
escape = False
|
||||||
command_buffer = command_buffer+bytes([byte])
|
command_buffer = command_buffer+bytes([byte])
|
||||||
if (len(command_buffer) == 4):
|
if (len(command_buffer) == 4):
|
||||||
self.r_stat_rx = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3])
|
self.r_stat_rx = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3])
|
||||||
|
|
||||||
elif (command == KISS.CMD_STAT_TX):
|
elif (command == KISS.CMD_STAT_TX):
|
||||||
if (byte == KISS.FESC):
|
if (byte == KISS.FESC):
|
||||||
escape = True
|
escape = True
|
||||||
else:
|
else:
|
||||||
if (escape):
|
if (escape):
|
||||||
if (byte == KISS.TFEND):
|
if (byte == KISS.TFEND):
|
||||||
byte = KISS.FEND
|
byte = KISS.FEND
|
||||||
if (byte == KISS.TFESC):
|
if (byte == KISS.TFESC):
|
||||||
byte = KISS.FESC
|
byte = KISS.FESC
|
||||||
escape = False
|
escape = False
|
||||||
command_buffer = command_buffer+bytes([byte])
|
command_buffer = command_buffer+bytes([byte])
|
||||||
if (len(command_buffer) == 4):
|
if (len(command_buffer) == 4):
|
||||||
self.r_stat_tx = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3])
|
self.r_stat_tx = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3])
|
||||||
|
|
||||||
elif (command == KISS.CMD_STAT_RSSI):
|
elif (command == KISS.CMD_STAT_RSSI):
|
||||||
self.r_stat_rssi = byte-RNodeInterface.RSSI_OFFSET
|
self.r_stat_rssi = byte-RNodeInterface.RSSI_OFFSET
|
||||||
elif (command == KISS.CMD_STAT_SNR):
|
elif (command == KISS.CMD_STAT_SNR):
|
||||||
self.r_stat_snr = int.from_bytes(bytes([byte]), byteorder="big", signed=True) * 0.25
|
self.r_stat_snr = int.from_bytes(bytes([byte]), byteorder="big", signed=True) * 0.25
|
||||||
elif (command == KISS.CMD_RANDOM):
|
elif (command == KISS.CMD_RANDOM):
|
||||||
self.r_random = byte
|
self.r_random = byte
|
||||||
elif (command == KISS.CMD_ERROR):
|
elif (command == KISS.CMD_ERROR):
|
||||||
if (byte == KISS.ERROR_INITRADIO):
|
if (byte == KISS.ERROR_INITRADIO):
|
||||||
RNS.log(str(self)+" hardware initialisation error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
RNS.log(str(self)+" hardware initialisation error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||||
elif (byte == KISS.ERROR_INITRADIO):
|
elif (byte == KISS.ERROR_INITRADIO):
|
||||||
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||||
else:
|
else:
|
||||||
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||||
elif (command == KISS.CMD_READY):
|
elif (command == KISS.CMD_READY):
|
||||||
self.process_queue()
|
self.process_queue()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
time_since_last = int(time.time()*1000) - last_read_ms
|
time_since_last = int(time.time()*1000) - last_read_ms
|
||||||
if len(data_buffer) > 0 and time_since_last > self.timeout:
|
if len(data_buffer) > 0 and time_since_last > self.timeout:
|
||||||
RNS.log(str(self)+" serial read timeout", RNS.LOG_DEBUG)
|
RNS.log(str(self)+" serial read timeout", RNS.LOG_DEBUG)
|
||||||
data_buffer = b""
|
data_buffer = b""
|
||||||
in_frame = False
|
in_frame = False
|
||||||
command = KISS.CMD_UNKNOWN
|
command = KISS.CMD_UNKNOWN
|
||||||
escape = False
|
escape = False
|
||||||
sleep(0.08)
|
sleep(0.08)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.online = False
|
self.online = False
|
||||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
|
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "RNodeInterface["+self.name+"]"
|
return "RNodeInterface["+self.name+"]"
|
||||||
|
|
||||||
|
@ -7,130 +7,130 @@ import time
|
|||||||
import RNS
|
import RNS
|
||||||
|
|
||||||
class HDLC():
|
class HDLC():
|
||||||
# The Serial Interface packetizes data using
|
# The Serial Interface packetizes data using
|
||||||
# simplified HDLC framing, similar to PPP
|
# simplified HDLC framing, similar to PPP
|
||||||
FLAG = 0x7E
|
FLAG = 0x7E
|
||||||
ESC = 0x7D
|
ESC = 0x7D
|
||||||
ESC_MASK = 0x20
|
ESC_MASK = 0x20
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def escape(data):
|
def escape(data):
|
||||||
data = data.replace(bytes([HDLC.ESC]), bytes([HDLC.ESC, HDLC.ESC^HDLC.ESC_MASK]))
|
data = data.replace(bytes([HDLC.ESC]), bytes([HDLC.ESC, HDLC.ESC^HDLC.ESC_MASK]))
|
||||||
data = data.replace(bytes([HDLC.FLAG]), bytes([HDLC.ESC, HDLC.FLAG^HDLC.ESC_MASK]))
|
data = data.replace(bytes([HDLC.FLAG]), bytes([HDLC.ESC, HDLC.FLAG^HDLC.ESC_MASK]))
|
||||||
return data
|
return data
|
||||||
|
|
||||||
class SerialInterface(Interface):
|
class SerialInterface(Interface):
|
||||||
MAX_CHUNK = 32768
|
MAX_CHUNK = 32768
|
||||||
|
|
||||||
owner = None
|
owner = None
|
||||||
port = None
|
port = None
|
||||||
speed = None
|
speed = None
|
||||||
databits = None
|
databits = None
|
||||||
parity = None
|
parity = None
|
||||||
stopbits = None
|
stopbits = None
|
||||||
serial = None
|
serial = None
|
||||||
|
|
||||||
def __init__(self, owner, name, port, speed, databits, parity, stopbits):
|
def __init__(self, owner, name, port, speed, databits, parity, stopbits):
|
||||||
self.serial = None
|
self.serial = None
|
||||||
self.owner = owner
|
self.owner = owner
|
||||||
self.name = name
|
self.name = name
|
||||||
self.port = port
|
self.port = port
|
||||||
self.speed = speed
|
self.speed = speed
|
||||||
self.databits = databits
|
self.databits = databits
|
||||||
self.parity = serial.PARITY_NONE
|
self.parity = serial.PARITY_NONE
|
||||||
self.stopbits = stopbits
|
self.stopbits = stopbits
|
||||||
self.timeout = 100
|
self.timeout = 100
|
||||||
self.online = False
|
self.online = False
|
||||||
|
|
||||||
if parity.lower() == "e" or parity.lower() == "even":
|
if parity.lower() == "e" or parity.lower() == "even":
|
||||||
self.parity = serial.PARITY_EVEN
|
self.parity = serial.PARITY_EVEN
|
||||||
|
|
||||||
if parity.lower() == "o" or parity.lower() == "odd":
|
if parity.lower() == "o" or parity.lower() == "odd":
|
||||||
self.parity = serial.PARITY_ODD
|
self.parity = serial.PARITY_ODD
|
||||||
|
|
||||||
try:
|
try:
|
||||||
RNS.log("Opening serial port "+self.port+"...")
|
RNS.log("Opening serial port "+self.port+"...")
|
||||||
self.serial = serial.Serial(
|
self.serial = serial.Serial(
|
||||||
port = self.port,
|
port = self.port,
|
||||||
baudrate = self.speed,
|
baudrate = self.speed,
|
||||||
bytesize = self.databits,
|
bytesize = self.databits,
|
||||||
parity = self.parity,
|
parity = self.parity,
|
||||||
stopbits = self.stopbits,
|
stopbits = self.stopbits,
|
||||||
xonxoff = False,
|
xonxoff = False,
|
||||||
rtscts = False,
|
rtscts = False,
|
||||||
timeout = 0,
|
timeout = 0,
|
||||||
inter_byte_timeout = None,
|
inter_byte_timeout = None,
|
||||||
write_timeout = None,
|
write_timeout = None,
|
||||||
dsrdtr = False,
|
dsrdtr = False,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
|
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
if self.serial.is_open:
|
if self.serial.is_open:
|
||||||
sleep(0.5)
|
sleep(0.5)
|
||||||
thread = threading.Thread(target=self.readLoop)
|
thread = threading.Thread(target=self.readLoop)
|
||||||
thread.setDaemon(True)
|
thread.setDaemon(True)
|
||||||
thread.start()
|
thread.start()
|
||||||
self.online = True
|
self.online = True
|
||||||
RNS.log("Serial port "+self.port+" is now open")
|
RNS.log("Serial port "+self.port+" is now open")
|
||||||
else:
|
else:
|
||||||
raise IOError("Could not open serial port")
|
raise IOError("Could not open serial port")
|
||||||
|
|
||||||
|
|
||||||
def processIncoming(self, data):
|
def processIncoming(self, data):
|
||||||
self.owner.inbound(data, self)
|
self.owner.inbound(data, self)
|
||||||
|
|
||||||
|
|
||||||
def processOutgoing(self,data):
|
def processOutgoing(self,data):
|
||||||
if self.online:
|
if self.online:
|
||||||
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
|
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
|
||||||
written = self.serial.write(data)
|
written = self.serial.write(data)
|
||||||
if written != len(data):
|
if written != len(data):
|
||||||
raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
|
raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
|
||||||
|
|
||||||
|
|
||||||
def readLoop(self):
|
def readLoop(self):
|
||||||
try:
|
try:
|
||||||
in_frame = False
|
in_frame = False
|
||||||
escape = False
|
escape = False
|
||||||
data_buffer = b""
|
data_buffer = b""
|
||||||
last_read_ms = int(time.time()*1000)
|
last_read_ms = int(time.time()*1000)
|
||||||
|
|
||||||
while self.serial.is_open:
|
while self.serial.is_open:
|
||||||
if self.serial.in_waiting:
|
if self.serial.in_waiting:
|
||||||
byte = ord(self.serial.read(1))
|
byte = ord(self.serial.read(1))
|
||||||
last_read_ms = int(time.time()*1000)
|
last_read_ms = int(time.time()*1000)
|
||||||
|
|
||||||
if (in_frame and byte == HDLC.FLAG):
|
if (in_frame and byte == HDLC.FLAG):
|
||||||
in_frame = False
|
in_frame = False
|
||||||
self.processIncoming(data_buffer)
|
self.processIncoming(data_buffer)
|
||||||
elif (byte == HDLC.FLAG):
|
elif (byte == HDLC.FLAG):
|
||||||
in_frame = True
|
in_frame = True
|
||||||
data_buffer = b""
|
data_buffer = b""
|
||||||
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
|
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
|
||||||
if (byte == HDLC.ESC):
|
if (byte == HDLC.ESC):
|
||||||
escape = True
|
escape = True
|
||||||
else:
|
else:
|
||||||
if (escape):
|
if (escape):
|
||||||
if (byte == HDLC.FLAG ^ HDLC.ESC_MASK):
|
if (byte == HDLC.FLAG ^ HDLC.ESC_MASK):
|
||||||
byte = HDLC.FLAG
|
byte = HDLC.FLAG
|
||||||
if (byte == HDLC.ESC ^ HDLC.ESC_MASK):
|
if (byte == HDLC.ESC ^ HDLC.ESC_MASK):
|
||||||
byte = HDLC.ESC
|
byte = HDLC.ESC
|
||||||
escape = False
|
escape = False
|
||||||
data_buffer = data_buffer+bytes([byte])
|
data_buffer = data_buffer+bytes([byte])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
time_since_last = int(time.time()*1000) - last_read_ms
|
time_since_last = int(time.time()*1000) - last_read_ms
|
||||||
if len(data_buffer) > 0 and time_since_last > self.timeout:
|
if len(data_buffer) > 0 and time_since_last > self.timeout:
|
||||||
data_buffer = b""
|
data_buffer = b""
|
||||||
in_frame = False
|
in_frame = False
|
||||||
escape = False
|
escape = False
|
||||||
sleep(0.08)
|
sleep(0.08)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.online = False
|
self.online = False
|
||||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
|
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "SerialInterface["+self.name+"]"
|
return "SerialInterface["+self.name+"]"
|
||||||
|
1032
RNS/Link.py
1032
RNS/Link.py
File diff suppressed because it is too large
Load Diff
694
RNS/Packet.py
694
RNS/Packet.py
@ -5,411 +5,411 @@ import time
|
|||||||
import RNS
|
import RNS
|
||||||
|
|
||||||
class Packet:
|
class Packet:
|
||||||
# Packet types
|
# Packet types
|
||||||
DATA = 0x00 # Data packets
|
DATA = 0x00 # Data packets
|
||||||
ANNOUNCE = 0x01 # Announces
|
ANNOUNCE = 0x01 # Announces
|
||||||
LINKREQUEST = 0x02 # Link requests
|
LINKREQUEST = 0x02 # Link requests
|
||||||
PROOF = 0x03 # Proofs
|
PROOF = 0x03 # Proofs
|
||||||
types = [DATA, ANNOUNCE, LINKREQUEST, PROOF]
|
types = [DATA, ANNOUNCE, LINKREQUEST, PROOF]
|
||||||
|
|
||||||
# Header types
|
# Header types
|
||||||
HEADER_1 = 0x00 # Normal header format
|
HEADER_1 = 0x00 # Normal header format
|
||||||
HEADER_2 = 0x01 # Header format used for packets in transport
|
HEADER_2 = 0x01 # Header format used for packets in transport
|
||||||
HEADER_3 = 0x02 # Reserved
|
HEADER_3 = 0x02 # Reserved
|
||||||
HEADER_4 = 0x03 # Reserved
|
HEADER_4 = 0x03 # Reserved
|
||||||
header_types = [HEADER_1, HEADER_2, HEADER_3, HEADER_4]
|
header_types = [HEADER_1, HEADER_2, HEADER_3, HEADER_4]
|
||||||
|
|
||||||
# Data packet context types
|
# Data packet context types
|
||||||
NONE = 0x00 # Generic data packet
|
NONE = 0x00 # Generic data packet
|
||||||
RESOURCE = 0x01 # Packet is part of a resource
|
RESOURCE = 0x01 # Packet is part of a resource
|
||||||
RESOURCE_ADV = 0x02 # Packet is a resource advertisement
|
RESOURCE_ADV = 0x02 # Packet is a resource advertisement
|
||||||
RESOURCE_REQ = 0x03 # Packet is a resource part request
|
RESOURCE_REQ = 0x03 # Packet is a resource part request
|
||||||
RESOURCE_HMU = 0x04 # Packet is a resource hashmap update
|
RESOURCE_HMU = 0x04 # Packet is a resource hashmap update
|
||||||
RESOURCE_PRF = 0x05 # Packet is a resource proof
|
RESOURCE_PRF = 0x05 # Packet is a resource proof
|
||||||
RESOURCE_ICL = 0x06 # Packet is a resource initiator cancel message
|
RESOURCE_ICL = 0x06 # Packet is a resource initiator cancel message
|
||||||
RESOURCE_RCL = 0x07 # Packet is a resource receiver cancel message
|
RESOURCE_RCL = 0x07 # Packet is a resource receiver cancel message
|
||||||
CACHE_REQUEST = 0x08 # Packet is a cache request
|
CACHE_REQUEST = 0x08 # Packet is a cache request
|
||||||
REQUEST = 0x09 # Packet is a request
|
REQUEST = 0x09 # Packet is a request
|
||||||
RESPONSE = 0x0A # Packet is a response to a request
|
RESPONSE = 0x0A # Packet is a response to a request
|
||||||
PATH_RESPONSE = 0x0B # Packet is a response to a path request
|
PATH_RESPONSE = 0x0B # Packet is a response to a path request
|
||||||
COMMAND = 0x0C # Packet is a command
|
COMMAND = 0x0C # Packet is a command
|
||||||
COMMAND_STATUS = 0x0D # Packet is a status of an executed command
|
COMMAND_STATUS = 0x0D # Packet is a status of an executed command
|
||||||
KEEPALIVE = 0xFB # Packet is a keepalive packet
|
KEEPALIVE = 0xFB # Packet is a keepalive packet
|
||||||
LINKCLOSE = 0xFC # Packet is a link close message
|
LINKCLOSE = 0xFC # Packet is a link close message
|
||||||
LINKPROOF = 0xFD # Packet is a link packet proof
|
LINKPROOF = 0xFD # Packet is a link packet proof
|
||||||
LRRTT = 0xFE # Packet is a link request round-trip time measurement
|
LRRTT = 0xFE # Packet is a link request round-trip time measurement
|
||||||
LRPROOF = 0xFF # Packet is a link request proof
|
LRPROOF = 0xFF # Packet is a link request proof
|
||||||
|
|
||||||
# This is used to calculate allowable
|
# This is used to calculate allowable
|
||||||
# payload sizes
|
# payload sizes
|
||||||
HEADER_MAXSIZE = 23
|
HEADER_MAXSIZE = 23
|
||||||
MDU = RNS.Reticulum.MDU
|
MDU = RNS.Reticulum.MDU
|
||||||
|
|
||||||
# With an MTU of 500, the maximum RSA-encrypted
|
# With an MTU of 500, the maximum RSA-encrypted
|
||||||
# amount of data we can send in a single packet
|
# amount of data we can send in a single packet
|
||||||
# is given by the below calculation; 258 bytes.
|
# is given by the below calculation; 258 bytes.
|
||||||
RSA_MDU = math.floor(MDU/RNS.Identity.DECRYPT_CHUNKSIZE)*RNS.Identity.ENCRYPT_CHUNKSIZE
|
RSA_MDU = math.floor(MDU/RNS.Identity.DECRYPT_CHUNKSIZE)*RNS.Identity.ENCRYPT_CHUNKSIZE
|
||||||
PLAIN_MDU = MDU
|
PLAIN_MDU = MDU
|
||||||
|
|
||||||
# TODO: This should be calculated
|
# TODO: This should be calculated
|
||||||
# more intelligently
|
# more intelligently
|
||||||
# Default packet timeout
|
# Default packet timeout
|
||||||
TIMEOUT = 60
|
TIMEOUT = 60
|
||||||
|
|
||||||
def __init__(self, destination, data, packet_type = DATA, context = NONE, transport_type = RNS.Transport.BROADCAST, header_type = HEADER_1, transport_id = None, attached_interface = None, create_receipt = True):
|
def __init__(self, destination, data, packet_type = DATA, context = NONE, transport_type = RNS.Transport.BROADCAST, header_type = HEADER_1, transport_id = None, attached_interface = None, create_receipt = True):
|
||||||
if destination != None:
|
if destination != None:
|
||||||
if transport_type == None:
|
if transport_type == None:
|
||||||
transport_type = RNS.Transport.BROADCAST
|
transport_type = RNS.Transport.BROADCAST
|
||||||
|
|
||||||
self.header_type = header_type
|
self.header_type = header_type
|
||||||
self.packet_type = packet_type
|
self.packet_type = packet_type
|
||||||
self.transport_type = transport_type
|
self.transport_type = transport_type
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
self.hops = 0;
|
self.hops = 0;
|
||||||
self.destination = destination
|
self.destination = destination
|
||||||
self.transport_id = transport_id
|
self.transport_id = transport_id
|
||||||
self.data = data
|
self.data = data
|
||||||
self.flags = self.getPackedFlags()
|
self.flags = self.getPackedFlags()
|
||||||
|
|
||||||
self.raw = None
|
self.raw = None
|
||||||
self.packed = False
|
self.packed = False
|
||||||
self.sent = False
|
self.sent = False
|
||||||
self.create_receipt = create_receipt
|
self.create_receipt = create_receipt
|
||||||
self.receipt = None
|
self.receipt = None
|
||||||
self.fromPacked = False
|
self.fromPacked = False
|
||||||
else:
|
else:
|
||||||
self.raw = data
|
self.raw = data
|
||||||
self.packed = True
|
self.packed = True
|
||||||
self.fromPacked = True
|
self.fromPacked = True
|
||||||
self.create_receipt = False
|
self.create_receipt = False
|
||||||
|
|
||||||
self.MTU = RNS.Reticulum.MTU
|
self.MTU = RNS.Reticulum.MTU
|
||||||
self.sent_at = None
|
self.sent_at = None
|
||||||
self.packet_hash = None
|
self.packet_hash = None
|
||||||
|
|
||||||
self.attached_interface = attached_interface
|
self.attached_interface = attached_interface
|
||||||
self.receiving_interface = None
|
self.receiving_interface = None
|
||||||
|
|
||||||
def getPackedFlags(self):
|
def getPackedFlags(self):
|
||||||
if self.context == Packet.LRPROOF:
|
if self.context == Packet.LRPROOF:
|
||||||
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | RNS.Destination.LINK | self.packet_type
|
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | RNS.Destination.LINK | self.packet_type
|
||||||
else:
|
else:
|
||||||
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | (self.destination.type << 2) | self.packet_type
|
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | (self.destination.type << 2) | self.packet_type
|
||||||
return packed_flags
|
return packed_flags
|
||||||
|
|
||||||
def pack(self):
|
def pack(self):
|
||||||
self.destination_hash = self.destination.hash
|
self.destination_hash = self.destination.hash
|
||||||
self.header = b""
|
self.header = b""
|
||||||
self.header += struct.pack("!B", self.flags)
|
self.header += struct.pack("!B", self.flags)
|
||||||
self.header += struct.pack("!B", self.hops)
|
self.header += struct.pack("!B", self.hops)
|
||||||
|
|
||||||
if self.context == Packet.LRPROOF:
|
if self.context == Packet.LRPROOF:
|
||||||
self.header += self.destination.link_id
|
self.header += self.destination.link_id
|
||||||
self.ciphertext = self.data
|
self.ciphertext = self.data
|
||||||
else:
|
else:
|
||||||
if self.header_type == Packet.HEADER_1:
|
if self.header_type == Packet.HEADER_1:
|
||||||
self.header += self.destination.hash
|
self.header += self.destination.hash
|
||||||
|
|
||||||
if self.packet_type == Packet.ANNOUNCE:
|
if self.packet_type == Packet.ANNOUNCE:
|
||||||
# Announce packets are not encrypted
|
# Announce packets are not encrypted
|
||||||
self.ciphertext = self.data
|
self.ciphertext = self.data
|
||||||
elif self.packet_type == Packet.PROOF and self.context == Packet.RESOURCE_PRF:
|
elif self.packet_type == Packet.PROOF and self.context == Packet.RESOURCE_PRF:
|
||||||
# Resource proofs are not encrypted
|
# Resource proofs are not encrypted
|
||||||
self.ciphertext = self.data
|
self.ciphertext = self.data
|
||||||
elif self.packet_type == Packet.PROOF and self.destination.type == RNS.Destination.LINK:
|
elif self.packet_type == Packet.PROOF and self.destination.type == RNS.Destination.LINK:
|
||||||
# Packet proofs over links are not encrypted
|
# Packet proofs over links are not encrypted
|
||||||
self.ciphertext = self.data
|
self.ciphertext = self.data
|
||||||
elif self.context == Packet.RESOURCE:
|
elif self.context == Packet.RESOURCE:
|
||||||
# A resource takes care of symmetric
|
# A resource takes care of symmetric
|
||||||
# encryption by itself
|
# encryption by itself
|
||||||
self.ciphertext = self.data
|
self.ciphertext = self.data
|
||||||
elif self.context == Packet.KEEPALIVE:
|
elif self.context == Packet.KEEPALIVE:
|
||||||
# Keepalive packets contain no actual
|
# Keepalive packets contain no actual
|
||||||
# data
|
# data
|
||||||
self.ciphertext = self.data
|
self.ciphertext = self.data
|
||||||
elif self.context == Packet.CACHE_REQUEST:
|
elif self.context == Packet.CACHE_REQUEST:
|
||||||
# Cache-requests are not encrypted
|
# Cache-requests are not encrypted
|
||||||
self.ciphertext = self.data
|
self.ciphertext = self.data
|
||||||
else:
|
else:
|
||||||
# In all other cases, we encrypt the packet
|
# In all other cases, we encrypt the packet
|
||||||
# with the destination's encryption method
|
# with the destination's encryption method
|
||||||
self.ciphertext = self.destination.encrypt(self.data)
|
self.ciphertext = self.destination.encrypt(self.data)
|
||||||
|
|
||||||
if self.header_type == Packet.HEADER_2:
|
if self.header_type == Packet.HEADER_2:
|
||||||
if self.transport_id != None:
|
if self.transport_id != None:
|
||||||
self.header += self.transport_id
|
self.header += self.transport_id
|
||||||
self.header += self.destination.hash
|
self.header += self.destination.hash
|
||||||
|
|
||||||
if self.packet_type == Packet.ANNOUNCE:
|
if self.packet_type == Packet.ANNOUNCE:
|
||||||
# Announce packets are not encrypted
|
# Announce packets are not encrypted
|
||||||
self.ciphertext = self.data
|
self.ciphertext = self.data
|
||||||
else:
|
else:
|
||||||
raise IOError("Packet with header type 2 must have a transport ID")
|
raise IOError("Packet with header type 2 must have a transport ID")
|
||||||
|
|
||||||
|
|
||||||
self.header += bytes([self.context])
|
self.header += bytes([self.context])
|
||||||
self.raw = self.header + self.ciphertext
|
self.raw = self.header + self.ciphertext
|
||||||
|
|
||||||
if len(self.raw) > self.MTU:
|
if len(self.raw) > self.MTU:
|
||||||
raise IOError("Packet size of "+str(len(self.raw))+" exceeds MTU of "+str(self.MTU)+" bytes")
|
raise IOError("Packet size of "+str(len(self.raw))+" exceeds MTU of "+str(self.MTU)+" bytes")
|
||||||
|
|
||||||
self.packed = True
|
self.packed = True
|
||||||
self.updateHash()
|
self.updateHash()
|
||||||
|
|
||||||
def unpack(self):
|
def unpack(self):
|
||||||
self.flags = self.raw[0]
|
self.flags = self.raw[0]
|
||||||
self.hops = self.raw[1]
|
self.hops = self.raw[1]
|
||||||
|
|
||||||
self.header_type = (self.flags & 0b11000000) >> 6
|
self.header_type = (self.flags & 0b11000000) >> 6
|
||||||
self.transport_type = (self.flags & 0b00110000) >> 4
|
self.transport_type = (self.flags & 0b00110000) >> 4
|
||||||
self.destination_type = (self.flags & 0b00001100) >> 2
|
self.destination_type = (self.flags & 0b00001100) >> 2
|
||||||
self.packet_type = (self.flags & 0b00000011)
|
self.packet_type = (self.flags & 0b00000011)
|
||||||
|
|
||||||
if self.header_type == Packet.HEADER_2:
|
if self.header_type == Packet.HEADER_2:
|
||||||
self.transport_id = self.raw[2:12]
|
self.transport_id = self.raw[2:12]
|
||||||
self.destination_hash = self.raw[12:22]
|
self.destination_hash = self.raw[12:22]
|
||||||
self.context = ord(self.raw[22:23])
|
self.context = ord(self.raw[22:23])
|
||||||
self.data = self.raw[23:]
|
self.data = self.raw[23:]
|
||||||
else:
|
else:
|
||||||
self.transport_id = None
|
self.transport_id = None
|
||||||
self.destination_hash = self.raw[2:12]
|
self.destination_hash = self.raw[2:12]
|
||||||
self.context = ord(self.raw[12:13])
|
self.context = ord(self.raw[12:13])
|
||||||
self.data = self.raw[13:]
|
self.data = self.raw[13:]
|
||||||
|
|
||||||
self.packed = False
|
self.packed = False
|
||||||
self.updateHash()
|
self.updateHash()
|
||||||
|
|
||||||
# Sends the packet. Returns a receipt if one is generated,
|
# Sends the packet. Returns a receipt if one is generated,
|
||||||
# or None if no receipt is available. Returns False if the
|
# or None if no receipt is available. Returns False if the
|
||||||
# packet could not be sent.
|
# packet could not be sent.
|
||||||
def send(self):
|
def send(self):
|
||||||
if not self.sent:
|
if not self.sent:
|
||||||
if self.destination.type == RNS.Destination.LINK:
|
if self.destination.type == RNS.Destination.LINK:
|
||||||
if self.destination.status == RNS.Link.CLOSED:
|
if self.destination.status == RNS.Link.CLOSED:
|
||||||
raise IOError("Attempt to transmit over a closed link")
|
raise IOError("Attempt to transmit over a closed link")
|
||||||
else:
|
else:
|
||||||
self.destination.last_outbound = time.time()
|
self.destination.last_outbound = time.time()
|
||||||
self.destination.tx += 1
|
self.destination.tx += 1
|
||||||
self.destination.txbytes += len(self.data)
|
self.destination.txbytes += len(self.data)
|
||||||
|
|
||||||
if not self.packed:
|
if not self.packed:
|
||||||
self.pack()
|
self.pack()
|
||||||
|
|
||||||
if RNS.Transport.outbound(self):
|
if RNS.Transport.outbound(self):
|
||||||
return self.receipt
|
return self.receipt
|
||||||
else:
|
else:
|
||||||
RNS.log("No interfaces could process the outbound packet", RNS.LOG_ERROR)
|
RNS.log("No interfaces could process the outbound packet", RNS.LOG_ERROR)
|
||||||
self.sent = False
|
self.sent = False
|
||||||
self.receipt = None
|
self.receipt = None
|
||||||
return False
|
return False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise IOError("Packet was already sent")
|
raise IOError("Packet was already sent")
|
||||||
|
|
||||||
def resend(self):
|
def resend(self):
|
||||||
if self.sent:
|
if self.sent:
|
||||||
if RNS.Transport.outbound(self):
|
if RNS.Transport.outbound(self):
|
||||||
return self.receipt
|
return self.receipt
|
||||||
else:
|
else:
|
||||||
RNS.log("No interfaces could process the outbound packet", RNS.LOG_ERROR)
|
RNS.log("No interfaces could process the outbound packet", RNS.LOG_ERROR)
|
||||||
self.sent = False
|
self.sent = False
|
||||||
self.receipt = None
|
self.receipt = None
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
raise IOError("Packet was not sent yet")
|
raise IOError("Packet was not sent yet")
|
||||||
|
|
||||||
def prove(self, destination=None):
|
def prove(self, destination=None):
|
||||||
if self.fromPacked and hasattr(self, "destination") and self.destination:
|
if self.fromPacked and hasattr(self, "destination") and self.destination:
|
||||||
if self.destination.identity and self.destination.identity.prv:
|
if self.destination.identity and self.destination.identity.prv:
|
||||||
self.destination.identity.prove(self, destination)
|
self.destination.identity.prove(self, destination)
|
||||||
elif self.fromPacked and hasattr(self, "link") and self.link:
|
elif self.fromPacked and hasattr(self, "link") and self.link:
|
||||||
self.link.prove_packet(self)
|
self.link.prove_packet(self)
|
||||||
else:
|
else:
|
||||||
RNS.log("Could not prove packet associated with neither a destination nor a link", RNS.LOG_ERROR)
|
RNS.log("Could not prove packet associated with neither a destination nor a link", RNS.LOG_ERROR)
|
||||||
|
|
||||||
# Generates a special destination that allows Reticulum
|
# Generates a special destination that allows Reticulum
|
||||||
# to direct the proof back to the proved packet's sender
|
# to direct the proof back to the proved packet's sender
|
||||||
def generateProofDestination(self):
|
def generateProofDestination(self):
|
||||||
return ProofDestination(self)
|
return ProofDestination(self)
|
||||||
|
|
||||||
def validateProofPacket(self, proof_packet):
|
def validateProofPacket(self, proof_packet):
|
||||||
return self.receipt.validateProofPacket(proof_packet)
|
return self.receipt.validateProofPacket(proof_packet)
|
||||||
|
|
||||||
def validateProof(self, proof):
|
def validateProof(self, proof):
|
||||||
return self.receipt.validateProof(proof)
|
return self.receipt.validateProof(proof)
|
||||||
|
|
||||||
def updateHash(self):
|
def updateHash(self):
|
||||||
self.packet_hash = self.getHash()
|
self.packet_hash = self.getHash()
|
||||||
|
|
||||||
def getHash(self):
|
def getHash(self):
|
||||||
return RNS.Identity.fullHash(self.getHashablePart())
|
return RNS.Identity.fullHash(self.getHashablePart())
|
||||||
|
|
||||||
def getTruncatedHash(self):
|
def getTruncatedHash(self):
|
||||||
return RNS.Identity.truncatedHash(self.getHashablePart())
|
return RNS.Identity.truncatedHash(self.getHashablePart())
|
||||||
|
|
||||||
def getHashablePart(self):
|
def getHashablePart(self):
|
||||||
hashable_part = bytes([self.raw[0] & 0b00001111])
|
hashable_part = bytes([self.raw[0] & 0b00001111])
|
||||||
if self.header_type == Packet.HEADER_2:
|
if self.header_type == Packet.HEADER_2:
|
||||||
hashable_part += self.raw[12:]
|
hashable_part += self.raw[12:]
|
||||||
else:
|
else:
|
||||||
hashable_part += self.raw[2:]
|
hashable_part += self.raw[2:]
|
||||||
|
|
||||||
return hashable_part
|
return hashable_part
|
||||||
|
|
||||||
class ProofDestination:
|
class ProofDestination:
|
||||||
def __init__(self, packet):
|
def __init__(self, packet):
|
||||||
self.hash = packet.getHash()[:10];
|
self.hash = packet.getHash()[:10];
|
||||||
self.type = RNS.Destination.SINGLE
|
self.type = RNS.Destination.SINGLE
|
||||||
|
|
||||||
def encrypt(self, plaintext):
|
def encrypt(self, plaintext):
|
||||||
return plaintext
|
return plaintext
|
||||||
|
|
||||||
|
|
||||||
class PacketReceipt:
|
class PacketReceipt:
|
||||||
# Receipt status constants
|
# Receipt status constants
|
||||||
FAILED = 0x00
|
FAILED = 0x00
|
||||||
SENT = 0x01
|
SENT = 0x01
|
||||||
DELIVERED = 0x02
|
DELIVERED = 0x02
|
||||||
CULLED = 0xFF
|
CULLED = 0xFF
|
||||||
|
|
||||||
|
|
||||||
EXPL_LENGTH = RNS.Identity.HASHLENGTH//8+RNS.Identity.SIGLENGTH//8
|
EXPL_LENGTH = RNS.Identity.HASHLENGTH//8+RNS.Identity.SIGLENGTH//8
|
||||||
IMPL_LENGTH = RNS.Identity.SIGLENGTH//8
|
IMPL_LENGTH = RNS.Identity.SIGLENGTH//8
|
||||||
|
|
||||||
# Creates a new packet receipt from a sent packet
|
# Creates a new packet receipt from a sent packet
|
||||||
def __init__(self, packet):
|
def __init__(self, packet):
|
||||||
self.hash = packet.getHash()
|
self.hash = packet.getHash()
|
||||||
self.sent = True
|
self.sent = True
|
||||||
self.sent_at = time.time()
|
self.sent_at = time.time()
|
||||||
self.timeout = Packet.TIMEOUT
|
self.timeout = Packet.TIMEOUT
|
||||||
self.proved = False
|
self.proved = False
|
||||||
self.status = PacketReceipt.SENT
|
self.status = PacketReceipt.SENT
|
||||||
self.destination = packet.destination
|
self.destination = packet.destination
|
||||||
self.callbacks = PacketReceiptCallbacks()
|
self.callbacks = PacketReceiptCallbacks()
|
||||||
self.concluded_at = None
|
self.concluded_at = None
|
||||||
|
|
||||||
# Validate a proof packet
|
# Validate a proof packet
|
||||||
def validateProofPacket(self, proof_packet):
|
def validateProofPacket(self, proof_packet):
|
||||||
if hasattr(proof_packet, "link") and proof_packet.link:
|
if hasattr(proof_packet, "link") and proof_packet.link:
|
||||||
return self.validate_link_proof(proof_packet.data, proof_packet.link)
|
return self.validate_link_proof(proof_packet.data, proof_packet.link)
|
||||||
else:
|
else:
|
||||||
return self.validateProof(proof_packet.data)
|
return self.validateProof(proof_packet.data)
|
||||||
|
|
||||||
# Validate a raw proof for a link
|
# Validate a raw proof for a link
|
||||||
def validate_link_proof(self, proof, link):
|
def validate_link_proof(self, proof, link):
|
||||||
# TODO: Hardcoded as explicit proofs for now
|
# TODO: Hardcoded as explicit proofs for now
|
||||||
if True or len(proof) == PacketReceipt.EXPL_LENGTH:
|
if True or len(proof) == PacketReceipt.EXPL_LENGTH:
|
||||||
# This is an explicit proof
|
# This is an explicit proof
|
||||||
proof_hash = proof[:RNS.Identity.HASHLENGTH//8]
|
proof_hash = proof[:RNS.Identity.HASHLENGTH//8]
|
||||||
signature = proof[RNS.Identity.HASHLENGTH//8:RNS.Identity.HASHLENGTH//8+RNS.Identity.SIGLENGTH//8]
|
signature = proof[RNS.Identity.HASHLENGTH//8:RNS.Identity.HASHLENGTH//8+RNS.Identity.SIGLENGTH//8]
|
||||||
if proof_hash == self.hash:
|
if proof_hash == self.hash:
|
||||||
proof_valid = link.validate(signature, self.hash)
|
proof_valid = link.validate(signature, self.hash)
|
||||||
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()
|
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
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
elif len(proof) == PacketReceipt.IMPL_LENGTH:
|
elif len(proof) == PacketReceipt.IMPL_LENGTH:
|
||||||
pass
|
pass
|
||||||
# TODO: Why is this disabled?
|
# TODO: Why is this disabled?
|
||||||
# signature = proof[:RNS.Identity.SIGLENGTH//8]
|
# signature = proof[:RNS.Identity.SIGLENGTH//8]
|
||||||
# proof_valid = self.link.validate(signature, self.hash)
|
# proof_valid = self.link.validate(signature, self.hash)
|
||||||
# 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()
|
# self.concluded_at = time.time()
|
||||||
# if self.callbacks.delivery != None:
|
# if self.callbacks.delivery != None:
|
||||||
# self.callbacks.delivery(self)
|
# self.callbacks.delivery(self)
|
||||||
# RNS.log("valid")
|
# RNS.log("valid")
|
||||||
# return True
|
# return True
|
||||||
# else:
|
# else:
|
||||||
# RNS.log("invalid")
|
# RNS.log("invalid")
|
||||||
# return False
|
# return False
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Validate a raw proof
|
# Validate a raw proof
|
||||||
def validateProof(self, proof):
|
def validateProof(self, proof):
|
||||||
if len(proof) == PacketReceipt.EXPL_LENGTH:
|
if len(proof) == PacketReceipt.EXPL_LENGTH:
|
||||||
# This is an explicit proof
|
# This is an explicit proof
|
||||||
proof_hash = proof[:RNS.Identity.HASHLENGTH//8]
|
proof_hash = proof[:RNS.Identity.HASHLENGTH//8]
|
||||||
signature = proof[RNS.Identity.HASHLENGTH//8:RNS.Identity.HASHLENGTH//8+RNS.Identity.SIGLENGTH//8]
|
signature = proof[RNS.Identity.HASHLENGTH//8:RNS.Identity.HASHLENGTH//8+RNS.Identity.SIGLENGTH//8]
|
||||||
if proof_hash == self.hash:
|
if proof_hash == self.hash:
|
||||||
proof_valid = self.destination.identity.validate(signature, self.hash)
|
proof_valid = self.destination.identity.validate(signature, self.hash)
|
||||||
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()
|
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
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
elif len(proof) == PacketReceipt.IMPL_LENGTH:
|
elif len(proof) == PacketReceipt.IMPL_LENGTH:
|
||||||
# This is an implicit proof
|
# This is an implicit proof
|
||||||
if self.destination.identity == None:
|
if self.destination.identity == None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
signature = proof[:RNS.Identity.SIGLENGTH//8]
|
signature = proof[:RNS.Identity.SIGLENGTH//8]
|
||||||
proof_valid = self.destination.identity.validate(signature, self.hash)
|
proof_valid = self.destination.identity.validate(signature, self.hash)
|
||||||
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()
|
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
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def rtt(self):
|
def rtt(self):
|
||||||
return self.concluded_at - self.sent_at
|
return self.concluded_at - self.sent_at
|
||||||
|
|
||||||
def is_timed_out(self):
|
def is_timed_out(self):
|
||||||
return (self.sent_at+self.timeout < time.time())
|
return (self.sent_at+self.timeout < time.time())
|
||||||
|
|
||||||
def check_timeout(self):
|
def check_timeout(self):
|
||||||
if self.is_timed_out():
|
if self.is_timed_out():
|
||||||
if self.timeout == -1:
|
if self.timeout == -1:
|
||||||
self.status = PacketReceipt.CULLED
|
self.status = PacketReceipt.CULLED
|
||||||
else:
|
else:
|
||||||
self.status = PacketReceipt.FAILED
|
self.status = PacketReceipt.FAILED
|
||||||
|
|
||||||
self.concluded_at = time.time()
|
self.concluded_at = time.time()
|
||||||
|
|
||||||
if self.callbacks.timeout:
|
if self.callbacks.timeout:
|
||||||
thread = threading.Thread(target=self.callbacks.timeout, args=(self,))
|
thread = threading.Thread(target=self.callbacks.timeout, args=(self,))
|
||||||
thread.setDaemon(True)
|
thread.setDaemon(True)
|
||||||
thread.start()
|
thread.start()
|
||||||
#self.callbacks.timeout(self)
|
#self.callbacks.timeout(self)
|
||||||
|
|
||||||
|
|
||||||
# Set the timeout in seconds
|
# Set the timeout in seconds
|
||||||
def set_timeout(self, timeout):
|
def set_timeout(self, timeout):
|
||||||
self.timeout = float(timeout)
|
self.timeout = float(timeout)
|
||||||
|
|
||||||
# Set a function that gets called when
|
# Set a function that gets called when
|
||||||
# a successfull delivery has been proved
|
# a successfull delivery has been proved
|
||||||
def delivery_callback(self, callback):
|
def delivery_callback(self, callback):
|
||||||
self.callbacks.delivery = callback
|
self.callbacks.delivery = callback
|
||||||
|
|
||||||
# Set a function that gets called if the
|
# Set a function that gets called if the
|
||||||
# delivery times out
|
# delivery times out
|
||||||
def timeout_callback(self, callback):
|
def timeout_callback(self, callback):
|
||||||
self.callbacks.timeout = callback
|
self.callbacks.timeout = callback
|
||||||
|
|
||||||
class PacketReceiptCallbacks:
|
class PacketReceiptCallbacks:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.delivery = None
|
self.delivery = None
|
||||||
self.timeout = None
|
self.timeout = None
|
658
RNS/Reticulum.py
658
RNS/Reticulum.py
@ -10,387 +10,387 @@ import os
|
|||||||
import RNS
|
import RNS
|
||||||
|
|
||||||
class Reticulum:
|
class Reticulum:
|
||||||
MTU = 500
|
MTU = 500
|
||||||
HEADER_MAXSIZE = 23
|
HEADER_MAXSIZE = 23
|
||||||
MDU = MTU - HEADER_MAXSIZE
|
MDU = MTU - HEADER_MAXSIZE
|
||||||
|
|
||||||
router = None
|
router = None
|
||||||
config = None
|
config = None
|
||||||
|
|
||||||
configdir = os.path.expanduser("~")+"/.reticulum"
|
configdir = os.path.expanduser("~")+"/.reticulum"
|
||||||
configpath = ""
|
configpath = ""
|
||||||
storagepath = ""
|
storagepath = ""
|
||||||
cachepath = ""
|
cachepath = ""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def exit_handler():
|
def exit_handler():
|
||||||
RNS.Transport.exitHandler()
|
RNS.Transport.exitHandler()
|
||||||
RNS.Identity.exitHandler()
|
RNS.Identity.exitHandler()
|
||||||
|
|
||||||
def __init__(self,configdir=None):
|
def __init__(self,configdir=None):
|
||||||
if configdir != None:
|
if configdir != None:
|
||||||
Reticulum.configdir = configdir
|
Reticulum.configdir = configdir
|
||||||
|
|
||||||
Reticulum.configpath = Reticulum.configdir+"/config"
|
Reticulum.configpath = Reticulum.configdir+"/config"
|
||||||
Reticulum.storagepath = Reticulum.configdir+"/storage"
|
Reticulum.storagepath = Reticulum.configdir+"/storage"
|
||||||
Reticulum.cachepath = Reticulum.configdir+"/storage/cache"
|
Reticulum.cachepath = Reticulum.configdir+"/storage/cache"
|
||||||
Reticulum.resourcepath = Reticulum.configdir+"/storage/resources"
|
Reticulum.resourcepath = Reticulum.configdir+"/storage/resources"
|
||||||
|
|
||||||
Reticulum.__allow_unencrypted = False
|
Reticulum.__allow_unencrypted = False
|
||||||
Reticulum.__transport_enabled = False
|
Reticulum.__transport_enabled = False
|
||||||
Reticulum.__use_implicit_proof = True
|
Reticulum.__use_implicit_proof = True
|
||||||
|
|
||||||
self.local_interface_port = 37428
|
self.local_interface_port = 37428
|
||||||
self.share_instance = True
|
self.share_instance = True
|
||||||
|
|
||||||
self.is_shared_instance = False
|
self.is_shared_instance = False
|
||||||
self.is_connected_to_shared_instance = False
|
self.is_connected_to_shared_instance = False
|
||||||
self.is_standalone_instance = False
|
self.is_standalone_instance = False
|
||||||
|
|
||||||
if not os.path.isdir(Reticulum.storagepath):
|
if not os.path.isdir(Reticulum.storagepath):
|
||||||
os.makedirs(Reticulum.storagepath)
|
os.makedirs(Reticulum.storagepath)
|
||||||
|
|
||||||
if not os.path.isdir(Reticulum.cachepath):
|
if not os.path.isdir(Reticulum.cachepath):
|
||||||
os.makedirs(Reticulum.cachepath)
|
os.makedirs(Reticulum.cachepath)
|
||||||
|
|
||||||
if not os.path.isdir(Reticulum.resourcepath):
|
if not os.path.isdir(Reticulum.resourcepath):
|
||||||
os.makedirs(Reticulum.resourcepath)
|
os.makedirs(Reticulum.resourcepath)
|
||||||
|
|
||||||
if os.path.isfile(self.configpath):
|
if os.path.isfile(self.configpath):
|
||||||
try:
|
try:
|
||||||
self.config = ConfigObj(self.configpath)
|
self.config = ConfigObj(self.configpath)
|
||||||
RNS.log("Configuration loaded from "+self.configpath)
|
RNS.log("Configuration loaded from "+self.configpath)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Could not parse the configuration at "+self.configpath, RNS.LOG_ERROR)
|
RNS.log("Could not parse the configuration at "+self.configpath, RNS.LOG_ERROR)
|
||||||
RNS.log("Check your configuration file for errors!", RNS.LOG_ERROR)
|
RNS.log("Check your configuration file for errors!", RNS.LOG_ERROR)
|
||||||
RNS.panic()
|
RNS.panic()
|
||||||
else:
|
else:
|
||||||
RNS.log("Could not load config file, creating default configuration file...")
|
RNS.log("Could not load config file, creating default configuration file...")
|
||||||
self.createDefaultConfig()
|
self.createDefaultConfig()
|
||||||
RNS.log("Default config file created. Make any necessary changes in "+Reticulum.configdir+"/config and start Reticulum again.")
|
RNS.log("Default config file created. Make any necessary changes in "+Reticulum.configdir+"/config and start Reticulum again.")
|
||||||
RNS.log("Exiting now!")
|
RNS.log("Exiting now!")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
self.applyConfig()
|
self.applyConfig()
|
||||||
RNS.Identity.loadKnownDestinations()
|
RNS.Identity.loadKnownDestinations()
|
||||||
|
|
||||||
RNS.Transport.start(self)
|
RNS.Transport.start(self)
|
||||||
|
|
||||||
atexit.register(Reticulum.exit_handler)
|
atexit.register(Reticulum.exit_handler)
|
||||||
|
|
||||||
def start_local_interface(self):
|
def start_local_interface(self):
|
||||||
if self.share_instance:
|
if self.share_instance:
|
||||||
try:
|
try:
|
||||||
interface = LocalInterface.LocalServerInterface(
|
interface = LocalInterface.LocalServerInterface(
|
||||||
RNS.Transport,
|
RNS.Transport,
|
||||||
self.local_interface_port
|
self.local_interface_port
|
||||||
)
|
)
|
||||||
interface.OUT = True
|
interface.OUT = True
|
||||||
RNS.Transport.interfaces.append(interface)
|
RNS.Transport.interfaces.append(interface)
|
||||||
self.is_shared_instance = True
|
self.is_shared_instance = True
|
||||||
RNS.log("Started shared instance interface: "+str(interface), RNS.LOG_DEBUG)
|
RNS.log("Started shared instance interface: "+str(interface), RNS.LOG_DEBUG)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
try:
|
try:
|
||||||
interface = LocalInterface.LocalClientInterface(
|
interface = LocalInterface.LocalClientInterface(
|
||||||
RNS.Transport,
|
RNS.Transport,
|
||||||
"Local shared instance",
|
"Local shared instance",
|
||||||
self.local_interface_port)
|
self.local_interface_port)
|
||||||
interface.target_port = self.local_interface_port
|
interface.target_port = self.local_interface_port
|
||||||
interface.OUT = True
|
interface.OUT = True
|
||||||
RNS.Transport.interfaces.append(interface)
|
RNS.Transport.interfaces.append(interface)
|
||||||
self.is_shared_instance = False
|
self.is_shared_instance = False
|
||||||
self.is_standalone_instance = False
|
self.is_standalone_instance = False
|
||||||
self.is_connected_to_shared_instance = True
|
self.is_connected_to_shared_instance = True
|
||||||
RNS.log("Connected to local shared instance via: "+str(interface), RNS.LOG_DEBUG)
|
RNS.log("Connected to local shared instance via: "+str(interface), RNS.LOG_DEBUG)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Local shared instance appears to be running, but it could not be connected", RNS.LOG_ERROR)
|
RNS.log("Local shared instance appears to be running, but it could not be connected", RNS.LOG_ERROR)
|
||||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
self.is_shared_instance = False
|
self.is_shared_instance = False
|
||||||
self.is_standalone_instance = True
|
self.is_standalone_instance = True
|
||||||
self.is_connected_to_shared_instance = False
|
self.is_connected_to_shared_instance = False
|
||||||
else:
|
else:
|
||||||
self.is_shared_instance = False
|
self.is_shared_instance = False
|
||||||
self.is_standalone_instance = True
|
self.is_standalone_instance = True
|
||||||
self.is_connected_to_shared_instance = False
|
self.is_connected_to_shared_instance = False
|
||||||
|
|
||||||
def applyConfig(self):
|
def applyConfig(self):
|
||||||
if "logging" in self.config:
|
if "logging" in self.config:
|
||||||
for option in self.config["logging"]:
|
for option in self.config["logging"]:
|
||||||
value = self.config["logging"][option]
|
value = self.config["logging"][option]
|
||||||
if option == "loglevel":
|
if option == "loglevel":
|
||||||
RNS.loglevel = int(value)
|
RNS.loglevel = int(value)
|
||||||
if RNS.loglevel < 0:
|
if RNS.loglevel < 0:
|
||||||
RNS.loglevel = 0
|
RNS.loglevel = 0
|
||||||
if RNS.loglevel > 7:
|
if RNS.loglevel > 7:
|
||||||
RNS.loglevel = 7
|
RNS.loglevel = 7
|
||||||
|
|
||||||
if "reticulum" in self.config:
|
if "reticulum" in self.config:
|
||||||
for option in self.config["reticulum"]:
|
for option in self.config["reticulum"]:
|
||||||
value = self.config["reticulum"][option]
|
value = self.config["reticulum"][option]
|
||||||
if option == "share_instance":
|
if option == "share_instance":
|
||||||
value = self.config["reticulum"].as_bool(option)
|
value = self.config["reticulum"].as_bool(option)
|
||||||
self.share_instance = value
|
self.share_instance = value
|
||||||
if option == "shared_instance_port":
|
if option == "shared_instance_port":
|
||||||
value = int(self.config["reticulum"][option])
|
value = int(self.config["reticulum"][option])
|
||||||
self.local_interface_port = value
|
self.local_interface_port = value
|
||||||
if option == "enable_transport":
|
if option == "enable_transport":
|
||||||
v = self.config["reticulum"].as_bool(option)
|
v = self.config["reticulum"].as_bool(option)
|
||||||
if v == True:
|
if v == True:
|
||||||
Reticulum.__transport_enabled = True
|
Reticulum.__transport_enabled = True
|
||||||
if option == "use_implicit_proof":
|
if option == "use_implicit_proof":
|
||||||
v = self.config["reticulum"].as_bool(option)
|
v = self.config["reticulum"].as_bool(option)
|
||||||
if v == True:
|
if v == True:
|
||||||
Reticulum.__use_implicit_proof = True
|
Reticulum.__use_implicit_proof = True
|
||||||
if v == False:
|
if v == False:
|
||||||
Reticulum.__use_implicit_proof = False
|
Reticulum.__use_implicit_proof = False
|
||||||
if option == "allow_unencrypted":
|
if option == "allow_unencrypted":
|
||||||
v = self.config["reticulum"].as_bool(option)
|
v = self.config["reticulum"].as_bool(option)
|
||||||
if v == True:
|
if v == True:
|
||||||
RNS.log("", RNS.LOG_CRITICAL)
|
RNS.log("", RNS.LOG_CRITICAL)
|
||||||
RNS.log("! ! ! ! ! ! ! ! !", RNS.LOG_CRITICAL)
|
RNS.log("! ! ! ! ! ! ! ! !", RNS.LOG_CRITICAL)
|
||||||
RNS.log("", RNS.LOG_CRITICAL)
|
RNS.log("", RNS.LOG_CRITICAL)
|
||||||
RNS.log("Danger! Encryptionless links have been allowed in the config file!", RNS.LOG_CRITICAL)
|
RNS.log("Danger! Encryptionless links have been allowed in the config file!", RNS.LOG_CRITICAL)
|
||||||
RNS.log("Beware of the consequences! Any data sent over a link can potentially be intercepted,", RNS.LOG_CRITICAL)
|
RNS.log("Beware of the consequences! Any data sent over a link can potentially be intercepted,", RNS.LOG_CRITICAL)
|
||||||
RNS.log("read and modified! If you are not absolutely sure that you want this,", RNS.LOG_CRITICAL)
|
RNS.log("read and modified! If you are not absolutely sure that you want this,", RNS.LOG_CRITICAL)
|
||||||
RNS.log("you should exit Reticulum NOW and change your config file!", RNS.LOG_CRITICAL)
|
RNS.log("you should exit Reticulum NOW and change your config file!", RNS.LOG_CRITICAL)
|
||||||
RNS.log("", RNS.LOG_CRITICAL)
|
RNS.log("", RNS.LOG_CRITICAL)
|
||||||
RNS.log("! ! ! ! ! ! ! ! !", RNS.LOG_CRITICAL)
|
RNS.log("! ! ! ! ! ! ! ! !", RNS.LOG_CRITICAL)
|
||||||
RNS.log("", RNS.LOG_CRITICAL)
|
RNS.log("", RNS.LOG_CRITICAL)
|
||||||
Reticulum.__allow_unencrypted = True
|
Reticulum.__allow_unencrypted = True
|
||||||
|
|
||||||
self.start_local_interface()
|
self.start_local_interface()
|
||||||
|
|
||||||
if self.is_shared_instance or self.is_standalone_instance:
|
if self.is_shared_instance or self.is_standalone_instance:
|
||||||
interface_names = []
|
interface_names = []
|
||||||
for name in self.config["interfaces"]:
|
for name in self.config["interfaces"]:
|
||||||
if not name in interface_names:
|
if not name in interface_names:
|
||||||
c = self.config["interfaces"][name]
|
c = self.config["interfaces"][name]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if ("interface_enabled" in c) and c.as_bool("interface_enabled") == True:
|
if ("interface_enabled" in c) and c.as_bool("interface_enabled") == True:
|
||||||
if c["type"] == "UdpInterface":
|
if c["type"] == "UdpInterface":
|
||||||
interface = UdpInterface.UdpInterface(
|
interface = UdpInterface.UdpInterface(
|
||||||
RNS.Transport,
|
RNS.Transport,
|
||||||
name,
|
name,
|
||||||
c["listen_ip"],
|
c["listen_ip"],
|
||||||
int(c["listen_port"]),
|
int(c["listen_port"]),
|
||||||
c["forward_ip"],
|
c["forward_ip"],
|
||||||
int(c["forward_port"])
|
int(c["forward_port"])
|
||||||
)
|
)
|
||||||
|
|
||||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||||
interface.OUT = True
|
interface.OUT = True
|
||||||
else:
|
else:
|
||||||
interface.OUT = False
|
interface.OUT = False
|
||||||
|
|
||||||
RNS.Transport.interfaces.append(interface)
|
RNS.Transport.interfaces.append(interface)
|
||||||
|
|
||||||
|
|
||||||
if c["type"] == "TCPServerInterface":
|
if c["type"] == "TCPServerInterface":
|
||||||
interface = TCPInterface.TCPServerInterface(
|
interface = TCPInterface.TCPServerInterface(
|
||||||
RNS.Transport,
|
RNS.Transport,
|
||||||
name,
|
name,
|
||||||
c["listen_ip"],
|
c["listen_ip"],
|
||||||
int(c["listen_port"])
|
int(c["listen_port"])
|
||||||
)
|
)
|
||||||
|
|
||||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||||
interface.OUT = True
|
interface.OUT = True
|
||||||
else:
|
else:
|
||||||
interface.OUT = False
|
interface.OUT = False
|
||||||
|
|
||||||
RNS.Transport.interfaces.append(interface)
|
RNS.Transport.interfaces.append(interface)
|
||||||
|
|
||||||
|
|
||||||
if c["type"] == "TCPClientInterface":
|
if c["type"] == "TCPClientInterface":
|
||||||
interface = TCPInterface.TCPClientInterface(
|
interface = TCPInterface.TCPClientInterface(
|
||||||
RNS.Transport,
|
RNS.Transport,
|
||||||
name,
|
name,
|
||||||
c["target_host"],
|
c["target_host"],
|
||||||
int(c["target_port"])
|
int(c["target_port"])
|
||||||
)
|
)
|
||||||
|
|
||||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||||
interface.OUT = True
|
interface.OUT = True
|
||||||
else:
|
else:
|
||||||
interface.OUT = False
|
interface.OUT = False
|
||||||
|
|
||||||
RNS.Transport.interfaces.append(interface)
|
RNS.Transport.interfaces.append(interface)
|
||||||
|
|
||||||
|
|
||||||
if c["type"] == "SerialInterface":
|
if c["type"] == "SerialInterface":
|
||||||
port = c["port"] if "port" in c else None
|
port = c["port"] if "port" in c else None
|
||||||
speed = int(c["speed"]) if "speed" in c else 9600
|
speed = int(c["speed"]) if "speed" in c else 9600
|
||||||
databits = int(c["databits"]) if "databits" in c else 8
|
databits = int(c["databits"]) if "databits" in c else 8
|
||||||
parity = c["parity"] if "parity" in c else "N"
|
parity = c["parity"] if "parity" in c else "N"
|
||||||
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
|
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
|
||||||
|
|
||||||
if port == None:
|
if port == None:
|
||||||
raise ValueError("No port specified for serial interface")
|
raise ValueError("No port specified for serial interface")
|
||||||
|
|
||||||
interface = SerialInterface.SerialInterface(
|
interface = SerialInterface.SerialInterface(
|
||||||
RNS.Transport,
|
RNS.Transport,
|
||||||
name,
|
name,
|
||||||
port,
|
port,
|
||||||
speed,
|
speed,
|
||||||
databits,
|
databits,
|
||||||
parity,
|
parity,
|
||||||
stopbits
|
stopbits
|
||||||
)
|
)
|
||||||
|
|
||||||
if "outgoing" in c and c["outgoing"].lower() == "true":
|
if "outgoing" in c and c["outgoing"].lower() == "true":
|
||||||
interface.OUT = True
|
interface.OUT = True
|
||||||
else:
|
else:
|
||||||
interface.OUT = False
|
interface.OUT = False
|
||||||
|
|
||||||
RNS.Transport.interfaces.append(interface)
|
RNS.Transport.interfaces.append(interface)
|
||||||
|
|
||||||
if c["type"] == "KISSInterface":
|
if c["type"] == "KISSInterface":
|
||||||
preamble = int(c["preamble"]) if "preamble" in c else None
|
preamble = int(c["preamble"]) if "preamble" in c else None
|
||||||
txtail = int(c["txtail"]) if "txtail" in c else None
|
txtail = int(c["txtail"]) if "txtail" in c else None
|
||||||
persistence = int(c["persistence"]) if "persistence" in c else None
|
persistence = int(c["persistence"]) if "persistence" in c else None
|
||||||
slottime = int(c["slottime"]) if "slottime" in c else None
|
slottime = int(c["slottime"]) if "slottime" in c else None
|
||||||
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
|
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
|
||||||
port = c["port"] if "port" in c else None
|
port = c["port"] if "port" in c else None
|
||||||
speed = int(c["speed"]) if "speed" in c else 9600
|
speed = int(c["speed"]) if "speed" in c else 9600
|
||||||
databits = int(c["databits"]) if "databits" in c else 8
|
databits = int(c["databits"]) if "databits" in c else 8
|
||||||
parity = c["parity"] if "parity" in c else "N"
|
parity = c["parity"] if "parity" in c else "N"
|
||||||
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
|
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
|
||||||
|
|
||||||
if port == None:
|
if port == None:
|
||||||
raise ValueError("No port specified for serial interface")
|
raise ValueError("No port specified for serial interface")
|
||||||
|
|
||||||
interface = KISSInterface.KISSInterface(
|
interface = KISSInterface.KISSInterface(
|
||||||
RNS.Transport,
|
RNS.Transport,
|
||||||
name,
|
name,
|
||||||
port,
|
port,
|
||||||
speed,
|
speed,
|
||||||
databits,
|
databits,
|
||||||
parity,
|
parity,
|
||||||
stopbits,
|
stopbits,
|
||||||
preamble,
|
preamble,
|
||||||
txtail,
|
txtail,
|
||||||
persistence,
|
persistence,
|
||||||
slottime,
|
slottime,
|
||||||
flow_control
|
flow_control
|
||||||
)
|
)
|
||||||
|
|
||||||
if "outgoing" in c and c["outgoing"].lower() == "true":
|
if "outgoing" in c and c["outgoing"].lower() == "true":
|
||||||
interface.OUT = True
|
interface.OUT = True
|
||||||
else:
|
else:
|
||||||
interface.OUT = False
|
interface.OUT = False
|
||||||
|
|
||||||
RNS.Transport.interfaces.append(interface)
|
RNS.Transport.interfaces.append(interface)
|
||||||
|
|
||||||
if c["type"] == "AX25KISSInterface":
|
if c["type"] == "AX25KISSInterface":
|
||||||
preamble = int(c["preamble"]) if "preamble" in c else None
|
preamble = int(c["preamble"]) if "preamble" in c else None
|
||||||
txtail = int(c["txtail"]) if "txtail" in c else None
|
txtail = int(c["txtail"]) if "txtail" in c else None
|
||||||
persistence = int(c["persistence"]) if "persistence" in c else None
|
persistence = int(c["persistence"]) if "persistence" in c else None
|
||||||
slottime = int(c["slottime"]) if "slottime" in c else None
|
slottime = int(c["slottime"]) if "slottime" in c else None
|
||||||
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
|
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
|
||||||
port = c["port"] if "port" in c else None
|
port = c["port"] if "port" in c else None
|
||||||
speed = int(c["speed"]) if "speed" in c else 9600
|
speed = int(c["speed"]) if "speed" in c else 9600
|
||||||
databits = int(c["databits"]) if "databits" in c else 8
|
databits = int(c["databits"]) if "databits" in c else 8
|
||||||
parity = c["parity"] if "parity" in c else "N"
|
parity = c["parity"] if "parity" in c else "N"
|
||||||
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
|
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
|
||||||
|
|
||||||
callsign = c["callsign"] if "callsign" in c else ""
|
callsign = c["callsign"] if "callsign" in c else ""
|
||||||
ssid = int(c["ssid"]) if "ssid" in c else -1
|
ssid = int(c["ssid"]) if "ssid" in c else -1
|
||||||
|
|
||||||
if port == None:
|
if port == None:
|
||||||
raise ValueError("No port specified for serial interface")
|
raise ValueError("No port specified for serial interface")
|
||||||
|
|
||||||
interface = AX25KISSInterface.AX25KISSInterface(
|
interface = AX25KISSInterface.AX25KISSInterface(
|
||||||
RNS.Transport,
|
RNS.Transport,
|
||||||
name,
|
name,
|
||||||
callsign,
|
callsign,
|
||||||
ssid,
|
ssid,
|
||||||
port,
|
port,
|
||||||
speed,
|
speed,
|
||||||
databits,
|
databits,
|
||||||
parity,
|
parity,
|
||||||
stopbits,
|
stopbits,
|
||||||
preamble,
|
preamble,
|
||||||
txtail,
|
txtail,
|
||||||
persistence,
|
persistence,
|
||||||
slottime,
|
slottime,
|
||||||
flow_control
|
flow_control
|
||||||
)
|
)
|
||||||
|
|
||||||
if "outgoing" in c and c["outgoing"].lower() == "true":
|
if "outgoing" in c and c["outgoing"].lower() == "true":
|
||||||
interface.OUT = True
|
interface.OUT = True
|
||||||
else:
|
else:
|
||||||
interface.OUT = False
|
interface.OUT = False
|
||||||
|
|
||||||
RNS.Transport.interfaces.append(interface)
|
RNS.Transport.interfaces.append(interface)
|
||||||
|
|
||||||
if c["type"] == "RNodeInterface":
|
if c["type"] == "RNodeInterface":
|
||||||
frequency = int(c["frequency"]) if "frequency" in c else None
|
frequency = int(c["frequency"]) if "frequency" in c else None
|
||||||
bandwidth = int(c["bandwidth"]) if "bandwidth" in c else None
|
bandwidth = int(c["bandwidth"]) if "bandwidth" in c else None
|
||||||
txpower = int(c["txpower"]) if "txpower" in c else None
|
txpower = int(c["txpower"]) if "txpower" in c else None
|
||||||
spreadingfactor = int(c["spreadingfactor"]) if "spreadingfactor" in c else None
|
spreadingfactor = int(c["spreadingfactor"]) if "spreadingfactor" in c else None
|
||||||
codingrate = int(c["codingrate"]) if "codingrate" in c else None
|
codingrate = int(c["codingrate"]) if "codingrate" in c else None
|
||||||
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
|
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
|
||||||
id_interval = int(c["id_interval"]) if "id_interval" in c else None
|
id_interval = int(c["id_interval"]) if "id_interval" in c else None
|
||||||
id_callsign = c["id_callsign"] if "id_callsign" in c else None
|
id_callsign = c["id_callsign"] if "id_callsign" in c else None
|
||||||
|
|
||||||
port = c["port"] if "port" in c else None
|
port = c["port"] if "port" in c else None
|
||||||
|
|
||||||
if port == None:
|
if port == None:
|
||||||
raise ValueError("No port specified for RNode interface")
|
raise ValueError("No port specified for RNode interface")
|
||||||
|
|
||||||
interface = RNodeInterface.RNodeInterface(
|
interface = RNodeInterface.RNodeInterface(
|
||||||
RNS.Transport,
|
RNS.Transport,
|
||||||
name,
|
name,
|
||||||
port,
|
port,
|
||||||
frequency = frequency,
|
frequency = frequency,
|
||||||
bandwidth = bandwidth,
|
bandwidth = bandwidth,
|
||||||
txpower = txpower,
|
txpower = txpower,
|
||||||
sf = spreadingfactor,
|
sf = spreadingfactor,
|
||||||
cr = codingrate,
|
cr = codingrate,
|
||||||
flow_control = flow_control,
|
flow_control = flow_control,
|
||||||
id_interval = id_interval,
|
id_interval = id_interval,
|
||||||
id_callsign = id_callsign
|
id_callsign = id_callsign
|
||||||
)
|
)
|
||||||
|
|
||||||
if "outgoing" in c and c["outgoing"].lower() == "true":
|
if "outgoing" in c and c["outgoing"].lower() == "true":
|
||||||
interface.OUT = True
|
interface.OUT = True
|
||||||
else:
|
else:
|
||||||
interface.OUT = False
|
interface.OUT = False
|
||||||
|
|
||||||
RNS.Transport.interfaces.append(interface)
|
RNS.Transport.interfaces.append(interface)
|
||||||
else:
|
else:
|
||||||
RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_NOTICE)
|
RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_NOTICE)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("The interface \""+name+"\" could not be created. Check your configuration file for errors!", RNS.LOG_ERROR)
|
RNS.log("The interface \""+name+"\" could not be created. Check your configuration file for errors!", RNS.LOG_ERROR)
|
||||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
RNS.panic()
|
RNS.panic()
|
||||||
else:
|
else:
|
||||||
RNS.log("The interface name \""+name+"\" was already used. Check your configuration file for errors!", RNS.LOG_ERROR)
|
RNS.log("The interface name \""+name+"\" was already used. Check your configuration file for errors!", RNS.LOG_ERROR)
|
||||||
RNS.panic()
|
RNS.panic()
|
||||||
|
|
||||||
|
|
||||||
def createDefaultConfig(self):
|
def createDefaultConfig(self):
|
||||||
self.config = ConfigObj(__default_rns_config__)
|
self.config = ConfigObj(__default_rns_config__)
|
||||||
self.config.filename = Reticulum.configpath
|
self.config.filename = Reticulum.configpath
|
||||||
|
|
||||||
if not os.path.isdir(Reticulum.configdir):
|
if not os.path.isdir(Reticulum.configdir):
|
||||||
os.makedirs(Reticulum.configdir)
|
os.makedirs(Reticulum.configdir)
|
||||||
self.config.write()
|
self.config.write()
|
||||||
self.applyConfig()
|
self.applyConfig()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def should_allow_unencrypted():
|
def should_allow_unencrypted():
|
||||||
return Reticulum.__allow_unencrypted
|
return Reticulum.__allow_unencrypted
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def should_use_implicit_proof():
|
def should_use_implicit_proof():
|
||||||
return Reticulum.__use_implicit_proof
|
return Reticulum.__use_implicit_proof
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def transport_enabled():
|
def transport_enabled():
|
||||||
return Reticulum.__transport_enabled
|
return Reticulum.__transport_enabled
|
||||||
|
|
||||||
# Default configuration file:
|
# Default configuration file:
|
||||||
__default_rns_config__ = '''# This is the default Reticulum config file.
|
__default_rns_config__ = '''# This is the default Reticulum config file.
|
||||||
@ -467,7 +467,7 @@ loglevel = 4
|
|||||||
# needs or turn it off completely.
|
# needs or turn it off completely.
|
||||||
|
|
||||||
[[Default UDP Interface]]
|
[[Default UDP Interface]]
|
||||||
type = UdpInterface
|
type = UdpInterface
|
||||||
interface_enabled = True
|
interface_enabled = True
|
||||||
outgoing = True
|
outgoing = True
|
||||||
listen_ip = 0.0.0.0
|
listen_ip = 0.0.0.0
|
||||||
@ -564,7 +564,7 @@ loglevel = 4
|
|||||||
# Allow transmit on interface.
|
# Allow transmit on interface.
|
||||||
outgoing = true
|
outgoing = true
|
||||||
|
|
||||||
# Serial port for the device
|
# Serial port for the device
|
||||||
port = /dev/ttyUSB1
|
port = /dev/ttyUSB1
|
||||||
|
|
||||||
# Set the serial baud-rate and other
|
# Set the serial baud-rate and other
|
||||||
@ -621,7 +621,7 @@ loglevel = 4
|
|||||||
# Allow transmit on interface.
|
# Allow transmit on interface.
|
||||||
outgoing = true
|
outgoing = true
|
||||||
|
|
||||||
# Serial port for the device
|
# Serial port for the device
|
||||||
port = /dev/ttyUSB2
|
port = /dev/ttyUSB2
|
||||||
|
|
||||||
# Set the serial baud-rate and other
|
# Set the serial baud-rate and other
|
||||||
|
2240
RNS/Transport.py
2240
RNS/Transport.py
File diff suppressed because it is too large
Load Diff
@ -25,10 +25,10 @@ LOG_VERBOSE = 5
|
|||||||
LOG_DEBUG = 6
|
LOG_DEBUG = 6
|
||||||
LOG_EXTREME = 7
|
LOG_EXTREME = 7
|
||||||
|
|
||||||
LOG_STDOUT = 0x91
|
LOG_STDOUT = 0x91
|
||||||
LOG_FILE = 0x92
|
LOG_FILE = 0x92
|
||||||
|
|
||||||
loglevel = LOG_NOTICE
|
loglevel = LOG_NOTICE
|
||||||
logfile = None
|
logfile = None
|
||||||
logdest = LOG_STDOUT
|
logdest = LOG_STDOUT
|
||||||
logtimefmt = "%Y-%m-%d %H:%M:%S"
|
logtimefmt = "%Y-%m-%d %H:%M:%S"
|
||||||
@ -36,54 +36,54 @@ logtimefmt = "%Y-%m-%d %H:%M:%S"
|
|||||||
random.seed(os.urandom(10))
|
random.seed(os.urandom(10))
|
||||||
|
|
||||||
def loglevelname(level):
|
def loglevelname(level):
|
||||||
if (level == LOG_CRITICAL):
|
if (level == LOG_CRITICAL):
|
||||||
return "Critical"
|
return "Critical"
|
||||||
if (level == LOG_ERROR):
|
if (level == LOG_ERROR):
|
||||||
return "Error"
|
return "Error"
|
||||||
if (level == LOG_WARNING):
|
if (level == LOG_WARNING):
|
||||||
return "Warning"
|
return "Warning"
|
||||||
if (level == LOG_NOTICE):
|
if (level == LOG_NOTICE):
|
||||||
return "Notice"
|
return "Notice"
|
||||||
if (level == LOG_INFO):
|
if (level == LOG_INFO):
|
||||||
return "Info"
|
return "Info"
|
||||||
if (level == LOG_VERBOSE):
|
if (level == LOG_VERBOSE):
|
||||||
return "Verbose"
|
return "Verbose"
|
||||||
if (level == LOG_DEBUG):
|
if (level == LOG_DEBUG):
|
||||||
return "Debug"
|
return "Debug"
|
||||||
if (level == LOG_EXTREME):
|
if (level == LOG_EXTREME):
|
||||||
return "Extra"
|
return "Extra"
|
||||||
|
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
|
|
||||||
def log(msg, level=3):
|
def log(msg, level=3):
|
||||||
# TODO: not thread safe
|
# TODO: not thread safe
|
||||||
if loglevel >= level:
|
if loglevel >= level:
|
||||||
timestamp = time.time()
|
timestamp = time.time()
|
||||||
logstring = "["+time.strftime(logtimefmt)+"] ["+loglevelname(level)+"] "+msg
|
logstring = "["+time.strftime(logtimefmt)+"] ["+loglevelname(level)+"] "+msg
|
||||||
|
|
||||||
if (logdest == LOG_STDOUT):
|
if (logdest == LOG_STDOUT):
|
||||||
print(logstring)
|
print(logstring)
|
||||||
|
|
||||||
if (logdest == LOG_FILE and logfile != None):
|
if (logdest == LOG_FILE and logfile != None):
|
||||||
file = open(logfile, "a")
|
file = open(logfile, "a")
|
||||||
file.write(logstring+"\n")
|
file.write(logstring+"\n")
|
||||||
file.close()
|
file.close()
|
||||||
|
|
||||||
def rand():
|
def rand():
|
||||||
result = random.random()
|
result = random.random()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def hexrep(data, delimit=True):
|
def hexrep(data, delimit=True):
|
||||||
delimiter = ":"
|
delimiter = ":"
|
||||||
if not delimit:
|
if not delimit:
|
||||||
delimiter = ""
|
delimiter = ""
|
||||||
hexrep = delimiter.join("{:02x}".format(c) for c in data)
|
hexrep = delimiter.join("{:02x}".format(c) for c in data)
|
||||||
return hexrep
|
return hexrep
|
||||||
|
|
||||||
def prettyhexrep(data):
|
def prettyhexrep(data):
|
||||||
delimiter = ""
|
delimiter = ""
|
||||||
hexrep = "<"+delimiter.join("{:02x}".format(c) for c in data)+">"
|
hexrep = "<"+delimiter.join("{:02x}".format(c) for c in data)+">"
|
||||||
return hexrep
|
return hexrep
|
||||||
|
|
||||||
def panic():
|
def panic():
|
||||||
os._exit(255)
|
os._exit(255)
|
Loading…
Reference in New Issue
Block a user