Added docstrings, refactored method names.

This commit is contained in:
Mark Qvist 2021-05-16 23:14:49 +02:00
parent 3f1e2bc682
commit dfb5af5dd1
4 changed files with 168 additions and 26 deletions

View File

@ -161,7 +161,7 @@ class Identity:
announced_identity.load_public_key(public_key) announced_identity.load_public_key(public_key)
if announced_identity.pub != None and announced_identity.validate(signature, signed_data): if announced_identity.pub != None and announced_identity.validate(signature, signed_data):
RNS.Identity.remember(packet.getHash(), destination_hash, public_key, app_data) RNS.Identity.remember(packet.get_hash(), destination_hash, public_key, app_data)
RNS.log("Stored valid announce from "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG) RNS.log("Stored valid announce from "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG)
del announced_identity del announced_identity
return True return True

View File

@ -19,11 +19,23 @@ class LinkCallbacks:
self.link_established = None self.link_established = None
self.link_closed = None self.link_closed = None
self.packet = None self.packet = None
self.resource = None
self.resource_started = None self.resource_started = None
self.resource_concluded = None self.resource_concluded = None
class Link: class Link:
"""
This class.
:param destination: A :ref:`RNS.Destination<api-destination>` instance which to establish a link to.
:param owner: Internal use by :ref:`RNS.Transport<api-Transport>`, ignore this argument.
:param peer_pub_bytes: Internal use by :ref:`RNS.Transport<api-Transport>`, ignore this argument.
"""
CURVE = ec.SECP256R1() CURVE = ec.SECP256R1()
"""
The curve used for Elliptic Curve DH key exchanges
"""
ECPUBSIZE = 91 ECPUBSIZE = 91
BLOCKSIZE = 16 BLOCKSIZE = 16
AES_HMAC_OVERHEAD = 58 AES_HMAC_OVERHEAD = 58
@ -33,9 +45,15 @@ class Link:
# but calculated from something like # but calculated from something like
# first-hop RTT latency and distance # first-hop RTT latency and distance
DEFAULT_TIMEOUT = 15.0 DEFAULT_TIMEOUT = 15.0
"""
Default timeout for link establishment in seconds.
"""
TIMEOUT_FACTOR = 3 TIMEOUT_FACTOR = 3
STALE_GRACE = 2 STALE_GRACE = 2
KEEPALIVE = 180 KEEPALIVE = 180
"""
Interval for sending keep-alive packets on established links in seconds.
"""
PENDING = 0x00 PENDING = 0x00
HANDSHAKE = 0x01 HANDSHAKE = 0x01
@ -243,18 +261,31 @@ class Link:
return None return None
def no_inbound_for(self): def no_inbound_for(self):
"""
:returns: The time in seconds since last inbound packet on the link.
"""
return time.time() - self.last_inbound return time.time() - self.last_inbound
def no_outbound_for(self): def no_outbound_for(self):
"""
:returns: The time in seconds since last outbound packet on the link.
"""
return time.time() - self.last_outbound return time.time() - self.last_outbound
def inactive_for(self): def inactive_for(self):
"""
:returns: The time in seconds since activity on the link.
"""
return min(self.no_inbound_for(), self.no_outbound_for()) return min(self.no_inbound_for(), self.no_outbound_for())
def had_outbound(self): def had_outbound(self):
self.last_outbound = time.time() self.last_outbound = time.time()
def teardown(self): def teardown(self):
"""
Closes the link and purges encryption keys. New keys will
be used if a new link to the same destination is established.
"""
if self.status != Link.PENDING and self.status != Link.CLOSED: if self.status != Link.PENDING and self.status != Link.CLOSED:
teardown_packet = RNS.Packet(self, self.link_id, context=RNS.Packet.LINKCLOSE) teardown_packet = RNS.Packet(self, self.link_id, context=RNS.Packet.LINKCLOSE)
teardown_packet.send() teardown_packet.send()
@ -398,9 +429,8 @@ class Link:
pass pass
elif self.resource_strategy == Link.ACCEPT_APP: elif self.resource_strategy == Link.ACCEPT_APP:
if self.callbacks.resource != None: if self.callbacks.resource != None:
thread = threading.Thread(target=self.callbacks.resource, args=(packet)) if self.callbacks.resource(resource):
thread.setDaemon(True) RNS.Resource.accept(packet, self.callbacks.resource_concluded)
thread.start()
elif self.resource_strategy == Link.ACCEPT_ALL: elif self.resource_strategy == Link.ACCEPT_ALL:
RNS.Resource.accept(packet, self.callbacks.resource_concluded) RNS.Resource.accept(packet, self.callbacks.resource_concluded)
@ -496,14 +526,41 @@ class Link:
self.callbacks.link_closed = callback self.callbacks.link_closed = callback
def packet_callback(self, callback): def packet_callback(self, callback):
"""
Registers a function to be called when a packet has been
received over this link.
:param callback: A function or method with the signature *callback(message, packet)* to be called.
"""
self.callbacks.packet = callback self.callbacks.packet = callback
# Called when an incoming resource transfer is started def resource_callback(self, callback):
"""
Registers a function to be called when a resource has been
advertised over this link. If the function returns *True*
the resource will be accepted. If it returns *False* it will
be ignored.
:param callback: A function or method with the signature *callback(resource)* to be called.
"""
self.callbacks.resource = callback
def resource_started_callback(self, callback): def resource_started_callback(self, callback):
"""
Registers a function to be called when a resource has begun
transferring over this link.
:param callback: A function or method with the signature *callback(resource)* to be called.
"""
self.callbacks.resource_started = callback self.callbacks.resource_started = callback
# Called when a resource transfer is concluded
def resource_concluded_callback(self, callback): def resource_concluded_callback(self, callback):
"""
Registers a function to be called when a resource has concluded
transferring over this link.
:param callback: A function or method with the signature *callback(resource)* to be called.
"""
self.callbacks.resource_concluded = callback self.callbacks.resource_concluded = callback
def resource_concluded(self, resource): def resource_concluded(self, resource):
@ -513,6 +570,12 @@ class Link:
self.outgoing_resources.remove(resource) self.outgoing_resources.remove(resource)
def set_resource_strategy(self, resource_strategy): def set_resource_strategy(self, resource_strategy):
"""
Sets the resource strategy for the link.
:param resource_strategy: One of ``RNS.Link.ACCEPT_NONE``, ``RNS.Link.ACCEPT_ALL`` or ``RNS.Link.ACCEPT_APP``. If ``RNS.Link.ACCEPT_APP`` is set, the `resource_callback` will be called to determine whether the resource should be accepted or not.
:raises: *TypeError* if the resource strategy is unsupported.
"""
if not resource_strategy in Link.resource_strategies: if not resource_strategy in Link.resource_strategies:
raise TypeError("Unsupported resource strategy") raise TypeError("Unsupported resource strategy")
else: else:
@ -542,7 +605,17 @@ class Link:
else: else:
return True return True
def disableEncryption(self): def disable_encryption(self):
"""
HAZARDOUS. This will downgrade the link to encryptionless. All
information over the link will be sent in plaintext. Never use
this in production applications. Should only be used for debugging
purposes, and will disappear in a future version.
If encryptionless links are not explicitly allowed in the users
configuration file, Reticulum will terminate itself along with the
client application and throw an error message to the user.
"""
if (RNS.Reticulum.should_allow_unencrypted()): if (RNS.Reticulum.should_allow_unencrypted()):
RNS.log("The link "+str(self)+" was downgraded to an encryptionless link", RNS.LOG_NOTICE) RNS.log("The link "+str(self)+" was downgraded to an encryptionless link", RNS.LOG_NOTICE)
self.__encryption_disabled = True self.__encryption_disabled = True

