diff --git a/RNS/Destination.py b/RNS/Destination.py index 7ce6239f..9fc6ab03 100755 --- a/RNS/Destination.py +++ b/RNS/Destination.py @@ -368,9 +368,9 @@ class Destination: def set_max_request_size(self, max_request_size): """ - Registers a request handler. + Sets the maximum accepted request size for registered request handlers. - :param max_request_size: The path for the request handler to be registered. + :param max_request_size: The maximum accepted request size in bytes, as an integer. :raises: ``TypeError`` or ``ValueError`` if any of the argument is invalid. """ try: max_request_size = int(max_request_size) diff --git a/RNS/Link.py b/RNS/Link.py index be4d8202..41f6c9e3 100644 --- a/RNS/Link.py +++ b/RNS/Link.py @@ -470,7 +470,8 @@ class Link: self.had_outbound() - def request(self, path, data = None, response_callback = None, failed_callback = None, progress_callback = None, timeout = None): + def request(self, path, data=None, response_callback=None, failed_callback=None, progress_callback=None, + timeout=None, max_response_size=None): """ Sends a request to the remote peer. @@ -478,6 +479,7 @@ class Link: :param response_callback: An optional function or method with the signature *response_callback(request_receipt)* to be called when a response is received. See the :ref:`Request Example` for more info. :param failed_callback: An optional function or method with the signature *failed_callback(request_receipt)* to be called when a request fails. See the :ref:`Request Example` for more info. :param progress_callback: An optional function or method with the signature *progress_callback(request_receipt)* to be called when progress is made receiving the response. Progress can be accessed as a float between 0.0 and 1.0 by the *request_receipt.progress* property. + :param max_response_size: An optional maximum accepted response size, in bytes as an integer. :param timeout: An optional timeout in seconds for the request. If *None* is supplied it will be calculated based on link RTT. :returns: A :ref:`RNS.RequestReceipt` instance if the request was sent, or *False* if it was not. """ @@ -495,18 +497,17 @@ class Link: if packet_receipt == False: return False else: packet_receipt.set_timeout(timeout) - return RequestReceipt(self, packet_receipt = packet_receipt, response_callback = response_callback, - failed_callback = failed_callback, progress_callback = progress_callback, - timeout = timeout, request_size = len(packed_request)) - + return RequestReceipt(self, packet_receipt=packet_receipt, response_callback=response_callback, + failed_callback=failed_callback, progress_callback=progress_callback, timeout=timeout, + request_size=len(packed_request), max_response_size=max_response_size) else: request_id = RNS.Identity.truncated_hash(packed_request) RNS.log("Sending request "+RNS.prettyhexrep(request_id)+" as resource.", RNS.LOG_DEBUG) request_resource = RNS.Resource(packed_request, self, request_id = request_id, is_response = False, timeout = timeout) - return RequestReceipt(self, resource = request_resource, response_callback = response_callback, - failed_callback = failed_callback, progress_callback = progress_callback, - timeout = timeout, request_size = len(packed_request)) + return RequestReceipt(self, resource=request_resource, response_callback=response_callback, + failed_callback=failed_callback, progress_callback=progress_callback, timeout=timeout, + request_size=len(packed_request), max_response_size=max_response_size) def update_mdu(self): self.mdu = self.mtu - RNS.Reticulum.HEADER_MAXSIZE - RNS.Reticulum.IFAC_MIN_SIZE @@ -853,19 +854,26 @@ class Link: identity_string = str(self.get_remote_identity()) if self.get_remote_identity() != None else "" RNS.log("Request "+RNS.prettyhexrep(request_id)+" from "+identity_string+" not allowed for: "+str(path), RNS.LOG_DEBUG) - def handle_response(self, request_id, response_data, response_size, response_transfer_size, metadata=None, update_sizes=False): + def handle_response(self, request_id, response_data, response_size, response_transfer_size, metadata=None, update_sizes=False, check_size=False): if self.status == Link.ACTIVE: remove = None for pending_request in self.pending_requests: if pending_request.request_id == request_id: + if not check_size or pending_request.max_response_size == None: size_ok = True + else: size_ok = response_size <= pending_request.max_response_size + remove = pending_request + try: if update_sizes: pending_request.response_size = response_size if pending_request.response_transfer_size == None: pending_request.response_transfer_size = 0 pending_request.response_transfer_size += response_transfer_size - pending_request.response_received(response_data, metadata) + if size_ok: pending_request.response_received(response_data, metadata) + else: + RNS.log(f"Rejected response with excessive size {RNS.prettysize(response_size)} on {self}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None + pending_request.response_rejected() except Exception as e: RNS.log("Error occurred while handling response. The contained exception was: "+str(e), RNS.LOG_ERROR) break @@ -999,7 +1007,7 @@ class Link: request_id = unpacked_response[0] response_data = unpacked_response[1] transfer_size = len(umsgpack.packb(response_data))-2 - def job(): self.handle_response(request_id, response_data, transfer_size, transfer_size, update_sizes=True) + def job(): self.handle_response(request_id, response_data, transfer_size, transfer_size, update_sizes=True, check_size=True) threading.Thread(target=job, daemon=True).start() self.__update_phy_stats(packet, query_shared=True) except Exception as e: RNS.log("Error occurred while handling response. The contained exception was: "+str(e), RNS.LOG_ERROR) @@ -1025,21 +1033,28 @@ class Link: if size_ok: RNS.Resource.accept(packet, callback=self.request_resource_concluded) else: RNS.Resource.reject(packet) - RNS.log(f"Ignored request with excessive size {RNS.prettysize(RNS.ResourceAdvertisement.read_size(packet))} on {self.destination}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None + RNS.log(f"Rejected request with excessive size {RNS.prettysize(RNS.ResourceAdvertisement.read_size(packet))} on {self}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None elif RNS.ResourceAdvertisement.is_response(packet): request_id = RNS.ResourceAdvertisement.read_request_id(packet) for pending_request in self.pending_requests: if pending_request.request_id == request_id: - response_resource = RNS.Resource.accept(packet, callback=self.response_resource_concluded, progress_callback=pending_request.response_resource_progress, request_id = request_id) - if response_resource != None: - if pending_request.response_size == None: - pending_request.response_size = RNS.ResourceAdvertisement.read_size(packet) - if pending_request.response_transfer_size == None: - pending_request.response_transfer_size = 0 - pending_request.response_transfer_size += RNS.ResourceAdvertisement.read_transfer_size(packet) - if pending_request.started_at == None: - pending_request.started_at = time.time() - pending_request.response_resource_progress(response_resource) + if pending_request.max_response_size == None: size_ok = True + else: size_ok = RNS.ResourceAdvertisement.read_size(packet) <= pending_request.max_response_size + if not size_ok: + RNS.Resource.reject(packet) + pending_request.response_rejected() + RNS.log(f"Rejected response with excessive size {RNS.prettysize(RNS.ResourceAdvertisement.read_size(packet))} on {self}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None + else: + response_resource = RNS.Resource.accept(packet, callback=self.response_resource_concluded, progress_callback=pending_request.response_resource_progress, request_id = request_id) + if response_resource != None: + if pending_request.response_size == None: + pending_request.response_size = RNS.ResourceAdvertisement.read_size(packet) + if pending_request.response_transfer_size == None: + pending_request.response_transfer_size = 0 + pending_request.response_transfer_size += RNS.ResourceAdvertisement.read_transfer_size(packet) + if pending_request.started_at == None: + pending_request.started_at = time.time() + pending_request.response_resource_progress(response_resource) elif self.resource_strategy == Link.ACCEPT_NONE: pass elif self.resource_strategy == Link.ACCEPT_APP: if self.callbacks.resource != None: @@ -1301,7 +1316,9 @@ class RequestReceipt(): RECEIVING = 0x03 READY = 0x04 - def __init__(self, link, packet_receipt = None, resource = None, response_callback = None, failed_callback = None, progress_callback = None, timeout = None, request_size = None): + def __init__(self, link, packet_receipt=None, resource=None, response_callback=None, failed_callback=None, + progress_callback=None, timeout=None, request_size=None, max_response_size=None): + self.packet_receipt = packet_receipt self.resource = resource self.started_at = None @@ -1328,6 +1345,7 @@ class RequestReceipt(): self.progress = 0 self.concluded_at = None self.response_concluded_at = None + self.max_response_size = max_response_size if timeout != None: self.timeout = timeout @@ -1358,8 +1376,7 @@ class RequestReceipt(): self.link.pending_requests.remove(self) if self.callbacks.failed != None: - try: - self.callbacks.failed(self) + try: self.callbacks.failed(self) except Exception as e: RNS.log("Error while executing request failed callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR) @@ -1382,6 +1399,16 @@ class RequestReceipt(): try: self.callbacks.failed(self) except Exception as e: RNS.log("Error while executing request timed out callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR) + def response_rejected(self): + if self in self.link.pending_requests and self.status == RequestReceipt.DELIVERED: + self.status = RequestReceipt.FAILED + self.concluded_at = time.time() + self.link.pending_requests.remove(self) + + if self.callbacks.failed != None: + try: self.callbacks.failed(self) + except Exception as e: RNS.log("Error while executing request timed out callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR) + def response_resource_progress(self, resource): if resource != None: if not self.status == RequestReceipt.FAILED: