fix: trace packet paths

closes #86
This commit is contained in:
Enot (ded) Skelly
2026-07-23 09:20:00 -07:00
parent 8b5e1853b1
commit 7a58a07bbd
2 changed files with 51 additions and 4 deletions
+37 -2
View File
@@ -323,6 +323,20 @@ func (s *Store) GetPacket(ctx context.Context, packetHash []byte) (*api.Packet,
}
p.TransportCodes = tc
}
// TRACE payloads repurpose the per-observation Path field to carry per-hop SNR bytes
// rather than hashes (see internal/ingest/packet.go), so per-observation PathBytes
// can't be resolved the normal way for TRACE. The actual resolvable path is the
// trace payload's own embedded PathHashes -- constant for this packet hash, so
// compute it once rather than per observation.
var traceRawHashes [][]byte
if row.PayloadType == int16(meshcore.PayloadTypeTrace) {
if trace, err := meshcore.TraceFromBytes(row.RawPayload); err == nil {
hashSize := int(trace.PathHashSize())
for i := 0; i+hashSize <= len(trace.PathHashes); i += hashSize {
traceRawHashes = append(traceRawHashes, trace.PathHashes[i:i+hashSize])
}
}
}
for _, v := range obsRows {
obs := api.PacketObservationDetail{
ID: v.ID,
@@ -342,7 +356,16 @@ func (s *Store) GetPacket(ctx context.Context, packetHash []byte) (*api.Packet,
prop := int32(v.HeardAt.Time.Sub(minHeardAt).Milliseconds())
obs.PropagationTimeMs = &prop
resolvedPath := []api.ResolvedHop{}
if v.PathBytes != nil && v.HashSize > 0 {
if row.PayloadType == int16(meshcore.PayloadTypeTrace) {
if len(traceRawHashes) > 0 {
resolved, err := s.ResolvePathHashes(ctx, v.Iata, traceRawHashes)
if err != nil {
log.Printf("store: path resolution failed for observation %d: %v", v.ID, err)
} else {
resolvedPath = api.BuildResolvedPath(traceRawHashes, resolved)
}
}
} else if v.PathBytes != nil && v.HashSize > 0 {
hashSize := int(v.HashSize)
hashes := make([][]byte, 0, len(v.PathBytes)/hashSize)
for i := 0; i+hashSize <= len(v.PathBytes); i += hashSize {
@@ -356,7 +379,19 @@ func (s *Store) GetPacket(ctx context.Context, packetHash []byte) (*api.Packet,
}
}
obs.ResolvedPath = resolvedPath
if v.PathBytes != nil {
if row.PayloadType == int16(meshcore.PayloadTypeTrace) && len(traceRawHashes) > 0 {
// Swap in the trace's own path hashes so PathData's hop-block split (driven by
// pathBytes + hashSize) lines up 1:1 with resolvedPath -- the raw SNR bytes
// from the physical Path field don't chunk into the same hop count.
var traceHashBytes []byte
for _, h := range traceRawHashes {
traceHashBytes = append(traceHashBytes, h...)
}
pb := hex.EncodeToString(traceHashBytes)
obs.PathBytes = &pb
obs.PathLength.HashSize = int16(len(traceRawHashes[0]))
obs.PathLength.HopCount = int16(len(traceRawHashes))
} else if v.PathBytes != nil {
pb := hex.EncodeToString(v.PathBytes)
obs.PathBytes = &pb
}
+14 -2
View File
@@ -336,6 +336,10 @@ func (w *Worker) handlePacket(ctx context.Context, iata, pubkeyHex string, raw [
originPubkey := []byte(nil)
var parsedPayload json.RawMessage
var traceTag []byte
// For PayloadTypeTrace, packet.Path holds one SNR byte per hop (not hashes -- see
// below), so the "physical route" hashes for resolvedPath/known-route purposes come
// instead from the TRACE payload's own embedded PathHashes (the path being probed).
var traceRawHashes [][]byte
switch packet.PayloadType() {
case meshcore.PayloadTypeGrpTxt:
@@ -515,6 +519,7 @@ func (w *Worker) handlePacket(ctx context.Context, iata, pubkeyHex string, raw [
hashes = append(hashes, hex.EncodeToString(h))
rawHashes = append(rawHashes, h)
}
traceRawHashes = rawHashes
// SNR values are in packet.Path, one signed int8 per consumed hop
snrValues := make([]float32, 0, len(packet.Path))
for _, b := range packet.Path {
@@ -761,7 +766,15 @@ func (w *Worker) handlePacket(ctx context.Context, iata, pubkeyHex string, raw [
}
}
resolved, err := w.db.ResolvePathHashes(ctx, iata, packet.PathHashes())
// packet.PathHashes() reads packet.Path as hash-sized chunks, which is only true for
// ordinary flood/direct-routed packets. TRACE repurposes packet.Path to carry one SNR
// byte per hop instead, so for TRACE we resolve against the trace payload's own
// PathHashes (the path being probed) rather than treating SNR bytes as hashes.
hashes := packet.PathHashes()
if packet.PayloadType() == meshcore.PayloadTypeTrace {
hashes = traceRawHashes
}
resolved, err := w.db.ResolvePathHashes(ctx, iata, hashes)
if err != nil {
log.Printf("ingest[%s]: path resolution failed: %v", w.cfg.BrokerName, err)
}
@@ -771,7 +784,6 @@ func (w *Worker) handlePacket(ctx context.Context, iata, pubkeyHex string, raw [
resolvedIDs = append(resolvedIDs, e.NodeID)
}
}
hashes := packet.PathHashes()
if len(hashes) > 0 && resolved != nil {
allHigh := true
nodeIDs := make([]uuid.UUID, 0, len(hashes))