138 lines
5.0 KiB
Python
138 lines
5.0 KiB
Python
import asyncio
|
|
import os
|
|
import time
|
|
|
|
import RNS
|
|
|
|
|
|
class RNProbeHandler:
|
|
DEFAULT_PROBE_SIZE = 16
|
|
DEFAULT_TIMEOUT = 12
|
|
|
|
def __init__(self, reticulum_instance, identity):
|
|
self.reticulum = reticulum_instance
|
|
self.identity = identity
|
|
|
|
async def probe_destination(
|
|
self,
|
|
destination_hash: bytes,
|
|
full_name: str,
|
|
size: int = DEFAULT_PROBE_SIZE,
|
|
timeout: float | None = None,
|
|
wait: float = 0,
|
|
probes: int = 1,
|
|
):
|
|
try:
|
|
app_name, aspects = RNS.Destination.app_and_aspects_from_name(full_name)
|
|
except Exception as e:
|
|
msg = f"Invalid destination name: {e}"
|
|
raise ValueError(msg)
|
|
|
|
if not RNS.Transport.has_path(destination_hash):
|
|
RNS.Transport.request_path(destination_hash)
|
|
|
|
timeout_after = time.time() + (timeout or self.DEFAULT_TIMEOUT + self.reticulum.get_first_hop_timeout(destination_hash))
|
|
while not RNS.Transport.has_path(destination_hash) and time.time() < timeout_after:
|
|
await asyncio.sleep(0.1)
|
|
|
|
if not RNS.Transport.has_path(destination_hash):
|
|
msg = "Path request timed out"
|
|
raise TimeoutError(msg)
|
|
|
|
server_identity = RNS.Identity.recall(destination_hash)
|
|
request_destination = RNS.Destination(
|
|
server_identity,
|
|
RNS.Destination.OUT,
|
|
RNS.Destination.SINGLE,
|
|
app_name,
|
|
*aspects,
|
|
)
|
|
|
|
results = []
|
|
sent = 0
|
|
|
|
while probes > 0:
|
|
if sent > 0:
|
|
await asyncio.sleep(wait)
|
|
|
|
try:
|
|
probe = RNS.Packet(request_destination, os.urandom(size))
|
|
probe.pack()
|
|
except OSError:
|
|
msg = f"Probe packet size of {len(probe.raw)} bytes exceeds MTU of {RNS.Reticulum.MTU} bytes"
|
|
raise ValueError(msg)
|
|
|
|
receipt = probe.send()
|
|
sent += 1
|
|
|
|
next_hop = self.reticulum.get_next_hop(destination_hash)
|
|
via_str = f" via {RNS.prettyhexrep(next_hop)}" if next_hop else ""
|
|
if_name = self.reticulum.get_next_hop_if_name(destination_hash)
|
|
if_str = f" on {if_name}" if if_name and if_name != "None" else ""
|
|
|
|
timeout_after = time.time() + (timeout or self.DEFAULT_TIMEOUT + self.reticulum.get_first_hop_timeout(destination_hash))
|
|
while receipt.status == RNS.PacketReceipt.SENT and time.time() < timeout_after:
|
|
await asyncio.sleep(0.1)
|
|
|
|
result: dict = {
|
|
"probe_number": sent,
|
|
"size": size,
|
|
"destination": RNS.prettyhexrep(destination_hash),
|
|
"via": via_str,
|
|
"interface": if_str,
|
|
"status": "timeout",
|
|
}
|
|
|
|
if time.time() > timeout_after:
|
|
result["status"] = "timeout"
|
|
elif receipt.status == RNS.PacketReceipt.DELIVERED:
|
|
hops = RNS.Transport.hops_to(destination_hash)
|
|
rtt = receipt.get_rtt()
|
|
|
|
if rtt >= 1:
|
|
rtt_str = f"{round(rtt, 3)} seconds"
|
|
else:
|
|
rtt_str = f"{round(rtt * 1000, 3)} milliseconds"
|
|
|
|
reception_stats = {}
|
|
if self.reticulum.is_connected_to_shared_instance:
|
|
reception_rssi = self.reticulum.get_packet_rssi(receipt.proof_packet.packet_hash)
|
|
reception_snr = self.reticulum.get_packet_snr(receipt.proof_packet.packet_hash)
|
|
reception_q = self.reticulum.get_packet_q(receipt.proof_packet.packet_hash)
|
|
|
|
if reception_rssi is not None:
|
|
reception_stats["rssi"] = reception_rssi
|
|
if reception_snr is not None:
|
|
reception_stats["snr"] = reception_snr
|
|
if reception_q is not None:
|
|
reception_stats["quality"] = reception_q
|
|
elif receipt.proof_packet:
|
|
if receipt.proof_packet.rssi is not None:
|
|
reception_stats["rssi"] = receipt.proof_packet.rssi
|
|
if receipt.proof_packet.snr is not None:
|
|
reception_stats["snr"] = receipt.proof_packet.snr
|
|
|
|
result.update(
|
|
{
|
|
"status": "delivered",
|
|
"hops": hops,
|
|
"rtt": rtt,
|
|
"rtt_string": rtt_str,
|
|
"reception_stats": reception_stats,
|
|
},
|
|
)
|
|
else:
|
|
result["status"] = "failed"
|
|
|
|
results.append(result)
|
|
probes -= 1
|
|
|
|
return {
|
|
"results": results,
|
|
"sent": sent,
|
|
"delivered": sum(1 for r in results if r["status"] == "delivered"),
|
|
"timeouts": sum(1 for r in results if r["status"] == "timeout"),
|
|
"failed": sum(1 for r in results if r["status"] == "failed"),
|
|
}
|
|
|