This is just a quick post of my thoughts going over some findings from applications with regards to cold path request and link establishment timeouts for slow interfaces
One issue is that many applications (MeshChat, NomadNet, and MeshChatX, inherited from MeshChat) set their own timers when it comes to waiting for a path response and link to establish for the first hop.
The first thing here is: Don't use timers unless they are absolutely necessary. The Reticulum API is built to handle all of this happening asynchronously. But when you have to use timers, like say for Nomad page downloading, this is my recommendation:
In MeshChat, the link and path establishment timeout are both pinned to 15 seconds for accessing Nomad pages for example
Fixed timers like these will always fail over slow interfaces. Consider a packet radio link where the net throughput is 125 bits per second or super low LoRa CF/SF settings. These are all valid Reticulum interfaces.
At a 15 second path request timeout, a full round-trip for one path request will take 234 bytes and a little more with an IFAC.
At 125 bits per second, this is not possible to achieve in a 15 second window. At 250 bps, it just barely fits in the fixed timer with no margin at all.
So what is a better way of doing things?
Get the lowest advertised bitrate of an online interface, clamped to 50 bps, and compute how long it would take using an MTU to get the first cold path request timeout.
The drawback is that a instance with mixed interfaces (Some slow, some fast) will always wait that ceiling for a first cold path request. This is unavoidable because we don't know what itnerface it's sitting behind. But the alternative is that path requests fail always every time.
I put an example of this as a PR into MeshChatX which you can find the code for here, in path_utils
https://github.com/RFnexus/MeshChatX/commit/f785e6a896205c85ecbfa0d054a04c9fc2b81d10
The simplest form of this looks like:
def path_response_window(destination_hash, reticulum=None):
if reticulum is None:
reticulum = RNS.Reticulum.get_instance()
window = reticulum.get_first_hop_timeout(destination_hash)
bitrate = slowest_online_bitrate(reticulum)
if bitrate:
window = max(
window,
2 * (PATH_EXCHANGE_BYTES * 8 / max(bitrate, MIN_WINDOW_BITRATE)) + 10,
)
return max(window, RNS.Transport.PATH_REQUEST_TIMEOUT)
We compute the path request timeout based on the actual MTU it takes over the air.
The next is link establishment timeouts. Every application should use the timeout reported by Reticulum versus it's own timer. Reticulum already computes a timeout for every link it creates with link.establishment_timeout but I don't think the API has this in the documentation anywhere
No application should use a flat timer for this. It should always use what is reported by RNS, and my suggestion is to add a little bit of margin to it, maybe +3 or +6 seconds.
timeout = getattr(link, "establishment_timeout", None)
You should also always use RNS.Reticulum.get_instance().get_first_hop_timeout() instead of RNS.Transport.first_hop_timeout() because if your app is a client of a shared RNSD instance it will just use its local socket timeout and not the interfaces.
Those are all my thoughts for now, there may even be a better way of doing this and I would like to hear some feedback or suggestions on what the best patterns are when considering very slow interfaces which a lot of stock RNS/LXMF applications struggle with.