2025-04-15 20:19:33 +02:00
# Reticulum License
2022-04-01 17:18:18 +02:00
#
2025-04-15 20:19:33 +02:00
# Copyright (c) 2016-2025 Mark Qvist
2022-04-01 17:18:18 +02:00
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
2025-04-15 20:19:33 +02:00
# - The Software shall not be used in any kind of system which includes amongst
# its functions the ability to purposefully do harm to human beings.
#
# - The Software shall not be used, directly or indirectly, in the creation of
# an artificial intelligence, machine learning or language model training
# dataset, including but not limited to any use that contributes to the
# training or development of such a model or algorithm.
#
# - The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
2022-04-01 17:18:18 +02:00
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
2020-05-13 13:08:48 +02:00
import threading
2016-06-03 19:02:02 +02:00
import struct
2020-04-28 20:50:57 +02:00
import math
2018-03-20 12:32:41 +01:00
import time
2018-04-04 14:14:22 +02:00
import RNS
2016-06-03 19:02:02 +02:00
class Packet :
2021-05-16 23:14:49 +02:00
"""
2021-05-20 15:31:38 +02:00
The Packet class is used to create packet instances that can be sent
2022-09-30 11:26:51 +02:00
over a Reticulum network . Packets will automatically be encrypted if
2023-10-01 11:46:30 +02:00
they are addressed to a ` ` RNS . Destination . SINGLE ` ` destination ,
2021-05-20 15:31:38 +02:00
` ` RNS . Destination . GROUP ` ` destination or a : ref : ` RNS . Link < api - link > ` .
For ` ` RNS . Destination . GROUP ` ` destinations , Reticulum will use the
2022-09-30 11:15:34 +02:00
pre - shared key configured for the destination . All packets to group
2025-05-10 15:42:22 +02:00
destinations are encrypted with the same AES - 256 key .
2021-05-20 15:31:38 +02:00
2022-09-30 11:15:34 +02:00
For ` ` RNS . Destination . SINGLE ` ` destinations , Reticulum will use a newly
2025-05-10 15:42:22 +02:00
derived ephemeral AES - 256 key for every packet .
2022-09-30 11:15:34 +02:00
For : ref : ` RNS . Link < api - link > ` destinations , Reticulum will use per - link
ephemeral keys , and offers * * Forward Secrecy * * .
2021-05-16 23:14:49 +02:00
: 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 .
"""
2020-08-13 12:15:56 +02:00
# 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
2023-01-27 14:43:44 +02:00
header_types = [ HEADER_1 , HEADER_2 ]
2020-08-13 12:15:56 +02:00
2021-08-20 23:29:06 +02:00
# Packet context types
2020-08-13 12:15:56 +02:00
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
2023-02-25 18:23:25 -06:00
CHANNEL = 0x0E # Packet contains link channel data
2021-08-19 14:10:37 +02:00
KEEPALIVE = 0xFA # Packet is a keepalive packet
LINKIDENTIFY = 0xFB # Packet is a link peer identification proof
2020-08-13 12:15:56 +02:00
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
2024-09-04 17:37:18 +02:00
# Context flag values
FLAG_SET = 0x01
FLAG_UNSET = 0x00
2020-08-13 12:15:56 +02:00
# This is used to calculate allowable
# payload sizes
2021-05-20 15:31:38 +02:00
HEADER_MAXSIZE = RNS . Reticulum . HEADER_MAXSIZE
2020-08-13 12:15:56 +02:00
MDU = RNS . Reticulum . MDU
2021-05-20 15:31:38 +02:00
# With an MTU of 500, the maximum of data we can
# send in a single encrypted packet is given by
# the below calculation; 383 bytes.
2024-11-22 15:19:12 +01:00
ENCRYPTED_MDU = math . floor ( ( RNS . Reticulum . MDU - RNS . Identity . TOKEN_OVERHEAD - RNS . Identity . KEYSIZE / / 16 ) / RNS . Identity . AES128_BLOCKSIZE ) * RNS . Identity . AES128_BLOCKSIZE - 1
2021-05-20 15:31:38 +02:00
"""
The maximum size of the payload data in a single encrypted packet
"""
2021-08-28 20:01:01 +02:00
PLAIN_MDU = MDU
2021-05-20 15:31:38 +02:00
"""
2022-04-27 13:21:53 +02:00
The maximum size of the payload data in a single unencrypted packet
2021-05-20 15:31:38 +02:00
"""
2020-08-13 12:15:56 +02:00
2021-12-05 11:45:13 +01:00
TIMEOUT_PER_HOP = RNS . Reticulum . DEFAULT_PER_HOP_TIMEOUT
2020-08-13 12:15:56 +02:00
2025-04-03 17:36:37 +02:00
__slots__ = " hops " , " header " , " header_type " , " packet_type " , " transport_type " , " context " , " context_flag " , " destination "
__slots__ + = " transport_id " , " data " , " flags " , " raw " , " packed " , " sent " , " create_receipt " , " receipt " , " fromPacked " , " MTU "
__slots__ + = " sent_at " , " packet_hash " , " ratchet_id " , " attached_interface " , " receiving_interface " , " rssi " , " snr " , " q "
2026-08-24 22:12:04 +02:00
__slots__ + = " ciphertext " , " plaintext " , " destination_hash " , " destination_type " , " link " , " map_hash " , " is_outbound_pr "
__slots__ + = " traffic_class " , " announce_signature_validated "
2025-04-03 17:36:37 +02:00
2024-09-04 17:37:18 +02:00
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 , context_flag = FLAG_UNSET ) :
2020-08-13 12:15:56 +02:00
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
2024-09-04 17:37:18 +02:00
self . context_flag = context_flag
2020-08-13 12:15:56 +02:00
self . hops = 0 ;
self . destination = destination
self . transport_id = transport_id
self . data = data
2021-05-16 16:42:07 +02:00
self . flags = self . get_packed_flags ( )
2020-08-13 12:15:56 +02:00
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
2025-01-11 13:22:18 +01:00
if destination and destination . type == RNS . Destination . LINK :
self . MTU = destination . mtu
else :
self . MTU = RNS . Reticulum . MTU
2020-08-13 12:15:56 +02:00
self . sent_at = None
self . packet_hash = None
2024-09-08 14:55:07 +02:00
self . ratchet_id = None
2020-08-13 12:15:56 +02:00
2026-08-18 13:17:34 +02:00
self . traffic_class = None
2026-08-24 22:12:04 +02:00
self . announce_signature_validated = None
2020-08-13 12:15:56 +02:00
self . attached_interface = attached_interface
self . receiving_interface = None
2026-05-09 01:14:40 +02:00
self . is_outbound_pr = False
2021-10-12 16:34:17 +02:00
self . rssi = None
self . snr = None
2023-10-24 01:41:12 +02:00
self . q = None
2020-08-13 12:15:56 +02:00
2021-05-16 16:42:07 +02:00
def get_packed_flags ( self ) :
2020-08-13 12:15:56 +02:00
if self . context == Packet . LRPROOF :
2024-09-04 17:37:18 +02:00
packed_flags = ( self . header_type << 6 ) | ( self . context_flag << 5 ) | ( self . transport_type << 4 ) | ( RNS . Destination . LINK << 2 ) | self . packet_type
2020-08-13 12:15:56 +02:00
else :
2024-09-04 17:37:18 +02:00
packed_flags = ( self . header_type << 6 ) | ( self . context_flag << 5 ) | ( self . transport_type << 4 ) | ( self . destination . type << 2 ) | self . packet_type
2020-08-13 12:15:56 +02:00
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 )
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
2021-05-20 20:32:08 +02:00
elif self . packet_type == Packet . LINKREQUEST :
# Link request packets are not encrypted
self . ciphertext = self . data
2020-08-13 12:15:56 +02:00
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 :
2023-05-09 22:12:49 +02:00
# A resource takes care of encryption
# by itself
2020-08-13 12:15:56 +02:00
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 )
2024-09-08 14:55:07 +02:00
if hasattr ( self . destination , " latest_ratchet_id " ) :
self . ratchet_id = self . destination . latest_ratchet_id
2020-08-13 12:15:56 +02:00
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 " )
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 " )
self . packed = True
2021-05-16 23:14:49 +02:00
self . update_hash ( )
2020-08-13 12:15:56 +02:00
2021-05-20 16:56:08 +02:00
2020-08-13 12:15:56 +02:00
def unpack ( self ) :
2022-01-11 03:06:16 +01:00
try :
self . flags = self . raw [ 0 ]
self . hops = self . raw [ 1 ]
2020-08-13 12:15:56 +02:00
2026-07-10 01:15:57 +02:00
if self . hops > = RNS . Transport . PATHFINDER_M :
raise ValueError ( f " Invalid hop count { self . hops } " )
2023-01-27 14:43:44 +02:00
self . header_type = ( self . flags & 0b01000000 ) >> 6
2024-09-04 17:37:18 +02:00
self . context_flag = ( self . flags & 0b00100000 ) >> 5
self . transport_type = ( self . flags & 0b00010000 ) >> 4
2022-01-11 03:06:16 +01:00
self . destination_type = ( self . flags & 0b00001100 ) >> 2
self . packet_type = ( self . flags & 0b00000011 )
2022-06-30 14:02:57 +02:00
DST_LEN = RNS . Reticulum . TRUNCATED_HASHLENGTH / / 8
2022-01-11 03:06:16 +01:00
if self . header_type == Packet . HEADER_2 :
2022-06-30 14:02:57 +02:00
self . transport_id = self . raw [ 2 : DST_LEN + 2 ]
self . destination_hash = self . raw [ DST_LEN + 2 : 2 * DST_LEN + 2 ]
self . context = ord ( self . raw [ 2 * DST_LEN + 2 : 2 * DST_LEN + 3 ] )
self . data = self . raw [ 2 * DST_LEN + 3 : ]
2026-08-21 16:48:05 +02:00
if len ( self . transport_id ) != DST_LEN : raise ValueError ( " Malformed Transport ID field " )
if len ( self . destination_hash ) != DST_LEN : raise ValueError ( " Malformed destination hash field " )
2022-01-11 03:06:16 +01:00
else :
self . transport_id = None
2022-06-30 14:02:57 +02:00
self . destination_hash = self . raw [ 2 : DST_LEN + 2 ]
self . context = ord ( self . raw [ DST_LEN + 2 : DST_LEN + 3 ] )
self . data = self . raw [ DST_LEN + 3 : ]
2026-08-21 16:48:05 +02:00
if len ( self . destination_hash ) != DST_LEN : raise ValueError ( " Malformed destination hash field " )
2022-01-11 03:06:16 +01:00
2026-08-25 14:02:08 +02:00
if len ( self . data ) == 0 : raise ValueError ( " Zero-length data field " )
2022-01-11 03:06:16 +01:00
self . packed = False
self . update_hash ( )
return True
except Exception as e :
2026-08-25 14:02:08 +02:00
RNS . log ( f " Received malformed packet, dropping it. The contained exception was: { e } " , RNS . LOG_DEBUG ) if RNS . sl ( RNS . LOG_DEBUG ) else None
2022-01-11 03:06:16 +01:00
return False
2020-08-13 12:15:56 +02:00
def send ( self ) :
2021-05-16 23:14:49 +02:00
"""
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 .
"""
2020-08-13 12:15:56 +02:00
if not self . sent :
2026-08-22 11:09:39 +02:00
if self . hops > = RNS . Transport . PATHFINDER_M : return False
2026-07-06 19:23:45 +02:00
if not self . packed : self . pack ( )
2020-08-13 12:15:56 +02:00
if self . destination . type == RNS . Destination . LINK :
if self . destination . status == RNS . Link . CLOSED :
2026-05-06 01:03:43 +02:00
RNS . log ( " Attempt to transmit over a closed link, dropping packet " , RNS . LOG_DEBUG ) if RNS . sl ( RNS . LOG_DEBUG ) else None
2025-04-05 14:40:00 +02:00
self . sent = False
self . receipt = None
return False
2020-08-13 12:15:56 +02:00
else :
self . destination . last_outbound = time . time ( )
self . destination . tx + = 1
2026-07-06 19:23:45 +02:00
self . destination . txbytes + = len ( self . ciphertext )
2022-07-02 08:45:40 +02:00
2026-01-01 14:01:05 +01:00
if RNS . Transport . outbound ( self ) : return self . receipt
2020-08-13 12:15:56 +02:00
else :
2026-09-04 11:41:27 +02:00
if not self . attached_interface :
RNS . log ( f " No interfaces could process the outbound { self . hops } hop, type { self . packet_type } packet for { self . destination } " , RNS . LOG_DEBUG ) if RNS . sl ( RNS . LOG_DEBUG ) else None
2020-08-13 12:15:56 +02:00
self . sent = False
self . receipt = None
return False
else :
raise IOError ( " Packet was already sent " )
def resend ( self ) :
2021-05-16 23:14:49 +02:00
"""
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 .
"""
2020-08-13 12:15:56 +02:00
if self . sent :
2023-05-09 22:12:49 +02:00
# Re-pack the packet to obtain new ciphertext for
# encrypted destinations
self . pack ( )
2026-06-27 21:59:34 +02:00
if RNS . Transport . outbound ( self ) : return self . receipt
2020-08-13 12:15:56 +02:00
else :
2026-04-15 12:06:15 +02:00
RNS . log ( " Re-send failed. No interfaces could process the outbound packet " , RNS . LOG_WARNING )
2020-08-13 12:15:56 +02:00
self . sent = False
self . receipt = None
return False
2026-06-27 21:59:34 +02:00
else : raise IOError ( " Packet was not sent yet " )
2020-08-13 12:15:56 +02:00
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 )
2026-06-27 21:59:34 +02:00
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 )
2020-08-13 12:15:56 +02:00
# Generates a special destination that allows Reticulum
# to direct the proof back to the proved packet's sender
2026-06-27 21:59:34 +02:00
def generate_proof_destination ( self ) : return ProofDestination ( self )
2020-08-13 12:15:56 +02:00
2026-06-27 21:59:34 +02:00
def validate_proof_packet ( self , proof_packet ) : return self . receipt . validate_proof_packet ( proof_packet )
2020-08-13 12:15:56 +02:00
2026-06-27 21:59:34 +02:00
def validate_proof ( self , proof ) : return self . receipt . validate_proof ( proof )
2020-08-13 12:15:56 +02:00
2026-06-27 21:59:34 +02:00
def update_hash ( self ) : self . packet_hash = self . get_hash ( )
2020-08-13 12:15:56 +02:00
2026-06-27 21:59:34 +02:00
def get_hash ( self ) : return RNS . Identity . full_hash ( self . get_hashable_part ( ) )
2020-08-13 12:15:56 +02:00
2026-06-27 21:59:34 +02:00
def getTruncatedHash ( self ) : return RNS . Identity . truncated_hash ( self . get_hashable_part ( ) )
2020-08-13 12:15:56 +02:00
2026-08-24 17:58:29 +02:00
@property
def truncated_packet_hash ( self ) :
return self . packet_hash [ : RNS . Reticulum . TRUNCATED_HASHLENGTH / / 8 ]
2021-05-16 23:14:49 +02:00
def get_hashable_part ( self ) :
2020-08-13 12:15:56 +02:00
hashable_part = bytes ( [ self . raw [ 0 ] & 0b00001111 ] )
2026-06-27 21:59:34 +02:00
if self . header_type == Packet . HEADER_2 : hashable_part + = self . raw [ ( RNS . Identity . TRUNCATED_HASHLENGTH / / 8 ) + 2 : ]
else : hashable_part + = self . raw [ 2 : ]
2020-08-13 12:15:56 +02:00
return hashable_part
2018-03-20 12:32:41 +01:00
2024-10-20 13:34:02 +02:00
def get_rssi ( self ) :
"""
: returns : The physical layer * Received Signal Strength Indication * if available , otherwise ` ` None ` ` .
"""
2026-06-27 21:59:34 +02:00
if self . rssi != None : return self . rssi
2026-08-15 01:01:38 +03:00
else : return RNS . Reticulum . get_instance ( ) . get_packet_rssi ( self . packet_hash )
2024-10-20 13:34:02 +02:00
def get_snr ( self ) :
"""
: returns : The physical layer * Signal - to - Noise Ratio * if available , otherwise ` ` None ` ` .
"""
2026-06-27 21:59:34 +02:00
if self . snr != None : return self . snr
2026-08-15 01:01:38 +03:00
else : return RNS . Reticulum . get_instance ( ) . get_packet_snr ( self . packet_hash )
2024-10-20 13:34:02 +02:00
def get_q ( self ) :
"""
: returns : The physical layer * Link Quality * if available , otherwise ` ` None ` ` .
"""
2026-06-27 21:59:34 +02:00
if self . q != None : return self . q
2026-08-15 01:01:38 +03:00
else : return RNS . Reticulum . get_instance ( ) . get_packet_q ( self . packet_hash )
2024-10-20 13:34:02 +02:00
2018-04-17 17:46:48 +02:00
class ProofDestination :
2020-08-13 12:15:56 +02:00
def __init__ ( self , packet ) :
2026-08-24 17:58:29 +02:00
self . hash = packet . truncated_packet_hash ;
2020-08-13 12:15:56 +02:00
self . type = RNS . Destination . SINGLE
2018-03-20 12:32:41 +01:00
2026-06-27 21:59:34 +02:00
def encrypt ( self , plaintext ) : return plaintext
2018-03-20 12:32:41 +01:00
2018-04-18 23:31:17 +02:00
class PacketReceipt :
2021-05-16 23:14:49 +02:00
"""
The PacketReceipt class is used to receive notifications about
: ref : ` RNS . Packet < api - packet > ` instances sent over the network . Instances
2021-09-02 20:35:42 +02:00
of this class are never created manually , but always returned from
the * send ( ) * method of a : ref : ` RNS . Packet < api - packet > ` instance .
2021-05-16 23:14:49 +02:00
"""
2020-08-13 12:15:56 +02:00
# 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
# Creates a new packet receipt from a sent packet
def __init__ ( self , packet ) :
2026-08-24 17:58:29 +02:00
self . hash = packet . packet_hash
self . truncated_hash = packet . truncated_packet_hash
2021-08-20 23:29:06 +02:00
self . sent = True
self . sent_at = time . time ( )
self . proved = False
self . status = PacketReceipt . SENT
self . destination = packet . destination
self . callbacks = PacketReceiptCallbacks ( )
self . concluded_at = None
2021-10-12 16:34:17 +02:00
self . proof_packet = None
2020-08-13 12:15:56 +02:00
2021-08-28 20:01:01 +02:00
if packet . destination . type == RNS . Destination . LINK :
2024-09-12 17:52:40 +02:00
self . timeout = max ( packet . destination . rtt * packet . destination . traffic_timeout_factor , RNS . Link . TRAFFIC_TIMEOUT_MIN_MS / 1000 )
2021-08-28 20:01:01 +02:00
else :
2023-10-28 00:05:35 +02:00
self . timeout = RNS . Reticulum . get_instance ( ) . get_first_hop_timeout ( self . destination . hash )
2023-10-27 18:16:52 +02:00
self . timeout + = Packet . TIMEOUT_PER_HOP * RNS . Transport . hops_to ( self . destination . hash )
2021-08-28 20:01:01 +02:00
2021-05-16 23:14:49 +02:00
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
2020-08-13 12:15:56 +02:00
# Validate a proof packet
2021-05-16 16:42:07 +02:00
def validate_proof_packet ( self , proof_packet ) :
2020-08-13 12:15:56 +02:00
if hasattr ( proof_packet , " link " ) and proof_packet . link :
2021-10-12 16:34:17 +02:00
return self . validate_link_proof ( proof_packet . data , proof_packet . link , proof_packet )
2020-08-13 12:15:56 +02:00
else :
2021-10-12 16:34:17 +02:00
return self . validate_proof ( proof_packet . data , proof_packet )
2020-08-13 12:15:56 +02:00
# Validate a raw proof for a link
2021-10-12 16:34:17 +02:00
def validate_link_proof ( self , proof , link , proof_packet = None ) :
2020-08-13 12:15:56 +02:00
# 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 ( )
2021-10-12 16:34:17 +02:00
self . proof_packet = proof_packet
2023-05-11 16:22:44 +02:00
link . last_proof = self . concluded_at
2021-10-12 16:34:17 +02:00
2020-08-13 12:15:56 +02:00
if self . callbacks . delivery != None :
2026-06-27 21:59:34 +02:00
try : self . callbacks . delivery ( self )
2023-05-10 18:40:46 +02:00
except Exception as e :
RNS . log ( " An error occurred while evaluating external delivery callback for " + str ( link ) , RNS . LOG_ERROR )
RNS . log ( " The contained exception was: " + str ( e ) , RNS . LOG_ERROR )
2024-09-08 14:55:07 +02:00
RNS . trace_exception ( e )
2023-05-10 18:40:46 +02:00
2020-08-13 12:15:56 +02:00
return True
2026-06-27 21:59:34 +02:00
else : return False
else : return False
2020-08-13 12:15:56 +02:00
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
2026-06-27 21:59:34 +02:00
else : return False
2020-08-13 12:15:56 +02:00
# Validate a raw proof
2021-10-12 16:34:17 +02:00
def validate_proof ( self , proof , proof_packet = None ) :
2020-08-13 12:15:56 +02:00
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 ]
2024-10-05 18:43:56 +02:00
if proof_hash == self . hash and hasattr ( self . destination , " identity " ) and self . destination . identity != None :
2020-08-13 12:15:56 +02:00
proof_valid = self . destination . identity . validate ( signature , self . hash )
if proof_valid :
self . status = PacketReceipt . DELIVERED
self . proved = True
self . concluded_at = time . time ( )
2021-10-12 16:34:17 +02:00
self . proof_packet = proof_packet
2020-08-13 12:15:56 +02:00
if self . callbacks . delivery != None :
2026-06-27 21:59:34 +02:00
try : self . callbacks . delivery ( self )
2021-10-15 14:36:50 +02:00
except Exception as e :
RNS . log ( " Error while executing proof validated callback. The contained exception was: " + str ( e ) , RNS . LOG_ERROR )
2020-08-13 12:15:56 +02:00
return True
2026-06-27 21:59:34 +02:00
else : return False
else : return False
2020-08-13 12:15:56 +02:00
elif len ( proof ) == PacketReceipt . IMPL_LENGTH :
# This is an implicit proof
2024-12-11 19:18:02 +01:00
2026-06-27 21:59:34 +02:00
if not hasattr ( self . destination , " identity " ) : return False
if self . destination . identity == None : return False
2020-08-13 12:15:56 +02:00
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 ( )
2021-10-12 16:34:17 +02:00
self . proof_packet = proof_packet
2021-10-15 14:36:50 +02:00
2020-08-13 12:15:56 +02:00
if self . callbacks . delivery != None :
2026-06-27 21:59:34 +02:00
try : self . callbacks . delivery ( self )
except Exception as e : RNS . log ( " Error while executing proof validated callback. The contained exception was: " + str ( e ) , RNS . LOG_ERROR )
2021-10-15 14:36:50 +02:00
2020-08-13 12:15:56 +02:00
return True
2026-06-27 21:59:34 +02:00
else : return False
else : return False
2020-08-13 12:15:56 +02:00
2021-05-20 15:31:38 +02:00
def get_rtt ( self ) :
2021-05-16 23:14:49 +02:00
"""
: returns : The round - trip - time in seconds
"""
2020-08-13 12:15:56 +02:00
return self . concluded_at - self . sent_at
def is_timed_out ( self ) :
return ( self . sent_at + self . timeout < time . time ( ) )
def check_timeout ( self ) :
2021-08-28 23:53:51 +02:00
if self . status == PacketReceipt . SENT and self . is_timed_out ( ) :
2026-06-27 21:59:34 +02:00
if self . timeout == - 1 : self . status = PacketReceipt . CULLED
else : self . status = PacketReceipt . FAILED
2020-08-13 12:15:56 +02:00
self . concluded_at = time . time ( )
if self . callbacks . timeout :
thread = threading . Thread ( target = self . callbacks . timeout , args = ( self , ) )
2022-09-30 19:02:25 +02:00
thread . daemon = True
2020-08-13 12:15:56 +02:00
thread . start ( )
def set_timeout ( self , timeout ) :
2021-05-16 23:14:49 +02:00
"""
Sets a timeout in seconds
: param timeout : The timeout in seconds .
"""
2020-08-13 12:15:56 +02:00
self . timeout = float ( timeout )
2021-05-20 15:31:38 +02:00
def set_delivery_callback ( self , callback ) :
2021-05-16 23:14:49 +02:00
"""
Sets a function that gets called if a successfull delivery has been proven .
: param callback : A * callable * with the signature * callback ( packet_receipt ) *
"""
2020-08-13 12:15:56 +02:00
self . callbacks . delivery = callback
# Set a function that gets called if the
# delivery times out
2021-05-20 15:31:38 +02:00
def set_timeout_callback ( self , callback ) :
2021-05-16 23:14:49 +02:00
"""
Sets a function that gets called if the delivery times out .
: param callback : A * callable * with the signature * callback ( packet_receipt ) *
"""
2020-08-13 12:15:56 +02:00
self . callbacks . timeout = callback
2018-04-18 23:31:17 +02:00
class PacketReceiptCallbacks :
2020-08-13 12:15:56 +02:00
def __init__ ( self ) :
self . delivery = None
self . timeout = None