View File

@ -5,6 +5,20 @@ import time
import RNS import RNS
class Packet: class Packet:
"""
The Packet class is used to create packet instances that can be
sent over a Reticulum network.
:param destination: A :ref:`RNS.Destination<api-destination>` instance to which the packet will be sent.
:param data: The data payload to be included in the packet as *bytes*.
:param create_receipt: Specifies whether a :ref:`RNS.PacketReceipt<api-packetreceipt>` should be created when instantiating the packet.
:param type: Internal use by :ref:`RNS.Transport<api-transport>`. Defaults to ``RNS.Packet.DATA``, and should not be specified.
:param context: Internal use by :ref:`RNS.Transport<api-transport>`. Ignore.
:param transport_type: Internal use by :ref:`RNS.Transport<api-transport>`. Ignore.
:param transport_id: Internal use by :ref:`RNS.Transport<api-transport>`. Ignore.
:param attached_interface: Internal use by :ref:`RNS.Transport<api-transport>`. Ignore.
"""
# Packet types # Packet types
DATA = 0x00 # Data packets DATA = 0x00 # Data packets
ANNOUNCE = 0x01 # Announces ANNOUNCE = 0x01 # Announces
@ -155,7 +169,7 @@ class Packet:
raise IOError("Packet size of "+str(len(self.raw))+" exceeds MTU of "+str(self.MTU)+" bytes") raise IOError("Packet size of "+str(len(self.raw))+" exceeds MTU of "+str(self.MTU)+" bytes")
self.packed = True self.packed = True
self.updateHash() self.update_hash()
def unpack(self): def unpack(self):
self.flags = self.raw[0] self.flags = self.raw[0]
@ -178,12 +192,14 @@ class Packet:
self.data = self.raw[13:] self.data = self.raw[13:]
self.packed = False self.packed = False
self.updateHash() self.update_hash()
# 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): def send(self):
"""
Sends the packet.
:returns: A :ref:`RNS.PacketReceipt<api-packetreceipt>` instance if *create_receipt* was set to *True* when the packet was instantiated, if not returns *None*. If the packet could not be sent *False* is returned.
"""
if not self.sent: if not self.sent:
if self.destination.type == RNS.Destination.LINK: if self.destination.type == RNS.Destination.LINK:
if self.destination.status == RNS.Link.CLOSED: if self.destination.status == RNS.Link.CLOSED:
@ -208,6 +224,11 @@ class Packet:
raise IOError("Packet was already sent") raise IOError("Packet was already sent")
def resend(self): def resend(self):
"""
Re-sends the packet.
:returns: A :ref:`RNS.PacketReceipt<api-packetreceipt>` instance if *create_receipt* was set to *True* when the packet was instantiated, if not returns *None*. If the packet could not be sent *False* is returned.
"""
if self.sent: if self.sent:
if RNS.Transport.outbound(self): if RNS.Transport.outbound(self):
return self.receipt return self.receipt
@ -239,16 +260,16 @@ class Packet:
def validate_proof(self, proof): def validate_proof(self, proof):
return self.receipt.validate_proof(proof) return self.receipt.validate_proof(proof)
def updateHash(self): def update_hash(self):
self.packet_hash = self.getHash() self.packet_hash = self.get_hash()
def getHash(self): def get_hash(self):
return RNS.Identity.full_hash(self.getHashablePart()) return RNS.Identity.full_hash(self.get_hashable_part())
def getTruncatedHash(self): def getTruncatedHash(self):
return RNS.Identity.truncated_hash(self.getHashablePart()) return RNS.Identity.truncated_hash(self.get_hashable_part())
def getHashablePart(self): def get_hashable_part(self):
hashable_part = bytes([self.raw[0] & 0b00001111]) hashable_part = bytes([self.raw[0] & 0b00001111])
if self.header_type == Packet.HEADER_2: if self.header_type == Packet.HEADER_2:
hashable_part += self.raw[12:] hashable_part += self.raw[12:]
@ -259,7 +280,7 @@ class Packet:
class ProofDestination: class ProofDestination:
def __init__(self, packet): def __init__(self, packet):
self.hash = packet.getHash()[:10]; self.hash = packet.get_hash()[:10];
self.type = RNS.Destination.SINGLE self.type = RNS.Destination.SINGLE
def encrypt(self, plaintext): def encrypt(self, plaintext):
@ -267,6 +288,12 @@ class ProofDestination:
class PacketReceipt: class PacketReceipt:
"""
The PacketReceipt class is used to receive notifications about
:ref:`RNS.Packet<api-packet>` instances sent over the network. Instances
of this class should never be created manually, but always returned
from a the *send()* method of a :ref:`RNS.Packet<api-packet>` instance.
"""
# Receipt status constants # Receipt status constants
FAILED = 0x00 FAILED = 0x00
SENT = 0x01 SENT = 0x01
@ -279,7 +306,7 @@ class PacketReceipt:
# Creates a new packet receipt from a sent packet # Creates a new packet receipt from a sent packet
def __init__(self, packet): def __init__(self, packet):
self.hash = packet.getHash() self.hash = packet.get_hash()
self.sent = True self.sent = True
self.sent_at = time.time() self.sent_at = time.time()
self.timeout = Packet.TIMEOUT self.timeout = Packet.TIMEOUT
@ -289,6 +316,12 @@ class PacketReceipt:
self.callbacks = PacketReceiptCallbacks() self.callbacks = PacketReceiptCallbacks()
self.concluded_at = None self.concluded_at = None
def get_status(self):
"""
:returns: The status of the associated :ref:`RNS.Packet<api-packet>` instance. Can be one of ``RNS.PacketReceipt.SENT``, ``RNS.PacketReceipt.DELIVERED``, ``RNS.PacketReceipt.FAILED`` or ``RNS.PacketReceipt.CULLED``.
"""
return self.status
# Validate a proof packet # Validate a proof packet
def validate_proof_packet(self, proof_packet): def validate_proof_packet(self, proof_packet):
if hasattr(proof_packet, "link") and proof_packet.link: if hasattr(proof_packet, "link") and proof_packet.link:
@ -374,6 +407,9 @@ class PacketReceipt:
return False return False
def rtt(self): def rtt(self):
"""
:returns: The round-trip-time in seconds
"""
return self.concluded_at - self.sent_at return self.concluded_at - self.sent_at
def is_timed_out(self): def is_timed_out(self):
@ -395,18 +431,30 @@ class PacketReceipt:
#self.callbacks.timeout(self) #self.callbacks.timeout(self)
# Set the timeout in seconds
def set_timeout(self, timeout): def set_timeout(self, timeout):
"""
Sets a timeout in seconds
:param timeout: The timeout in seconds.
"""
self.timeout = float(timeout) self.timeout = float(timeout)
# Set a function that gets called when
# a successfull delivery has been proved
def delivery_callback(self, callback): def delivery_callback(self, callback):
"""
Sets a function that gets called if a successfull delivery has been proven.
:param callback: A *callable* with the signature *callback(packet_receipt)*
"""
self.callbacks.delivery = callback self.callbacks.delivery = callback
# Set a function that gets called if the # Set a function that gets called if the
# delivery times out # delivery times out
def timeout_callback(self, callback): def timeout_callback(self, callback):
"""
Sets a function that gets called if the delivery times out.
:param callback: A *callable* with the signature *callback(packet_receipt)*
"""
self.callbacks.timeout = callback self.callbacks.timeout = callback
class PacketReceiptCallbacks: class PacketReceiptCallbacks:

View File

@ -318,7 +318,7 @@ class Transport:
Transport.jobs_locked = True Transport.jobs_locked = True
# TODO: This updateHash call might be redundant # TODO: This updateHash call might be redundant
packet.updateHash() packet.update_hash()
sent = False sent = False
# Check if we have a known path for the destination in the path table # Check if we have a known path for the destination in the path table
@ -907,12 +907,22 @@ class Transport:
@staticmethod @staticmethod
def register_announce_handler(handler): def register_announce_handler(handler):
"""
Registers an announce handler.
:param handler: Must be an object with an *aspect_filter* attribute and a *received_announce(destination_hash, announced_identity, app_data)* callable. See the :ref:`Announce Example<example-announce>` for more info.
"""
if hasattr(handler, "received_announce") and callable(handler.received_announce): if hasattr(handler, "received_announce") and callable(handler.received_announce):
if hasattr(handler, "aspect_filter"): if hasattr(handler, "aspect_filter"):
Transport.announce_handlers.append(handler) Transport.announce_handlers.append(handler)
@staticmethod @staticmethod
def deregister_announce_handler(handler): def deregister_announce_handler(handler):
"""
Deregisters an announce handler.
:param handler: The announce handler to be deregistered.
"""
while handler in Transport.announce_handlers: while handler in Transport.announce_handlers:
Transport.announce_handlers.remove(handler) Transport.announce_handlers.remove(handler)
@ -940,7 +950,7 @@ class Transport:
def cache(packet, force_cache=False): def cache(packet, force_cache=False):
if RNS.Transport.should_cache(packet) or force_cache: if RNS.Transport.should_cache(packet) or force_cache:
try: try:
packet_hash = RNS.hexrep(packet.getHash(), delimit=False) packet_hash = RNS.hexrep(packet.get_hash(), delimit=False)
interface_reference = None interface_reference = None
if packet.receiving_interface != None: if packet.receiving_interface != None:
interface_reference = str(packet.receiving_interface) interface_reference = str(packet.receiving_interface)
@ -1009,6 +1019,10 @@ class Transport:
@staticmethod @staticmethod
def has_path(destination_hash): def has_path(destination_hash):
"""
:param destination_hash: A destination hash as *bytes*.
:returns: *True* if a path to the destination is known, otherwise *False*.
"""
if destination_hash in Transport.destination_table: if destination_hash in Transport.destination_table:
return True return True
else: else:
@ -1016,6 +1030,13 @@ class Transport:
@staticmethod @staticmethod
def request_path(destination_hash): def request_path(destination_hash):
"""
Requests a path to the destination from the network. If
another reachable peer on the network knows a path, it
will announce it.
:param destination_hash: A destination hash as *bytes*.
"""
path_request_data = destination_hash + RNS.Identity.get_random_hash() path_request_data = destination_hash + RNS.Identity.get_random_hash()
path_request_dst = RNS.Destination(None, RNS.Destination.OUT, RNS.Destination.PLAIN, Transport.APP_NAME, "path", "request") path_request_dst = RNS.Destination(None, RNS.Destination.OUT, RNS.Destination.PLAIN, Transport.APP_NAME, "path", "request")
packet = RNS.Packet(path_request_dst, path_request_data, packet_type = RNS.Packet.DATA, transport_type = RNS.Transport.BROADCAST, header_type = RNS.Packet.HEADER_1) packet = RNS.Packet(path_request_dst, path_request_data, packet_type = RNS.Packet.DATA, transport_type = RNS.Transport.BROADCAST, header_type = RNS.Packet.HEADER_1)
@ -1147,7 +1168,7 @@ class Transport:
hops = de[2] hops = de[2]
expires = de[3] expires = de[3]
random_blobs = de[4] random_blobs = de[4]
packet_hash = de[6].getHash() packet_hash = de[6].get_hash()
serialised_entry = [ serialised_entry = [
destination_hash, destination_hash,