Compare commits

..

9 Commits

Author SHA1 Message Date
Lucas
5cf4869eda
Merge e47dcae54e into d2feb8b136 2024-05-04 23:26:08 +02:00
Mark Qvist
d2feb8b136 Improved path response logic 2024-05-04 21:57:03 +02:00
Mark Qvist
f595648a9b Updated tests 2024-05-04 20:27:27 +02:00
Mark Qvist
b06f5285c5 Fix LR proof delivery on unknown hop count paths 2024-05-04 20:27:04 +02:00
Mark Qvist
8330f70a27 Fixed link packet routing in topologies where transport packets leak to non-intended instances in the link chain 2024-05-04 19:52:02 +02:00
nothingbutlucas
e47dcae54e
refactor(rnodeconf): Replace string concatenation
Signed-off-by: nothingbutlucas <69118979+nothingbutlucas@users.noreply.github.com>
2024-05-04 14:29:56 -03:00
Mark Qvist
15e10b9435 Added expected hops property to link class 2024-05-04 19:15:57 +02:00
Mark Qvist
b91c852330 Updated path request timing 2024-05-04 16:19:04 +02:00
Mark Qvist
75acdf5902 Updated version 2024-05-03 23:49:39 +02:00
6 changed files with 174 additions and 87 deletions

View File

@ -168,16 +168,19 @@ class Link:
self.type = RNS.Destination.LINK
self.owner = owner
self.destination = destination
self.expected_hops = None
self.attached_interface = None
self.__remote_identity = None
self.__track_phy_stats = False
self._channel = None
if self.destination == None:
self.initiator = False
self.prv = X25519PrivateKey.generate()
self.sig_prv = self.owner.identity.sig_prv
else:
self.initiator = True
self.expected_hops = RNS.Transport.hops_to(self.destination.hash)
self.establishment_timeout = RNS.Reticulum.get_instance().get_first_hop_timeout(destination.hash)
self.establishment_timeout += Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, RNS.Transport.hops_to(destination.hash))
self.prv = X25519PrivateKey.generate()

View File

