Reticulum/RNS/Resource.py

677 lines
22 KiB
Python
Raw Permalink Normal View History

2018-04-18 21:31:17 +00:00
import RNS
import bz2
import math
import time
import threading
2020-04-22 10:07:13 +00:00
from .vendor import umsgpack as umsgpack
from time import sleep
2018-04-18 21:31:17 +00:00
class Resource:
WINDOW_FLEXIBILITY = 4
WINDOW_MIN = 1
WINDOW_MAX = 10
WINDOW = 4
MAPHASH_LEN = 4
SDU = RNS.Packet.MDU
RANDOM_HASH_SIZE = 4
# The maximum size to auto-compress with
# bz2 before sending.
AUTO_COMPRESS_MAX_SIZE = 32 * 1024 * 1024
2018-04-18 21:31:17 +00:00
2018-04-23 21:42:16 +00:00
# TODO: Should be allocated more
# intelligently
2020-04-28 20:23:14 +00:00
MAX_RETRIES = 2
2018-04-23 21:42:16 +00:00
SENDER_GRACE_TIME = 10
RETRY_GRACE_TIME = 0.25
2018-04-18 21:31:17 +00:00
HASHMAP_IS_NOT_EXHAUSTED = 0x00
HASHMAP_IS_EXHAUSTED = 0xFF
# Status constants
NONE = 0x00
QUEUED = 0x01
ADVERTISED = 0x02
TRANSFERRING = 0x03
2018-04-23 21:42:16 +00:00
AWAITING_PROOF = 0x04
ASSEMBLING = 0x05
COMPLETE = 0x06
FAILED = 0x07
CORRUPT = 0x08
2018-04-18 21:31:17 +00:00
@staticmethod
def accept(advertisement_packet, callback=None, progress_callback = None):
try:
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
resource = Resource(None, advertisement_packet.link)
resource.status = Resource.TRANSFERRING
resource.flags = adv.f
resource.size = adv.t
resource.uncompressed_size = adv.d
resource.hash = adv.h
resource.random_hash = adv.r
resource.hashmap_raw = adv.m
resource.encrypted = True if resource.flags & 0x01 else False
resource.compressed = True if resource.flags >> 1 & 0x01 else False
resource.initiator = False
resource.callback = callback
resource.__progress_callback = progress_callback
resource.total_parts = int(math.ceil(resource.size/float(Resource.SDU)))
resource.received_count = 0
resource.outstanding_parts = 0
resource.parts = [None] * resource.total_parts
resource.window = Resource.WINDOW
resource.window_max = Resource.WINDOW_MAX
resource.window_min = Resource.WINDOW_MIN
resource.window_flexibility = Resource.WINDOW_FLEXIBILITY
2018-04-18 21:31:17 +00:00
resource.last_activity = time.time()
resource.hashmap = [None] * resource.total_parts
resource.hashmap_height = 0
resource.waiting_for_hmu = False
resource.consecutive_completed_height = 0
2018-04-18 21:31:17 +00:00
resource.link.register_incoming_resource(resource)
RNS.log("Accepting resource advertisement for "+RNS.prettyhexrep(resource.hash), RNS.LOG_DEBUG)
resource.link.callbacks.resource_started(resource)
resource.hashmap_update(0, resource.hashmap_raw)
2018-04-23 21:42:16 +00:00
resource.watchdog_job()
2018-04-18 21:31:17 +00:00
return resource
2020-04-22 10:07:13 +00:00
except:
2018-04-23 21:42:16 +00:00
RNS.log("Could not decode resource advertisement, dropping resource", RNS.LOG_DEBUG)
2018-04-18 21:31:17 +00:00
return None
def __init__(self, data, link, advertise=True, auto_compress=True, must_compress=False, callback=None, progress_callback=None):
2018-04-18 21:31:17 +00:00
self.status = Resource.NONE
self.link = link
2018-04-23 21:42:16 +00:00
self.max_retries = Resource.MAX_RETRIES
self.retries_left = self.max_retries
self.default_timeout = self.link.default_timeout
self.timeout_factor = self.link.timeout_factor
self.sender_grace_time = Resource.SENDER_GRACE_TIME
self.hmu_retry_ok = False
self.watchdog_lock = False
self.__watchdog_job_id = 0
self.__progress_callback = progress_callback
2018-04-18 21:31:17 +00:00
self.rtt = None
self.receiver_min_consecutive_height = 0
2018-04-18 21:31:17 +00:00
if data != None:
self.initiator = True
self.callback = callback
self.uncompressed_data = data
compression_began = time.time()
if must_compress or (auto_compress and len(self.uncompressed_data) < Resource.AUTO_COMPRESS_MAX_SIZE):
RNS.log("Compressing resource data...", RNS.LOG_DEBUG)
2018-04-18 21:31:17 +00:00
self.compressed_data = bz2.compress(self.uncompressed_data)
RNS.log("Compression completed in "+str(round(time.time()-compression_began, 3))+" seconds", RNS.LOG_DEBUG)
else:
self.compressed_data = self.uncompressed_data
2018-04-18 21:31:17 +00:00
self.uncompressed_size = len(self.uncompressed_data)
self.compressed_size = len(self.compressed_data)
2018-04-18 21:31:17 +00:00
if (self.compressed_size < self.uncompressed_size and auto_compress):
saved_bytes = len(self.uncompressed_data) - len(self.compressed_data)
RNS.log("Compression saved "+str(saved_bytes)+" bytes, sending compressed", RNS.LOG_DEBUG)
2018-04-18 21:31:17 +00:00
self.data = b""
self.data += RNS.Identity.getRandomHash()[:Resource.RANDOM_HASH_SIZE]
self.data += self.compressed_data
self.compressed = True
self.uncompressed_data = None
else:
self.data = b""
self.data += RNS.Identity.getRandomHash()[:Resource.RANDOM_HASH_SIZE]
self.data += self.uncompressed_data
self.uncompressed_data = self.data
self.compressed = False
self.compressed_data = None
if auto_compress:
RNS.log("Compression did not decrease size, sending uncompressed", RNS.LOG_DEBUG)
# Resources handle encryption directly to
# make optimal use of packet MTU on an entire
# encrypted stream. The Resource instance will
# use it's underlying link directly to encrypt.
if not self.link.encryption_disabled():
self.data = self.link.encrypt(self.data)
self.encrypted = True
else:
self.encrypted = False
2018-04-18 21:31:17 +00:00
self.size = len(self.data)
self.sent_parts = 0
hashmap_entries = int(math.ceil(self.size/float(Resource.SDU)))
2018-04-18 21:31:17 +00:00
hashmap_ok = False
while not hashmap_ok:
hashmap_computation_began = time.time()
RNS.log("Starting resource hashmap computation with "+str(hashmap_entries)+" entries...", RNS.LOG_DEBUG)
self.random_hash = RNS.Identity.getRandomHash()[:Resource.RANDOM_HASH_SIZE]
self.hash = RNS.Identity.fullHash(data+self.random_hash)
self.expected_proof = RNS.Identity.fullHash(data+self.hash)
2018-04-18 21:31:17 +00:00
self.parts = []
self.hashmap = b""
collision_guard_list = []
for i in range(0,hashmap_entries):
2018-04-18 21:31:17 +00:00
data = self.data[i*Resource.SDU:(i+1)*Resource.SDU]
map_hash = self.getMapHash(data)
2018-04-18 21:31:17 +00:00
if map_hash in collision_guard_list:
RNS.log("Found hash collision in resource map, remapping...", RNS.LOG_VERBOSE)
hashmap_ok = False
break
else:
hashmap_ok = True
collision_guard_list.append(map_hash)
if len(collision_guard_list) > ResourceAdvertisement.COLLISION_GUARD_SIZE:
collision_guard_list.pop(0)
2018-04-18 21:31:17 +00:00
part = RNS.Packet(link, data, context=RNS.Packet.RESOURCE)
part.pack()
part.map_hash = map_hash
2018-04-18 21:31:17 +00:00
self.hashmap += part.map_hash
self.parts.append(part)
RNS.log("Hashmap computation concluded in "+str(round(time.time()-hashmap_computation_began, 3))+" seconds", RNS.LOG_DEBUG)
if advertise:
self.advertise()
else:
pass
2018-04-18 21:31:17 +00:00
def hashmap_update_packet(self, plaintext):
if not self.status == Resource.FAILED:
2018-04-23 21:42:16 +00:00
self.last_activity = time.time()
self.retries_left = self.max_retries
2020-04-22 10:07:13 +00:00
update = umsgpack.unpackb(plaintext[RNS.Identity.HASHLENGTH//8:])
2018-04-18 21:31:17 +00:00
self.hashmap_update(update[0], update[1])
def hashmap_update(self, segment, hashmap):
if not self.status == Resource.FAILED:
2018-04-23 21:42:16 +00:00
self.status = Resource.TRANSFERRING
2018-04-18 21:31:17 +00:00
seg_len = ResourceAdvertisement.HASHMAP_MAX_LEN
2020-04-22 10:07:13 +00:00
hashes = len(hashmap)//Resource.MAPHASH_LEN
2018-04-18 21:31:17 +00:00
for i in range(0,hashes):
2018-04-23 21:42:16 +00:00
if self.hashmap[i+segment*seg_len] == None:
self.hashmap_height += 1
2018-04-18 21:31:17 +00:00
self.hashmap[i+segment*seg_len] = hashmap[i*Resource.MAPHASH_LEN:(i+1)*Resource.MAPHASH_LEN]
self.waiting_for_hmu = False
self.request_next()
def getMapHash(self, data):
# TODO: This will break if running unencrypted,
# uncompressed transfers on streams with long blocks
# of identical bytes. Doing so would be very silly
# anyways but maybe it should be handled gracefully.
2018-04-18 21:31:17 +00:00
return RNS.Identity.fullHash(data+self.random_hash)[:Resource.MAPHASH_LEN]
def advertise(self):
thread = threading.Thread(target=self.__advertise_job)
thread.setDaemon(True)
thread.start()
def __advertise_job(self):
data = ResourceAdvertisement(self).pack()
2018-04-23 21:42:16 +00:00
self.advertisement_packet = RNS.Packet(self.link, data, context=RNS.Packet.RESOURCE_ADV)
2018-04-18 21:31:17 +00:00
while not self.link.ready_for_new_resource():
self.status = Resource.QUEUED
sleep(0.25)
try:
self.advertisement_packet.send()
self.last_activity = time.time()
self.adv_sent = self.last_activity
self.rtt = None
self.status = Resource.ADVERTISED
self.link.register_outgoing_resource(self)
RNS.log("Sent resource advertisement for "+RNS.prettyhexrep(self.hash), RNS.LOG_DEBUG)
except Exception as e:
RNS.log("Could not advertise resource, the contained exception was: "+str(e), RNS.LOG_ERROR)
self.cancel()
return
2018-04-18 21:31:17 +00:00
2018-04-23 21:42:16 +00:00
self.watchdog_job()
def watchdog_job(self):
thread = threading.Thread(target=self.__watchdog_job)
thread.setDaemon(True)
thread.start()
def __watchdog_job(self):
self.__watchdog_job_id += 1
this_job_id = self.__watchdog_job_id
while self.status < Resource.ASSEMBLING and this_job_id == self.__watchdog_job_id:
while self.watchdog_lock:
sleep(0.025)
sleep_time = None
if self.status == Resource.ADVERTISED:
sleep_time = (self.adv_sent+self.default_timeout)-time.time()
if sleep_time < 0:
if self.retries_left <= 0:
RNS.log("Resource transfer timeout after sending advertisement", RNS.LOG_DEBUG)
self.cancel()
sleep_time = 0.001
else:
try:
RNS.log("No part requests received, retrying resource advertisement...", RNS.LOG_DEBUG)
self.retries_left -= 1
self.advertisement_packet.resend()
self.last_activity = time.time()
self.adv_sent = self.last_activity
sleep_time = 0.001
except Exception as e:
RNS.log("Could not resend advertisement packet, cancelling resource", RNS.LOG_VERBOSE)
self.cancel()
2018-04-23 21:42:16 +00:00
elif self.status == Resource.TRANSFERRING:
if not self.initiator:
rtt = self.link.rtt if self.rtt == None else self.rtt
sleep_time = self.last_activity + (rtt*self.timeout_factor) + Resource.RETRY_GRACE_TIME - time.time()
2018-04-23 21:42:16 +00:00
if sleep_time < 0:
if self.retries_left > 0:
RNS.log("Timeout waiting for parts, requesting retry", RNS.LOG_DEBUG)
if self.window > self.window_min:
self.window -= 1
if self.window_max > self.window_min:
self.window_max -= 1
if (self.window_max - self.window) > (self.window_flexibility-1):
self.window_max -= 1
2018-04-23 21:42:16 +00:00
sleep_time = 0.001
self.retries_left -= 1
self.waiting_for_hmu = False
self.request_next()
else:
self.cancel()
sleep_time = 0.001
else:
max_wait = self.rtt * self.timeout_factor * self.max_retries + self.sender_grace_time
sleep_time = self.last_activity + max_wait - time.time()
if sleep_time < 0:
RNS.log("Resource timed out waiting for part requests", RNS.LOG_DEBUG)
self.cancel()
sleep_time = 0.001
elif self.status == Resource.AWAITING_PROOF:
sleep_time = self.last_part_sent + (self.rtt*self.timeout_factor+self.sender_grace_time) - time.time()
if sleep_time < 0:
if self.retries_left <= 0:
RNS.log("Resource timed out waiting for proof", RNS.LOG_DEBUG)
self.cancel()
sleep_time = 0.001
else:
RNS.log("All parts sent, but no resource proof received, querying network cache...", RNS.LOG_DEBUG)
self.retries_left -= 1
expected_data = self.hash + self.expected_proof
expected_proof_packet = RNS.Packet(self.link, expected_data, packet_type=RNS.Packet.PROOF, context=RNS.Packet.RESOURCE_PRF)
expected_proof_packet.pack()
RNS.Transport.cache_request(expected_proof_packet.packet_hash)
self.last_part_sent = time.time()
sleep_time = 0.001
if sleep_time == 0:
RNS.log("Warning! Link watchdog sleep time of 0!", RNS.LOG_WARNING)
if sleep_time == None or sleep_time < 0:
RNS.log("Timing error, cancelling resource transfer.", RNS.LOG_ERROR)
self.cancel()
if sleep_time != None:
sleep(sleep_time)
2018-04-23 21:42:16 +00:00
2018-04-18 21:31:17 +00:00
def assemble(self):
2020-05-13 07:31:43 +00:00
# TODO: Optimise assembly. It's way too
# slow for larger files
2018-04-18 21:31:17 +00:00
if not self.status == Resource.FAILED:
try:
2018-04-23 21:42:16 +00:00
self.status = Resource.ASSEMBLING
2020-04-22 10:07:13 +00:00
stream = b""
2018-04-18 21:31:17 +00:00
for part in self.parts:
stream += part
if self.encrypted:
data = self.link.decrypt(stream)
else:
data = stream
# Strip off random hash
data = data[Resource.RANDOM_HASH_SIZE:]
2018-04-18 21:31:17 +00:00
if self.compressed:
self.data = bz2.decompress(data)
else:
self.data = data
calculated_hash = RNS.Identity.fullHash(self.data+self.random_hash)
if calculated_hash == self.hash:
self.status = Resource.COMPLETE
self.prove()
else:
self.status = Resource.CORRUPT
2018-04-18 21:31:17 +00:00
except Exception as e:
RNS.log("Error while assembling received resource.", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
self.status = Resource.CORRUPT
if self.callback != None:
self.link.resource_concluded(self)
2018-04-18 21:31:17 +00:00
self.callback(self)
def prove(self):
if not self.status == Resource.FAILED:
try:
proof = RNS.Identity.fullHash(self.data+self.hash)
proof_data = self.hash+proof
proof_packet = RNS.Packet(self.link, proof_data, packet_type=RNS.Packet.PROOF, context=RNS.Packet.RESOURCE_PRF)
proof_packet.send()
except Exception as e:
RNS.log("Could not send proof packet, cancelling resource", RNS.LOG_DEBUG)
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
self.cancel()
2018-04-18 21:31:17 +00:00
def validateProof(self, proof_data):
if not self.status == Resource.FAILED:
2020-04-22 10:07:13 +00:00
if len(proof_data) == RNS.Identity.HASHLENGTH//8*2:
if proof_data[RNS.Identity.HASHLENGTH//8:] == self.expected_proof:
2018-04-18 21:31:17 +00:00
self.status = Resource.COMPLETE
if self.callback != None:
self.link.resource_concluded(self)
2018-04-18 21:31:17 +00:00
self.callback(self)
else:
pass
else:
pass
def receive_part(self, packet):
self.last_activity = time.time()
2018-04-23 21:42:16 +00:00
self.retries_left = self.max_retries
2018-04-18 21:31:17 +00:00
if self.req_resp == None:
self.req_resp = self.last_activity
rtt = self.req_resp-self.req_sent
if self.rtt == None:
self.rtt = rtt
2018-04-23 21:42:16 +00:00
self.watchdog_job()
2018-04-18 21:31:17 +00:00
elif self.rtt < rtt:
self.rtt = rtt
if not self.status == Resource.FAILED:
self.status = Resource.TRANSFERRING
part_data = packet.data
part_hash = self.getMapHash(part_data)
i = self.consecutive_completed_height
for map_hash in self.hashmap[self.consecutive_completed_height:self.consecutive_completed_height+self.window]:
2018-04-18 21:31:17 +00:00
if map_hash == part_hash:
if self.parts[i] == None:
# Insert data into parts list
2018-04-18 21:31:17 +00:00
self.parts[i] = part_data
self.received_count += 1
self.outstanding_parts -= 1
# Update consecutive completed pointer
if i == 0 or self.parts[i-1] != None and i == self.consecutive_completed_height:
self.consecutive_completed_height = i+1
cp = i+1
while cp < len(self.parts) and self.parts[cp] != None:
self.consecutive_completed_height = cp
cp += 1
2018-04-18 21:31:17 +00:00
i += 1
if self.__progress_callback != None:
self.__progress_callback(self)
if self.outstanding_parts == 0 and self.received_count == self.total_parts:
self.assemble()
elif self.outstanding_parts == 0:
# TODO: Figure out if ther is a mathematically
# optimal way to adjust windows
if self.window < self.window_max:
2018-04-18 21:31:17 +00:00
self.window += 1
if (self.window - self.window_min) > (self.window_flexibility-1):
self.window_min += 1
2018-04-18 21:31:17 +00:00
self.request_next()
# Called on incoming resource to send a request for more data
def request_next(self):
if not self.status == Resource.FAILED:
if not self.waiting_for_hmu:
self.outstanding_parts = 0
hashmap_exhausted = Resource.HASHMAP_IS_NOT_EXHAUSTED
2020-04-22 10:07:13 +00:00
requested_hashes = b""
2018-04-18 21:31:17 +00:00
i = 0; pn = self.consecutive_completed_height
for part in self.parts[self.consecutive_completed_height:self.consecutive_completed_height+self.window]:
2018-04-18 21:31:17 +00:00
if part == None:
part_hash = self.hashmap[pn]
if part_hash != None:
requested_hashes += part_hash
self.outstanding_parts += 1
i += 1
else:
hashmap_exhausted = Resource.HASHMAP_IS_EXHAUSTED
pn += 1
if i >= self.window or hashmap_exhausted == Resource.HASHMAP_IS_EXHAUSTED:
break
2020-04-22 10:07:13 +00:00
hmu_part = bytes([hashmap_exhausted])
2018-04-18 21:31:17 +00:00
if hashmap_exhausted == Resource.HASHMAP_IS_EXHAUSTED:
last_map_hash = self.hashmap[self.hashmap_height-1]
hmu_part += last_map_hash
self.waiting_for_hmu = True
2020-04-22 10:07:13 +00:00
requested_data = b""
2018-04-18 21:31:17 +00:00
request_data = hmu_part + self.hash + requested_hashes
request_packet = RNS.Packet(self.link, request_data, context = RNS.Packet.RESOURCE_REQ)
try:
request_packet.send()
self.last_activity = time.time()
self.req_sent = self.last_activity
self.req_resp = None
except Exception as e:
RNS.log("Could not send resource request packet, cancelling resource", RNS.LOG_DEBUG)
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
self.cancel()
2018-04-18 21:31:17 +00:00
# Called on outgoing resource to make it send more data
def request(self, request_data):
if not self.status == Resource.FAILED:
rtt = time.time() - self.adv_sent
if self.rtt == None:
self.rtt = rtt
2018-04-23 21:42:16 +00:00
if self.status != Resource.TRANSFERRING:
self.status = Resource.TRANSFERRING
self.watchdog_job()
self.retries_left = self.max_retries
2020-04-22 10:07:13 +00:00
wants_more_hashmap = True if request_data[0] == Resource.HASHMAP_IS_EXHAUSTED else False
2018-04-18 21:31:17 +00:00
pad = 1+Resource.MAPHASH_LEN if wants_more_hashmap else 1
2020-04-22 10:07:13 +00:00
requested_hashes = request_data[pad+RNS.Identity.HASHLENGTH//8:]
2018-04-18 21:31:17 +00:00
2020-04-22 10:07:13 +00:00
for i in range(0,len(requested_hashes)//Resource.MAPHASH_LEN):
2018-04-18 21:31:17 +00:00
requested_hash = requested_hashes[i*Resource.MAPHASH_LEN:(i+1)*Resource.MAPHASH_LEN]
search_start = self.receiver_min_consecutive_height
search_end = self.receiver_min_consecutive_height+ResourceAdvertisement.COLLISION_GUARD_SIZE
for part in self.parts[search_start:search_end]:
2018-04-18 21:31:17 +00:00
if part.map_hash == requested_hash:
2020-05-12 06:50:51 +00:00
try:
if not part.sent:
part.send()
self.sent_parts += 1
else:
part.resend()
self.last_activity = time.time()
self.last_part_sent = self.last_activity
break
except Exception as e:
RNS.log("Resource could not send parts, cancelling transfer!", RNS.LOG_DEBUG)
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
2020-05-12 06:50:51 +00:00
self.cancel()
2018-04-18 21:31:17 +00:00
if wants_more_hashmap:
last_map_hash = request_data[1:Resource.MAPHASH_LEN+1]
part_index = self.receiver_min_consecutive_height
search_start = part_index
search_end = self.receiver_min_consecutive_height+ResourceAdvertisement.COLLISION_GUARD_SIZE
for part in self.parts[search_start:search_end]:
2018-04-18 21:31:17 +00:00
part_index += 1
if part.map_hash == last_map_hash:
break
self.receiver_min_consecutive_height = max(part_index-1-Resource.WINDOW_MAX, 0)
2018-04-18 21:31:17 +00:00
if part_index % ResourceAdvertisement.HASHMAP_MAX_LEN != 0:
RNS.log("Resource sequencing error, cancelling transfer!", RNS.LOG_ERROR)
self.cancel()
else:
2020-04-22 10:07:13 +00:00
segment = part_index // ResourceAdvertisement.HASHMAP_MAX_LEN
2018-04-18 21:31:17 +00:00
hashmap_start = segment*ResourceAdvertisement.HASHMAP_MAX_LEN
hashmap_end = min((segment+1)*ResourceAdvertisement.HASHMAP_MAX_LEN, len(self.parts))
2020-04-22 10:07:13 +00:00
hashmap = b""
2018-04-18 21:31:17 +00:00
for i in range(hashmap_start,hashmap_end):
hashmap += self.hashmap[i*Resource.MAPHASH_LEN:(i+1)*Resource.MAPHASH_LEN]
hmu = self.hash+umsgpack.packb([segment, hashmap])
hmu_packet = RNS.Packet(self.link, hmu, context = RNS.Packet.RESOURCE_HMU)
2018-04-23 21:42:16 +00:00
try:
hmu_packet.send()
self.last_activity = time.time()
except Exception as e:
RNS.log("Could not send resource HMU packet, cancelling resource", RNS.LOG_DEBUG)
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
self.cancel()
2018-04-18 21:31:17 +00:00
2018-04-23 21:42:16 +00:00
if self.sent_parts == len(self.parts):
self.status = Resource.AWAITING_PROOF
if self.__progress_callback != None:
self.__progress_callback(self)
2018-04-18 21:31:17 +00:00
def cancel(self):
2018-04-23 21:42:16 +00:00
if self.status < Resource.COMPLETE:
self.status = Resource.FAILED
if self.initiator:
if self.link.status == RNS.Link.ACTIVE:
2020-05-12 06:50:51 +00:00
try:
cancel_packet = RNS.Packet(self.link, self.hash, context=RNS.Packet.RESOURCE_ICL)
cancel_packet.send()
except Exception as e:
RNS.log("Could not send resource cancel packet, the contained exception was: "+str(e), RNS.LOG_ERROR)
2018-04-23 21:42:16 +00:00
self.link.cancel_outgoing_resource(self)
else:
self.link.cancel_incoming_resource(self)
if self.callback != None:
self.link.resource_concluded(self)
2018-04-23 21:42:16 +00:00
self.callback(self)
2018-04-18 21:31:17 +00:00
def progress_callback(self, callback):
self.__progress_callback = callback
def progress(self):
if self.initiator:
progress = self.sent_parts / len(self.parts)
else:
progress = self.received_count / float(self.total_parts)
2018-04-18 21:31:17 +00:00
return progress
def __str__(self):
2018-04-23 21:42:16 +00:00
return RNS.prettyhexrep(self.hash)+str(self.link)
2018-04-18 21:31:17 +00:00
class ResourceAdvertisement:
# TODO: Can this be allocated dynamically? Keep in mind hashmap_update inference
HASHMAP_MAX_LEN = 84
COLLISION_GUARD_SIZE = 2*Resource.WINDOW_MAX+HASHMAP_MAX_LEN
2018-04-18 21:31:17 +00:00
def __init__(self, resource=None):
if resource != None:
self.t = resource.size # Transfer size
self.d = resource.uncompressed_size # Data size
self.n = len(resource.parts) # Number of parts
self.h = resource.hash # Resource hash
self.r = resource.random_hash # Resource random hash
self.m = resource.hashmap # Resource hashmap
self.c = resource.compressed # Compression flag
self.e = resource.encrypted # Encryption flag
self.f = 0x00 | self.c << 1 | self.e # Flags
def pack(self, segment=0):
hashmap_start = segment*ResourceAdvertisement.HASHMAP_MAX_LEN
hashmap_end = min((segment+1)*ResourceAdvertisement.HASHMAP_MAX_LEN, self.n)
2020-04-22 10:07:13 +00:00
hashmap = b""
2018-04-18 21:31:17 +00:00
for i in range(hashmap_start,hashmap_end):
hashmap += self.m[i*Resource.MAPHASH_LEN:(i+1)*Resource.MAPHASH_LEN]
dictionary = {
2020-04-22 10:07:13 +00:00
"t": self.t,
"d": self.d,
"n": self.n,
"h": self.h,
"r": self.r,
"f": self.f,
"m": hashmap
2018-04-18 21:31:17 +00:00
}
return umsgpack.packb(dictionary)
@staticmethod
def unpack(data):
dictionary = umsgpack.unpackb(data)
adv = ResourceAdvertisement()
adv.t = dictionary["t"]
adv.d = dictionary["d"]
adv.n = dictionary["n"]
adv.h = dictionary["h"]
adv.r = dictionary["r"]
adv.m = dictionary["m"]
adv.f = dictionary["f"]
adv.e = True if (adv.f & 0x01) == 0x01 else False
adv.c = True if ((adv.f >> 1) & 0x01) == 0x01 else False
return adv