diff --git a/RNS/Identity.py b/RNS/Identity.py index 03a0150..cda5b88 100644 --- a/RNS/Identity.py +++ b/RNS/Identity.py @@ -161,7 +161,7 @@ class Identity: announced_identity.load_public_key(public_key) 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) del announced_identity return True diff --git a/RNS/Link.py b/RNS/Link.py index d135b26..95fc569 100644 --- a/RNS/Link.py +++ b/RNS/Link.py @@ -19,11 +19,23 @@ class LinkCallbacks: self.link_established = None self.link_closed = None self.packet = None + self.resource = None self.resource_started = None self.resource_concluded = None class Link: + """ + This class. + + :param destination: A :ref:`RNS.Destination` instance which to establish a link to. + :param owner: Internal use by :ref:`RNS.Transport`, ignore this argument. + :param peer_pub_bytes: Internal use by :ref:`RNS.Transport`, ignore this argument. + """ CURVE = ec.SECP256R1() + """ + The curve used for Elliptic Curve DH key exchanges + """ + ECPUBSIZE = 91 BLOCKSIZE = 16 AES_HMAC_OVERHEAD = 58 @@ -33,9 +45,15 @@ class Link: # but calculated from something like # first-hop RTT latency and distance DEFAULT_TIMEOUT = 15.0 + """ + Default timeout for link establishment in seconds. + """ TIMEOUT_FACTOR = 3 STALE_GRACE = 2 KEEPALIVE = 180 + """ + Interval for sending keep-alive packets on established links in seconds. + """ PENDING = 0x00 HANDSHAKE = 0x01 @@ -243,18 +261,31 @@ class Link: return None def no_inbound_for(self): + """ + :returns: The time in seconds since last inbound packet on the link. + """ return time.time() - self.last_inbound def no_outbound_for(self): + """ + :returns: The time in seconds since last outbound packet on the link. + """ return time.time() - self.last_outbound def inactive_for(self): + """ + :returns: The time in seconds since activity on the link. + """ return min(self.no_inbound_for(), self.no_outbound_for()) def had_outbound(self): self.last_outbound = time.time() 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: teardown_packet = RNS.Packet(self, self.link_id, context=RNS.Packet.LINKCLOSE) teardown_packet.send() @@ -398,9 +429,8 @@ class Link: pass elif self.resource_strategy == Link.ACCEPT_APP: if self.callbacks.resource != None: - thread = threading.Thread(target=self.callbacks.resource, args=(packet)) - thread.setDaemon(True) - thread.start() + if self.callbacks.resource(resource): + RNS.Resource.accept(packet, self.callbacks.resource_concluded) elif self.resource_strategy == Link.ACCEPT_ALL: RNS.Resource.accept(packet, self.callbacks.resource_concluded) @@ -496,14 +526,41 @@ class Link: self.callbacks.link_closed = 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 - # 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): + """ + 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 - # Called when a resource transfer is concluded 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 def resource_concluded(self, resource): @@ -513,6 +570,12 @@ class Link: self.outgoing_resources.remove(resource) 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: raise TypeError("Unsupported resource strategy") else: @@ -542,7 +605,17 @@ class Link: else: 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()): RNS.log("The link "+str(self)+" was downgraded to an encryptionless link", RNS.LOG_NOTICE) self.__encryption_disabled = True diff --git a/RNS/Packet.py b/RNS/Packet.py index f4dd4ba..6283cfe 100755 --- a/RNS/Packet.py +++ b/RNS/Packet.py @@ -5,6 +5,20 @@ import time import RNS 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` 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` should be created when instantiating the packet. + :param type: Internal use by :ref:`RNS.Transport`. Defaults to ``RNS.Packet.DATA``, and should not be specified. + :param context: Internal use by :ref:`RNS.Transport`. Ignore. + :param transport_type: Internal use by :ref:`RNS.Transport`. Ignore. + :param transport_id: Internal use by :ref:`RNS.Transport`. Ignore. + :param attached_interface: Internal use by :ref:`RNS.Transport`. Ignore. + """ + # Packet types DATA = 0x00 # Data packets 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") self.packed = True - self.updateHash() + self.update_hash() def unpack(self): self.flags = self.raw[0] @@ -178,12 +192,14 @@ class Packet: self.data = self.raw[13:] 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): + """ + Sends the packet. + + :returns: A :ref:`RNS.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 self.destination.type == RNS.Destination.LINK: if self.destination.status == RNS.Link.CLOSED: @@ -208,6 +224,11 @@ class Packet: raise IOError("Packet was already sent") def resend(self): + """ + Re-sends the packet. + + :returns: A :ref:`RNS.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 RNS.Transport.outbound(self): return self.receipt @@ -239,16 +260,16 @@ class Packet: def validate_proof(self, proof): return self.receipt.validate_proof(proof) - def updateHash(self): - self.packet_hash = self.getHash() + def update_hash(self): + self.packet_hash = self.get_hash() - def getHash(self): - return RNS.Identity.full_hash(self.getHashablePart()) + def get_hash(self): + return RNS.Identity.full_hash(self.get_hashable_part()) 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]) if self.header_type == Packet.HEADER_2: hashable_part += self.raw[12:] @@ -259,7 +280,7 @@ class Packet: class ProofDestination: def __init__(self, packet): - self.hash = packet.getHash()[:10]; + self.hash = packet.get_hash()[:10]; self.type = RNS.Destination.SINGLE def encrypt(self, plaintext): @@ -267,6 +288,12 @@ class ProofDestination: class PacketReceipt: + """ + The PacketReceipt class is used to receive notifications about + :ref:`RNS.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` instance. + """ # Receipt status constants FAILED = 0x00 SENT = 0x01 @@ -279,7 +306,7 @@ class PacketReceipt: # Creates a new packet receipt from a sent packet def __init__(self, packet): - self.hash = packet.getHash() + self.hash = packet.get_hash() self.sent = True self.sent_at = time.time() self.timeout = Packet.TIMEOUT @@ -289,6 +316,12 @@ class PacketReceipt: self.callbacks = PacketReceiptCallbacks() self.concluded_at = None + def get_status(self): + """ + :returns: The status of the associated :ref:`RNS.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 def validate_proof_packet(self, proof_packet): if hasattr(proof_packet, "link") and proof_packet.link: @@ -374,6 +407,9 @@ class PacketReceipt: return False def rtt(self): + """ + :returns: The round-trip-time in seconds + """ return self.concluded_at - self.sent_at def is_timed_out(self): @@ -395,18 +431,30 @@ class PacketReceipt: #self.callbacks.timeout(self) - # Set the timeout in seconds def set_timeout(self, timeout): + """ + Sets a timeout in seconds + + :param timeout: The timeout in seconds. + """ self.timeout = float(timeout) - # Set a function that gets called when - # a successfull delivery has been proved 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 # Set a function that gets called if the # delivery times out 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 class PacketReceiptCallbacks: diff --git a/RNS/Transport.py b/RNS/Transport.py index 925af0d..8b82372 100755 --- a/RNS/Transport.py +++ b/RNS/Transport.py @@ -318,7 +318,7 @@ class Transport: Transport.jobs_locked = True # TODO: This updateHash call might be redundant - packet.updateHash() + packet.update_hash() sent = False # Check if we have a known path for the destination in the path table @@ -907,12 +907,22 @@ class Transport: @staticmethod 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` for more info. + """ if hasattr(handler, "received_announce") and callable(handler.received_announce): if hasattr(handler, "aspect_filter"): Transport.announce_handlers.append(handler) @staticmethod def deregister_announce_handler(handler): + """ + Deregisters an announce handler. + + :param handler: The announce handler to be deregistered. + """ while handler in Transport.announce_handlers: Transport.announce_handlers.remove(handler) @@ -940,7 +950,7 @@ class Transport: def cache(packet, force_cache=False): if RNS.Transport.should_cache(packet) or force_cache: try: - packet_hash = RNS.hexrep(packet.getHash(), delimit=False) + packet_hash = RNS.hexrep(packet.get_hash(), delimit=False) interface_reference = None if packet.receiving_interface != None: interface_reference = str(packet.receiving_interface) @@ -1009,6 +1019,10 @@ class Transport: @staticmethod 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: return True else: @@ -1016,6 +1030,13 @@ class Transport: @staticmethod 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_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) @@ -1147,7 +1168,7 @@ class Transport: hops = de[2] expires = de[3] random_blobs = de[4] - packet_hash = de[6].getHash() + packet_hash = de[6].get_hash() serialised_entry = [ destination_hash,