Apache-2.0 does not require per-file headers, but a file lifted out of the tree carried no marking at all. One comment line, first (or right after a shebang), on every .py under meshterm/, tests/, packaging/ and scripts/, plus the extensionless SPI bridge; tests/test_spdx.py keeps a new file from landing without one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
83 lines
3.9 KiB
Python
83 lines
3.9 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
"""The arithmetic a trace is sized and flagged by.
|
|
|
|
Two pure functions and the four constants behind them: how a per-hop path-hash width is
|
|
encoded into the trace ``flags`` byte, and how long to wait for a trace's reply given the
|
|
route it has to walk. Both are protocol arithmetic with no device, no persistence and no
|
|
rendering, and :class:`~meshterm.core.connection.MeshCoreDevice` needs them on the send path —
|
|
they live here so ``core`` never has to reach up into ``services`` for them. The trace
|
|
*runner* (:mod:`meshterm.services.trace_runner`) re-exports both under their own names, which
|
|
is where every other caller still reads them from.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
def path_hash_flags(width_bytes: int) -> int | None:
|
|
"""Return the trace ``flags`` value that encodes a per-hop path-hash width.
|
|
|
|
The trace subsystem encodes the hash size as ``1 << (flags & 3)`` on both the
|
|
send and receive sides (``send_trace`` and the ``TRACE_DATA`` reader), so only
|
|
widths of 1, 2, 4, or 8 bytes are representable. This is independent of, and
|
|
differs from, the ``mode = size - 1`` encoding used for *contact routing*
|
|
(``out_path_hash_mode``).
|
|
|
|
Args:
|
|
width_bytes: Path-hash width in bytes.
|
|
|
|
Returns:
|
|
The flags value (the exponent ``s``), or ``None`` if the width is not a
|
|
representable power of two.
|
|
"""
|
|
for s in range(4):
|
|
if (1 << s) == width_bytes:
|
|
return s
|
|
return None
|
|
|
|
|
|
#: Fixed overhead in a trace's reply-wait budget, in seconds: our own transmit, the far
|
|
#: endpoint's turnaround, and our receive-side decode — the cost that doesn't grow with
|
|
#: the route.
|
|
TRACE_TIMEOUT_BASE_S = 4.0
|
|
#: Per-hop allowance added to the budget, in seconds. Each entry in the walked path is one
|
|
#: relay transmission, and this covers its packet airtime, the repeater's processing, and
|
|
#: the randomised transmit backoff MeshCore adds so relays don't collide.
|
|
TRACE_TIMEOUT_PER_HOP_S = 1.6
|
|
#: Upper bound on the reply-wait, in seconds, so a route that never comes home still
|
|
#: surrenders the trace (and the session) in bounded time rather than scaling without end.
|
|
TRACE_TIMEOUT_CEILING_S = 30.0
|
|
#: Fallback budget, in seconds, when the hop count can't be known ahead of the send — a
|
|
#: path-less flood to a contact we hold no route for. This is the historical flat value,
|
|
#: kept for exactly the case where we can't size the walk.
|
|
TRACE_TIMEOUT_FLOOD_S = 10.0
|
|
|
|
|
|
def trace_timeout(hop_count: int) -> float:
|
|
"""Return the reply-wait budget, in seconds, for a trace walking ``hop_count`` hops.
|
|
|
|
A trace is a *single* packet that has to travel the whole path — out to the far hop
|
|
and back over the mirrored return leg — before its reply reaches us, so the wait has
|
|
to grow with the route. A flat budget sized for a neighbour cuts a long walk off
|
|
mid-flight: the packet completes the circuit on the mesh, but our wait has already
|
|
expired, so we log a phantom "no reply" for a route that actually worked. The budget
|
|
is a fixed base (:data:`TRACE_TIMEOUT_BASE_S`) plus a per-hop allowance
|
|
(:data:`TRACE_TIMEOUT_PER_HOP_S`) for each relay transmission, capped at
|
|
:data:`TRACE_TIMEOUT_CEILING_S`.
|
|
|
|
``hop_count`` is the number of hops in the *transmitted* path, which already includes
|
|
the mirrored return leg (see
|
|
:meth:`~meshterm.core.connection.MeshCoreDevice._trace_path_to_contact`), so it maps
|
|
one-to-one onto relay transmissions and must not be doubled here.
|
|
|
|
Args:
|
|
hop_count: Hops in the walked path (return leg included). ``0`` (or less) means the
|
|
count is unknown — a path-less flood — and yields :data:`TRACE_TIMEOUT_FLOOD_S`.
|
|
|
|
Returns:
|
|
The number of seconds to wait for the trace reply.
|
|
"""
|
|
if hop_count <= 0:
|
|
return TRACE_TIMEOUT_FLOOD_S
|
|
budget = TRACE_TIMEOUT_BASE_S + TRACE_TIMEOUT_PER_HOP_S * hop_count
|
|
return min(budget, TRACE_TIMEOUT_CEILING_S)
|