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