@ -64,8 +64,8 @@ class Transport:
LOCAL_REBROADCASTS_MAX = 2 # How many local rebroadcasts of an announce is allowed
PATH_REQUEST_TIMEOUT = 15 # Default timuout for client path requests in seconds
PATH_REQUEST_GRACE = 0.35 # Grace time before a path announcement is made, allows directly reachable peers to respond first
PATH_REQUEST_RW = 2 # Path request random window
PATH_REQUEST_GRACE = 0.4 # Grace time before a path announcement is made, allows directly reachable peers to respond first
PATH_REQUEST_RG = 0.6 # Extra grace time for roaming-mode interfaces to allow more suitable peers to respond first
PATH_REQUEST_MI = 20 # Minimum interval in seconds for automated path requests
STATE_UNKNOWN = 0x00
@ -746,7 +746,8 @@ class Transport:
packet.receipt = RNS.PacketReceipt(packet)
Transport.receipts.append(packet.receipt)
Transport.cache(packet)
# TODO: Enable when caching has been redesigned
# Transport.cache(packet)
# Check if we have a known path for the destination in the path table
if packet.packet_type != RNS.Packet.ANNOUNCE and packet.destination.type != RNS.Destination.PLAIN and packet.destination.type != RNS.Destination.GROUP and packet.destination_hash in Transport.destination_table:
@ -972,6 +973,13 @@ class Transport:
# TODO: Think long and hard about this.
# Is it even strictly necessary with the current
# transport rules?
# Filter packets intended for other transport instances
if packet.transport_id != None and packet.packet_type != RNS.Packet.ANNOUNCE:
if packet.transport_id != Transport.identity.hash:
RNS.log("Ignored packet "+RNS.prettyhexrep(packet.packet_hash)+" in transport for other transport instance", RNS.LOG_EXTREME)
return False
if packet.context == RNS.Packet.KEEPALIVE:
return True
if packet.context == RNS.Packet.RESOURCE_REQ:
@ -1136,10 +1144,31 @@ class Transport:
elif Transport.interface_to_shared_instance(interface):
packet.hops -= 1
if Transport.packet_filter(packet):
Transport.packet_hashlist.append(packet.packet_hash)
Transport.cache(packet)
# By default, remember packet hashes to avoid routing
# loops in the network, using the packet filter.
remember_packet_hash = True
# If this packet belongs to a link in our link table,
# we'll have to defer adding it to the filter list.
# In some cases, we might see a packet over a shared-
# medium interface, belonging to a link that transports
# or terminates with this instance, but before it would
# normally reach us. If the packet is appended to the
# filter list at this point, link transport will break.
if packet.destination_hash in Transport.link_table:
remember_packet_hash = False
# If this is a link request proof, don't add it until
# we are sure it's not actually somewhere else in the
# routing chain.
if packet.packet_type == RNS.Packet.PROOF and packet.context == RNS.Packet.LRPROOF:
remember_packet_hash = False
if remember_packet_hash:
Transport.packet_hashlist.append(packet.packet_hash)
# TODO: Enable when caching has been redesigned
# Transport.cache(packet)
# Check special conditions for local clients connected
# through a shared Reticulum instance
@ -1260,7 +1289,7 @@ class Transport:
link_entry = Transport.link_table[packet.destination_hash]
# If receiving and outbound interface is
# the same for this link, direction doesn't
# matter, and we simply send the packet on.
# matter, and we simply repeat the packet.
outbound_interface = None
if link_entry[2] == link_entry[4]:
# But check that taken hops matches one
@ -1281,6 +1310,11 @@ class Transport:
outbound_interface = link_entry[2]
if outbound_interface != None:
# Add this packet to the filter hashlist if we
# have determined that it's actually our turn
# to process it.
Transport.packet_hashlist.append(packet.packet_hash)
new_raw = packet.raw[0:1]
new_raw += struct.pack("!B", packet.hops)
new_raw += packet.raw[2:]
@ -1712,7 +1746,24 @@ class Transport:
# pending link
for link in Transport.pending_links:
if link.link_id == packet.destination_hash:
link.validate_proof(packet)
# We need to also allow an expected hops value of
# PATHFINDER_M, since in some cases, the number of hops
# to the destination will be unknown at link creation
# time. The real chance of this occuring is likely to be
# extremely small, and this allowance could probably
# be discarded without major issues, but it is kept
# for now to ensure backwards compatibility.
# TODO: Probably reset check back to
# if packet.hops == link.expected_hops:
# within one of the next releases
if packet.hops == link.expected_hops or link.expected_hops == RNS.Transport.PATHFINDER_M:
# Add this packet to the filter hashlist if we
# have determined that it's actually destined
# for this system, and then validate the proof
Transport.packet_hashlist.append(packet.packet_hash)
link.validate_proof(packet)
elif packet.context == RNS.Packet.RESOURCE_PRF:
for link in Transport.active_links:
@ -1729,7 +1780,7 @@ class Transport:
else:
proof_hash = None
# Check if this proof neds to be transported
# Check if this proof needs to be transported
if (RNS.Reticulum.transport_enabled() or from_local_client or proof_for_local_client) and packet.destination_hash in Transport.reverse_table:
reverse_entry = Transport.reverse_table.pop(packet.destination_hash)
if packet.receiving_interface == reverse_entry[1]:
@ -2124,6 +2175,7 @@ class Transport:
else:
return False
@staticmethod
def path_is_unresponsive(destination_hash):
if destination_hash in Transport.path_states:
if Transport.path_states[destination_hash] == Transport.STATE_UNRESPONSIVE:
@ -2288,8 +2340,18 @@ class Transport:
if is_from_local_client:
retransmit_timeout = now
else:
# TODO: Look at this timing
retransmit_timeout = now + Transport.PATH_REQUEST_GRACE # + (RNS.rand() * Transport.PATHFINDER_RW)
if Transport.is_local_client_interface(Transport.next_hop_interface(destination_hash)):
RNS.log("Path request destination "+RNS.prettyhexrep(destination_hash)+" is on a local client interface, rebroadcasting immediately", RNS.LOG_EXTREME)
retransmit_timeout = now
else:
retransmit_timeout = now + Transport.PATH_REQUEST_GRACE
# If we are answering on a roaming-mode interface, wait a
# little longer, to allow potential more well-connected
# peers to answer first.
if attached_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING:
retransmit_timeout += Transport.PATH_REQUEST_RG
# This handles an edge case where a peer sends a past
# request for a destination just after an announce for

View File

