diff --git a/cmd/ingestor/decoder.go b/cmd/ingestor/decoder.go index 708d5b40..78cc4f71 100644 --- a/cmd/ingestor/decoder.go +++ b/cmd/ingestor/decoder.go @@ -572,6 +572,21 @@ func DecodePacket(hexString string, channelKeys map[string]string) (*DecodedPack payloadBuf := buf[offset:] payload := decodePayload(header.PayloadType, payloadBuf, channelKeys) + // TRACE packets store hop IDs in the payload (buf[9:]) rather than the header + // path field. The header path byte still encodes hashSize in bits 6-7, which + // we use to split the payload path data into individual hop prefixes. + if header.PayloadType == PayloadTRACE && payload.PathData != "" { + pathBytes, err := hex.DecodeString(payload.PathData) + if err == nil && path.HashSize > 0 { + hops := make([]string, 0, len(pathBytes)/path.HashSize) + for i := 0; i+path.HashSize <= len(pathBytes); i += path.HashSize { + hops = append(hops, strings.ToUpper(hex.EncodeToString(pathBytes[i:i+path.HashSize]))) + } + path.Hops = hops + path.HashCount = len(hops) + } + } + return &DecodedPacket{ Header: header, TransportCodes: tc, diff --git a/cmd/ingestor/decoder_test.go b/cmd/ingestor/decoder_test.go index 51ae9896..1b60bc94 100644 --- a/cmd/ingestor/decoder_test.go +++ b/cmd/ingestor/decoder_test.go @@ -564,6 +564,31 @@ func TestDecodeTraceValid(t *testing.T) { } } +func TestDecodeTracePathParsing(t *testing.T) { + // Packet from issue #276: 260001807dca00000000007d547d + // Path byte 0x00 → hashSize=1, hops in payload at buf[9:] = 7d 54 7d + // Expected path: ["7D", "54", "7D"] + pkt, err := DecodePacket("260001807dca00000000007d547d", nil) + if err != nil { + t.Fatalf("DecodePacket error: %v", err) + } + if pkt.Payload.Type != "TRACE" { + t.Errorf("payload type=%s, want TRACE", pkt.Payload.Type) + } + want := []string{"7D", "54", "7D"} + if len(pkt.Path.Hops) != len(want) { + t.Fatalf("hops=%v, want %v", pkt.Path.Hops, want) + } + for i, h := range want { + if pkt.Path.Hops[i] != h { + t.Errorf("hops[%d]=%s, want %s", i, pkt.Path.Hops[i], h) + } + } + if pkt.Path.HashCount != 3 { + t.Errorf("hashCount=%d, want 3", pkt.Path.HashCount) + } +} + func TestDecodeAdvertShort(t *testing.T) { p := decodeAdvert(make([]byte, 50)) if p.Error != "too short for advert" { diff --git a/cmd/server/decoder.go b/cmd/server/decoder.go index 8058e022..c67fa9e2 100644 --- a/cmd/server/decoder.go +++ b/cmd/server/decoder.go @@ -373,6 +373,21 @@ func DecodePacket(hexString string) (*DecodedPacket, error) { payloadBuf := buf[offset:] payload := decodePayload(header.PayloadType, payloadBuf) + // TRACE packets store hop IDs in the payload (buf[9:]) rather than the header + // path field. The header path byte still encodes hashSize in bits 6-7, which + // we use to split the payload path data into individual hop prefixes. + if header.PayloadType == PayloadTRACE && payload.PathData != "" { + pathBytes, err := hex.DecodeString(payload.PathData) + if err == nil && path.HashSize > 0 { + hops := make([]string, 0, len(pathBytes)/path.HashSize) + for i := 0; i+path.HashSize <= len(pathBytes); i += path.HashSize { + hops = append(hops, strings.ToUpper(hex.EncodeToString(pathBytes[i:i+path.HashSize]))) + } + path.Hops = hops + path.HashCount = len(hops) + } + } + return &DecodedPacket{ Header: header, TransportCodes: tc,