Meshtastic stores well-known channel PSKs as a single byte index, so a
channel configured with "Ag==" was base64-decoded to one byte and handed
straight to AES. That is not just a failure to decode: algorithms.AES()
raises ValueError on a 1 byte key, and get_topic_envelopes only catches
MqttError, so the reader died on the first packet.
Expand single byte PSKs the way the firmware does in Channels::getKey --
copy the default PSK and bump its last byte by index - 1, so index 1 is
the default PSK itself. Index 0 means no encryption and is skipped.
Also validate that decoded keys are 16 or 32 bytes and log the offending
config value instead of letting a bad key reach AES.
* fix: correct traceroute path orientation when building graph edges
A completed traceroute is observed as a response packet, whose from/to are
reversed with respect to the path being traced: `to` is the node that started
the traceroute and `from` is the node that answered. Meshview assembled paths
as [from, *route, to], so both endpoint-adjacent edges were wrong for every
completed traceroute with at least one intermediate hop. Because the false
edges always attach to the endpoints, a busy initiator or gateway accumulated
false direct links to every remote target it ever traced.
Confirmed against TraceRouteModule::alterReceivedProtobuf, which renders a
response as printRoute(r, p.to, p.from, false), against the meshtastic python
CLI, which starts the forward path at p["to"] and the return path at p["from"],
and against real recorded traceroutes where a request's route[] is a prefix of
the matching response's route[].
Add meshview/traceroute.py and use it from /api/edges, /api/traceroute and
graph_traceroute, which had three separate copies of this logic that had all
drifted. /api/traceroute also gains initiator/target so the traceroute page
stops re-deriving the endpoints itself, which is where a fourth copy lived.
While in /api/edges, skip the NODENUM_BROADCAST placeholders that
insertUnknownHops() writes into route[] (they became a pseudo-node hub in the
graph) and guard against the (gateway, gateway) self edge that
graph_traceroute already guarded against.
* fix: do not invent links across unknown hops or unfinished returns
Review of becf4a8 found two remaining path errors.
Dropping NODENUM_BROADCAST from route[] during assembly joined the nodes
either side of it, so A -> B -> ? -> C -> D became A -> B -> C -> D and a
lone unknown hop collapsed into a false direct A -> D. Keep the placeholder
in the path and skip the edges that touch it instead. On a 2660 row sample
this removes 10 links that do not exist.
done=True only means a response was observed, not that it arrived. A gateway
usually reports one still travelling home with route_back holding just the
hops so far, so always appending the initiator claimed a final link that was
never seen. Append it only when the response reached the initiator, which the
spare snr_back entry from SNRonly=isToUs() marks. Note this is rare in
practice: 2 of 303 real responses, since the entry is added by the initiator
and gateways see packets in flight.
The deduplicated unique_forward_paths and unique_reverse_paths carry no
per-observation provenance, so the traceroute page can no longer tell which
of them reached the far endpoint. It now adds only the adjacent endpoint.
* fix: treat a response as proof the forward path reached the target
Winning path selection required return hops, so a response observed before
its first return hop contributed nothing and the page fell back to the
deduplicated reported paths, which cannot tell which of them reached the
target. A response with route=[B,C] and no route_back rendered A > B > C,
losing the target and the last link.
Select winning paths from response observations instead. A response only
exists because the request arrived, whatever its return trip went on to do.
Dropping the non-empty forward_hops guard at the same time means a zero hop
response contributes its real direct link, so a fully direct traceroute and a
completed direct return are no longer excluded from the graph.
Mark reported forward paths that some response reported, so the page can
close those on the target and they deduplicate against the winning path
instead of drawing a shorter copy underneath it.
error_reason sits in a protobuf oneof, so the NONE zero value is written
to the wire and printed verbatim, making successful ACKs look like
errors. Render NONE as "ACK" and anything else as "NAK: <REASON>".
Confirmed against live MQTT traffic (19 NONE, 22 NO_RESPONSE, all with
request_id set).