@ -56,12 +56,14 @@ fw_filename = None
fw_url = None
mapped_model = None
# TODO: Create and use constants for megas, kilos, etc (Avoid using magic numbers)
class KISS():
FEND = 0xC0
FESC = 0xDB
TFEND = 0xDC
TFESC = 0xDD
CMD_UNKNOWN = 0xFE
CMD_DATA = 0x00
CMD_FREQUENCY = 0x01
@ -102,11 +104,11 @@ class KISS():
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
@ -166,7 +168,7 @@ class ROM():
MODEL_E9 = 0xE9
MODEL_E3 = 0xE3
MODEL_E8 = 0xE8
PRODUCT_HMBRW = 0xF0
MODEL_FF = 0xFF
MODEL_FE = 0xFE
@ -280,7 +282,7 @@ try:
os.makedirs(ROM_DIR)
except Exception as e:
print("No access to directory "+str(CNF_DIR)+". This utility needs file system access to store firmware and data files. Cannot continue.")
print("".join(["No access to directory ", str(CNF_DIR), ". This utility needs file system access to store firmware and data files. Cannot continue."]))
print("The contained exception was:")
print(str(e))
exit(99)
@ -411,7 +413,7 @@ class RNode():
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("Radio reporting frequency is "+str(self.r_frequency/1000000.0)+" MHz")
RNS.log("".join(["Radio reporting frequency is ", str(self.r_frequency/1000000.0), " MHz"]))
self.updateBitrate()
elif (command == KISS.CMD_BANDWIDTH):
@ -427,7 +429,7 @@ class RNode():
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("Radio reporting bandwidth is "+str(self.r_bandwidth/1000.0)+" KHz")
RNS.log("".join(["Radio reporting bandwidth is ", str(self.r_bandwidth/1000.0), " KHz"]))
self.updateBitrate()
elif (command == KISS.CMD_BT_PIN):
@ -501,7 +503,7 @@ class RNode():
elif (command == KISS.CMD_TXPOWER):
self.r_txpower = byte
RNS.log("Radio reporting TX power is "+str(self.r_txpower)+" dBm")
RNS.log("".join(["Radio reporting TX power is ", str(self.r_txpower), " dBm"]))
elif (command == KISS.CMD_SF):
self.r_sf = byte
RNS.log("Radio reporting spreading factor is "+str(self.r_sf))
@ -549,17 +551,17 @@ class RNode():
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("".join([str(self), " hardware initialisation error (code ", RNS.hexrep(byte), ")"]))
elif (byte == KISS.ERROR_TXFAILED):
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")")
RNS.log("".join([str(self), " hardware TX error (code ", RNS.hexrep(byte), ")"]))
else:
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+")")
RNS.log("".join([str(self), " hardware error (code ", RNS.hexrep(byte), ")"]))
elif (command == KISS.CMD_DETECT):
if byte == KISS.DETECT_RESP:
self.detected = True
else:
self.detected = False
else:
time_since_last = int(time.time()*1000) - last_read_ms
if len(data_buffer) > 0 and time_since_last > self.timeout:
@ -816,7 +818,7 @@ class RNode():
from cryptography.hazmat.primitives.serialization import load_der_private_key
from cryptography.hazmat.primitives.asymmetric import padding
# Try loading local signing key for
# Try loading local signing key for
# validation of self-signed devices
if os.path.isdir(FWD_DIR) and os.path.isfile(FWD_DIR+"/signing.key"):
private_bytes = None
@ -852,7 +854,7 @@ class RNode():
RNS.log("Could not deserialize local signing key")
RNS.log(str(e))
# Try loading trusted signing key for
# Try loading trusted signing key for
# validation of devices
if os.path.isdir(TK_DIR):
for f in os.listdir(TK_DIR):
@ -930,7 +932,7 @@ class RNode():
print(" ")
print(" To initialise this device to a verifiable state, please run:")
print(" ")
print(" rnodeconf "+str(self.serial.name)+" --autoinstall")
print("".join([" rnodeconf ", str(self.serial.name), " --autoinstall"]))
print("")
@ -965,7 +967,7 @@ class RNode():
selected_version = None
selected_hash = None
firmware_version_url = "https://unsigned.io/firmware/latest/?v="+program_version+"&variant="
firmware_version_url = "".join(["https://unsigned.io/firmware/latest/?v=", program_version, "&variant="])
fallback_firmware_version_url = "https://github.com/markqvist/rnode_firmware/releases/latest/download/release.json"
def ensure_firmware_file(fw_filename):
global selected_version, selected_hash, upd_nocheck
@ -981,7 +983,7 @@ def ensure_firmware_file(fw_filename):
]
parts_missing = False
for rf in required_files:
if not os.path.isfile(EXT_DIR+"/"+rf):
if not os.path.isfile("".join([EXT_DIR, "/", rf])):
parts_missing = True
if parts_missing:
@ -994,7 +996,7 @@ def ensure_firmware_file(fw_filename):
release_info = vf.read().decode("utf-8").strip()
selected_version = release_info.split()[0]
selected_hash = release_info.split()[1]
RNS.log("Using existing firmware file: "+fw_filename+" for version "+selected_version)
RNS.log("".join(["Using existing firmware file: ", fw_filename, " for version ", selected_version]))
else:
RNS.log("No extracted firmware is available, cannot continue.")
RNS.log("Extract a firmware from an existing RNode first, using the --extract-firmware option.")
@ -1006,17 +1008,19 @@ def ensure_firmware_file(fw_filename):
try:
# if custom firmware url, download latest release
if selected_version == None and fw_url == None:
version_url = firmware_version_url+fw_filename
version_url = firmware_version_url + fw_filename
RNS.log("Retrieving latest version info from "+version_url)
urlretrieve(firmware_version_url+fw_filename, UPD_DIR+"/"+fw_filename+".version.latest")
urlretrieve(version_url, "".join([UPD_DIR, "/", fw_filename, ".version.latest"]))
else:
if fw_url != None:
if selected_version == None:
version_url = fw_url+"latest/download/release.json"
else:
version_url = fw_url+"download/"+selected_version+"/release.json"
version_url = "".join([fw_url, "download/", selected_version, "/release.json"])
else:
version_url = firmware_update_url+selected_version+"/release.json"
if selected_version is None:
selected_version = ""
version_url = "".join([firmware_update_url, selected_version, "/release.json"])
try:
RNS.log("Retrieving specified version info from "+version_url)
urlretrieve(version_url, UPD_DIR+"/version_release_info.json")
@ -1024,8 +1028,9 @@ def ensure_firmware_file(fw_filename):
with open(UPD_DIR+"/version_release_info.json", "rb") as rif:
rdat = json.loads(rif.read())
variant = rdat[fw_filename]
with open(UPD_DIR+"/"+fw_filename+".version.latest", "wb") as verf:
with open("".join([UPD_DIR, "/", fw_filename, ".version.latest"]), "wb") as verf:
inf_str = str(variant["version"])+" "+str(variant["hash"])
inf_str = "".join([str(variant["version"]), " ", str(variant["hash"])])
verf.write(inf_str.encode("utf-8"))
except Exception as e:
RNS.log("Failed to retrive version information for your board.")
@ -1055,7 +1060,7 @@ def ensure_firmware_file(fw_filename):
with open(UPD_DIR+"/fallback_release_info.json", "rb") as rif:
rdat = json.loads(rif.read())
variant = rdat[fw_filename]
with open(UPD_DIR+"/"+fw_filename+".version.latest", "wb") as verf:
with open("".join([UPD_DIR, "/", fw_filename, ".version.latest"]), "wb") as verf:
inf_str = str(variant["version"])+" "+str(variant["hash"])
verf.write(inf_str.encode("utf-8"))
@ -1064,7 +1069,7 @@ def ensure_firmware_file(fw_filename):
raise e
import shutil
file = open(UPD_DIR+"/"+fw_filename+".version.latest", "rb")
file = open("".join([UPD_DIR, "/", fw_filename, ".version.latest"]), "rb")
release_info = file.read().decode("utf-8").strip()
selected_version = release_info.split()[0]
if selected_version == "not":
@ -1074,7 +1079,7 @@ def ensure_firmware_file(fw_filename):
selected_hash = release_info.split()[1]
if not os.path.isdir(UPD_DIR+"/"+selected_version):
os.makedirs(UPD_DIR+"/"+selected_version)
shutil.copy(UPD_DIR+"/"+fw_filename+".version.latest", UPD_DIR+"/"+selected_version+"/"+fw_filename+".version")
shutil.copy("".join([UPD_DIR, "/", fw_filename, ".version.latest"]), "".join([UPD_DIR, "/", selected_version, "/", fw_filename, ".version"]))
RNS.log("The selected firmware for this board is version "+selected_version)
else:
@ -1084,32 +1089,32 @@ def ensure_firmware_file(fw_filename):
# if custom firmware url, use it
if fw_url != None:
update_target_url = fw_url+"download/"+selected_version+"/"+fw_filename
update_target_url = "".join([fw_url, "download/", selected_version, "/", fw_filename])
RNS.log("Retrieving firmware from custom url "+update_target_url)
else:
update_target_url = firmware_update_url+selected_version+"/"+fw_filename
update_target_url = "".join([firmware_update_url, selected_version, "/", fw_filename])
try:
if not os.path.isdir(UPD_DIR+"/"+selected_version):
os.makedirs(UPD_DIR+"/"+selected_version)
if not os.path.isfile(UPD_DIR+"/"+selected_version+"/"+fw_filename):
RNS.log("Firmware "+UPD_DIR+"/"+selected_version+"/"+fw_filename+" not found.")
RNS.log("Downloading missing firmware file: "+fw_filename+" for version "+selected_version)
urlretrieve(update_target_url, UPD_DIR+"/"+selected_version+"/"+fw_filename)
if not os.path.isfile("".join([UPD_DIR, "/", selected_version, "/", fw_filename])):
RNS.log("".join(["Firmware ", UPD_DIR, "/", selected_version, "/", fw_filename, " not found."]))
RNS.log("".join(["Downloading missing firmware file: ", fw_filename, " for version ", selected_version]))
urlretrieve(update_target_url, "".join([UPD_DIR, "/", selected_version, "/", fw_filename]))
RNS.log("Firmware file downloaded")
else:
RNS.log("Using existing firmware file: "+fw_filename+" for version "+selected_version)
RNS.log("".join(["Using existing firmware file: ", fw_filename, " for version ", selected_version]))
try:
if selected_hash == None:
try:
file = open(UPD_DIR+"/"+selected_version+"/"+fw_filename+".version", "rb")
file = open("".join([UPD_DIR, "/", selected_version, "/", fw_filename, ".version"]), "rb")
release_info = file.read().decode("utf-8").strip()
selected_hash = release_info.split()[1]
except Exception as e:
RNS.log("Could not read locally cached release information.")
RNS.log("Ensure "+UPD_DIR+"/"+selected_version+"/"+fw_filename+".version exists and has the correct format and hash.")
RNS.log("".join(["Ensure ", UPD_DIR, "/", selected_version, "/", fw_filename, ".version exists and has the correct format and hash."]))
RNS.log("You can clear the cache with the --clear-cache option and try again.")
if selected_hash == None:
@ -1117,7 +1122,8 @@ def ensure_firmware_file(fw_filename):
exit(97)
RNS.log("Verifying firmware integrity...")
fw_file = open(UPD_DIR+"/"+selected_version+"/"+fw_filename, "rb")
fw_file = open("".join([UPD_DIR, "/", selected_version, "/", fw_filename]), "rb")
# TODO: Remove unused variable
expected_hash = bytes.fromhex(selected_hash)
file_hash = hashlib.sha256(fw_file.read()).hexdigest()
if file_hash == selected_hash:
@ -1230,11 +1236,11 @@ def main():
parser.add_argument("-f", "--flash", action="store_true", help="Flash firmware and bootstrap EEPROM")
parser.add_argument("-r", "--rom", action="store_true", help="Bootstrap EEPROM without flashing firmware")
parser.add_argument("-k", "--key", action="store_true", help="Generate a new signing key and exit") #
parser.add_argument("-k", "--key", action="store_true", help="Generate a new signing key and exit") #
parser.add_argument("-S", "--sign", action="store_true", help="Display public part of signing key")
parser.add_argument("-H", "--firmware-hash", action="store", help="Display installed firmware hash")
parser.add_argument("--platform", action="store", metavar="platform", type=str, default=None, help="Platform specification for device bootstrap")
parser.add_argument("--product", action="store", metavar="product", type=str, default=None, help="Product specification for device bootstrap") #
parser.add_argument("--product", action="store", metavar="product", type=str, default=None, help="Product specification for device bootstrap") #
parser.add_argument("--model", action="store", metavar="model", type=str, default=None, help="Model code for device bootstrap")
parser.add_argument("--hwrev", action="store", metavar="revision", type=int, default=None, help="Hardware revision for device bootstrap")
@ -1263,7 +1269,7 @@ def main():
if args.fw_version != None:
selected_version = args.fw_version
try:
try:
check_float = float(selected_version)
except ValueError:
RNS.log("Selected version \""+selected_version+"\" does not appear to be a number.")
@ -1277,7 +1283,7 @@ def main():
if args.nocheck:
upd_nocheck = True
if args.public or args.key or args.flash or args.rom or args.autoinstall or args.trust_key:
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
@ -1296,7 +1302,7 @@ def main():
public_key = load_der_public_key(public_bytes, backend=default_backend())
key_hash = hashlib.sha256(public_bytes).hexdigest()
RNS.log("Trusting key: "+str(key_hash))
f = open(TK_DIR+"/"+str(key_hash)+".pubkey", "wb")
f = open("".join([TK_DIR, "/", str(key_hash), ".pubkey"]), "wb")
f.write(public_bytes)
f.close()
@ -1328,12 +1334,12 @@ def main():
ports = list_ports.comports()
portlist = []
for port in ports:
portlist.insert(0, port)
portlist.insert(0, port)
pi = 1
print("Detected serial ports:")
for port in portlist:
print(" ["+str(pi)+"] "+str(port.device)+" ("+str(port.product)+", "+str(port.serial_number)+")")
print("".join([" [", str(pi), "] ", str(port.device), " (", str(port.product), ", ", str(port.serial_number), ")"]))
pi += 1
print("\nEnter the number of the serial port your device is connected to:\n? ", end="")
@ -1355,7 +1361,7 @@ def main():
port_product = selected_port.product
port_serialno = selected_port.serial_number
print("\nOk, using device on "+str(port_path)+" ("+str(port_product)+", "+str(port_serialno)+")")
print("".join(["\nOk, using device on ", str(port_path), " (", str(port_product), ", ", str(port_serialno), ")"]))
else:
ports = list_ports.comports()
@ -1423,11 +1429,11 @@ def main():
hash_f.close()
extraction_parts = [
("bootloader", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0x1000 0x4650 \""+EXT_DIR+"/extracted_rnode_firmware.bootloader\""),
("partition table", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0x8000 0xC00 \""+EXT_DIR+"/extracted_rnode_firmware.partitions\""),
("app boot", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0xe000 0x2000 \""+EXT_DIR+"/extracted_rnode_firmware.boot_app0\""),
("application image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0x10000 0x200000 \""+EXT_DIR+"/extracted_rnode_firmware.bin\""),
("console image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud "+args.baud_flash+" --before default_reset --after hard_reset read_flash 0x210000 0x1F0000 \""+EXT_DIR+"/extracted_console_image.bin\""),
("bootloader", "".join(["python \"", CNF_DIR, "/recovery_esptool.py\" --chip esp32 --port ", port_path, " --baud ", args.baud_flash, " --before default_reset --after hard_reset read_flash 0x1000 0x4650 \"", EXT_DIR, "/extracted_rnode_firmware.bootloader\""])),
("partition table", "".join(["python \"", CNF_DIR, "/recovery_esptool.py\" --chip esp32 --port ", port_path, " --baud ", args.baud_flash, " --before default_reset --after hard_reset read_flash 0x8000 0xC00 \"", EXT_DIR, "/extracted_rnode_firmware.partitions\""])),
("app boot", "".join(["python \"", CNF_DIR, "/recovery_esptool.py\" --chip esp32 --port ", port_path, " --baud ", args.baud_flash, " --before default_reset --after hard_reset read_flash 0xe000 0x2000 \"", EXT_DIR, "/extracted_rnode_firmware.boot_app0\""])),
("application image", "".join(["python \"", CNF_DIR, "/recovery_esptool.py\" --chip esp32 --port ", port_path, " --baud ", args.baud_flash, " --before default_reset --after hard_reset read_flash 0x10000 0x200000 \"", EXT_DIR, "/extracted_rnode_firmware.bin\""])),
("console image", "".join(["python \"", CNF_DIR, "/recovery_esptool.py\" --chip esp32 --port ", port_path, " --baud ", args.baud_flash, " --before default_reset --after hard_reset read_flash 0x210000 0x1F0000 \"", EXT_DIR, "/extracted_console_image.bin\""])),
]
import subprocess, shlex
for part, command in extraction_parts:
@ -1460,12 +1466,12 @@ def main():
ports = list_ports.comports()
portlist = []
for port in ports:
portlist.insert(0, port)
portlist.insert(0, port)
pi = 1
print("Detected serial ports:")
for port in portlist:
print(" ["+str(pi)+"] "+str(port.device)+" ("+str(port.product)+", "+str(port.serial_number)+")")
print("".join([" [", str(pi), "] ", str(port.device), " (", str(port.product), ", ", str(port.serial_number), ")"]))
pi += 1
print("\nEnter the number of the serial port your device is connected to:\n? ", end="")
@ -1488,7 +1494,7 @@ def main():
port_serialno = selected_port.serial_number
clear()
print("\nOk, using device on "+str(port_path)+" ("+str(port_product)+", "+str(port_serialno)+")")
print("".join(["\nOk, using device on ", str(port_path), " (", str(port_product), ", ", str(port_serialno), ")"]))
else:
ports = list_ports.comports()
@ -1541,7 +1547,7 @@ def main():
print("correct firmware and provision it.")
else:
print("\nIt looks like this is a fresh device with no RNode firmware.")
print("")
print("What kind of device is this?\n")
print("[1] A specific kind of RNode")
@ -1981,7 +1987,7 @@ def main():
fw_filename = "rnode_firmware.hex"
elif selected_mcu == ROM.MCU_2560:
fw_filename = "rnode_firmware_m2560.hex"
elif selected_platform == ROM.PLATFORM_ESP32:
fw_filename = None
print("\nWhat kind of ESP32 board is this?\n")
@ -2094,7 +2100,7 @@ def main():
except Exception as e:
RNS.log("Could not load device signing key")
exit()
@ -2177,7 +2183,7 @@ def main():
if platform == "unzip":
flasher = "unzip"
if which(flasher) is not None:
return [flasher, "-o", UPD_DIR+"/"+selected_version+"/"+fw_filename, "-d", UPD_DIR+"/"+selected_version]
return [flasher, "-o", "".join([UPD_DIR, "/", selected_version, "/", fw_filename]), "-d", UPD_DIR+"/"+selected_version]
else:
RNS.log("")
RNS.log("You do not currently have the \""+flasher+"\" program installed on your system.")
@ -2194,9 +2200,9 @@ def main():
# avrdude -C/home/markqvist/.arduino15/packages/arduino/tools/avrdude/6.3.0-arduino17/etc/avrdude.conf -q -q -V -patmega2560 -cwiring -P/dev/ttyACM0 -b115200 -D -Uflash:w:/tmp/arduino-sketch-0E260F46C421A84A7CBAD48E859C8E64/RNode_Firmware.ino.hex:i
# avrdude -q -q -V -patmega2560 -cwiring -P/dev/ttyACM0 -b115200 -D -Uflash:w:/tmp/arduino-sketch-0E260F46C421A84A7CBAD48E859C8E64/RNode_Firmware.ino.hex:i
if fw_filename == "rnode_firmware.hex":
return [flasher, "-P", args.port, "-p", "m1284p", "-c", "arduino", "-b", "115200", "-U", "flash:w:"+UPD_DIR+"/"+selected_version+"/"+fw_filename+":i"]
return [flasher, "-P", args.port, "-p", "m1284p", "-c", "arduino", "-b", "115200", "-U", "".join(["flash:w:", UPD_DIR, "/", selected_version, "/", fw_filename, ":i"])]
elif fw_filename == "rnode_firmware_m2560.hex":
return [flasher, "-P", args.port, "-p", "atmega2560", "-c", "wiring", "-D", "-b", "115200", "-U", "flash:w:"+UPD_DIR+"/"+selected_version+"/"+fw_filename]
return [flasher, "-P", args.port, "-p", "atmega2560", "-c", "wiring", "-D", "-b", "115200", "-U", "".join(["flash:w:", UPD_DIR, "/", selected_version, "/", fw_filename])]
else:
RNS.log("")
RNS.log("You do not currently have the \""+flasher+"\" program installed on your system.")
@ -2224,6 +2230,7 @@ def main():
os.chmod(flasher, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP)
if which(flasher) is not None:
# TODO: Simplify this returns with a list and add or remove arguments
if fw_filename == "rnode_firmware_tbeam.zip":
if numeric_version >= 1.55:
return [
@ -2237,11 +2244,11 @@ def main():
"--flash_mode", "dio",
"--flash_freq", "80m",
"--flash_size", "4MB",
"0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.boot_app0",
"0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.bootloader",
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.bin",
"0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin",
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.partitions",
"0xe000", "".join([UPD_DIR, "/", selected_version, "/rnode_firmware_tbeam.boot_app0"]),
"0x1000", "".join([UPD_DIR, "/", selected_version, "/rnode_firmware_tbeam.bootloader"]),
"0x10000", "".join([UPD_DIR, "/", selected_version, "/rnode_firmware_tbeam.bin"]),
"0x210000","".join([UPD_DIR, "/", selected_version, "/console_image.bin"]),
"0x8000", "".join([UPD_DIR, "/", selected_version, "/rnode_firmware_tbeam.partitions"]),
]
else:
return [
@ -2672,7 +2679,7 @@ def main():
wants_fw_provision = False
if args.flash:
from subprocess import call
if fw_filename == None:
fw_filename = "rnode_firmware.hex"
@ -2703,9 +2710,9 @@ def main():
RNS.log("Error while flashing")
RNS.log(str(e))
exit(1)
else:
fw_src = UPD_DIR+"/"+selected_version+"/"
fw_src = "".join([UPD_DIR, "/", selected_version, "/"])
if os.path.isfile(fw_src+fw_filename):
try:
if fw_filename.endswith(".zip"):
@ -2862,7 +2869,7 @@ def main():
update_full_path = EXT_DIR+"/extracted_rnode_firmware.version"
else:
update_full_path = UPD_DIR+"/"+selected_version+"/"+fw_filename
if os.path.isfile(update_full_path):
if os.path.isfile(update_full_path):
try:
args.info = False
RNS.log("Updating RNode firmware for device on "+args.port)
@ -3017,13 +3024,13 @@ def main():
RNS.log("")
RNS.log("Device info:")
RNS.log("\tProduct : "+products[rnode.product]+" "+models[rnode.model][3]+" ("+bytes([rnode.product]).hex()+":"+bytes([rnode.model]).hex()+board_string+")")
RNS.log("".join(["\tProduct : ", products[rnode.product], " ", models[rnode.model][3], " (", bytes([rnode.product]).hex(), ":", bytes([rnode.model]).hex(), board_string, ")"]))
RNS.log("\tDevice signature : "+sigstring)
RNS.log("\tFirmware version : "+rnode.version)
RNS.log("\tHardware revision : "+str(int(rnode.hw_rev)))
RNS.log("\tSerial number : "+RNS.hexrep(rnode.serialno))
RNS.log("\tModem chip : "+str(models[rnode.model][5]))
RNS.log("\tFrequency range : "+str(rnode.min_freq/1e6)+" MHz - "+str(rnode.max_freq/1e6)+" MHz")
RNS.log("".join(["\tFrequency range : ", str(rnode.min_freq/1e6), " MHz - ", str(rnode.max_freq/1e6), " MHz"]))
RNS.log("\tMax TX power : "+str(rnode.max_output)+" dBm")
RNS.log("\tManufactured : "+timestring)
@ -3040,7 +3047,7 @@ def main():
RNS.log("\tDevice mode : TNC")
RNS.log("\t Frequency : "+str((rnode.conf_frequency/1000000.0))+" MHz")
RNS.log("\t Bandwidth : "+str(rnode.conf_bandwidth/1000.0)+" KHz")
RNS.log("\t TX power : "+str(rnode.conf_txpower)+" dBm ("+str(txp_mw)+" mW)")
RNS.log("".join(["\t TX power : ", str(rnode.conf_txpower), " dBm (", str(txp_mw), " mW)"]))
RNS.log("\t Spreading factor : "+str(rnode.conf_sf))
RNS.log("\t Coding rate : "+str(rnode.conf_cr))
RNS.log("\t On-air bitrate : "+str(rnode.bitrate_kbps)+" kbps")
@ -3070,7 +3077,7 @@ def main():
if args.autoinstall:
RNS.log("Clearing old EEPROM, this will take about 15 seconds...")
rnode.wipe_eeprom()
if rnode.platform == ROM.PLATFORM_ESP32:
RNS.log("Waiting for ESP32 reset...")
time.sleep(6)
@ -3241,7 +3248,7 @@ def main():
vf.close()
else:
partition_filename = fw_filename.replace(".zip", ".bin")
partition_hash = get_partition_hash(UPD_DIR+"/"+selected_version+"/"+partition_filename)
partition_hash = get_partition_hash("".join([UPD_DIR, "/", selected_version, "/", partition_filename]))
if partition_hash != None:
time.sleep(0.75)

View File

@ -1 +1 @@
__version__ = "0.7.3"
__version__ = "0.7.4"

View File

@ -99,6 +99,9 @@ class TestLink(unittest.TestCase):
id1 = RNS.Identity.from_bytes(bytes.fromhex(fixed_keys[0][0]))
self.assertEqual(id1.hash, bytes.fromhex(fixed_keys[0][1]))
RNS.Transport.request_path(bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
time.sleep(0.2)
dest = RNS.Destination(id1, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "link", "establish")
self.assertEqual(dest.hash, bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
@ -120,6 +123,9 @@ class TestLink(unittest.TestCase):
id1 = RNS.Identity.from_bytes(bytes.fromhex(fixed_keys[0][0]))
self.assertEqual(id1.hash, bytes.fromhex(fixed_keys[0][1]))
RNS.Transport.request_path(bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
time.sleep(0.2)
dest = RNS.Destination(id1, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "link", "establish")
self.assertEqual(dest.hash, bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
@ -400,6 +406,9 @@ class TestLink(unittest.TestCase):
id1 = RNS.Identity.from_bytes(bytes.fromhex(fixed_keys[0][0]))
self.assertEqual(id1.hash, bytes.fromhex(fixed_keys[0][1]))
RNS.Transport.request_path(bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
time.sleep(0.2)
dest = RNS.Destination(id1, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "link", "establish")
self.assertEqual(dest.hash, bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
@ -449,6 +458,9 @@ class TestLink(unittest.TestCase):
id1 = RNS.Identity.from_bytes(bytes.fromhex(fixed_keys[0][0]))
self.assertEqual(id1.hash, bytes.fromhex(fixed_keys[0][1]))
RNS.Transport.request_path(bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
time.sleep(0.2)
dest = RNS.Destination(id1, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "link", "establish")
self.assertEqual(dest.hash, bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
@ -502,6 +514,9 @@ class TestLink(unittest.TestCase):
id1 = RNS.Identity.from_bytes(bytes.fromhex(fixed_keys[0][0]))
self.assertEqual(id1.hash, bytes.fromhex(fixed_keys[0][1]))
RNS.Transport.request_path(bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
time.sleep(0.2)
dest = RNS.Destination(id1, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "link", "establish")
self.assertEqual(dest.hash, bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))

View File

@ -9,4 +9,4 @@
loglevel = 1
[interfaces]
# No interfaces, only local traffic
# No interfaces, only local traffic