mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-09-16 08:52:38 +00:00
feat: include source and destination in path data where available
realted to #85
This commit is contained in:
@@ -337,6 +337,48 @@ func (s *Store) GetPacket(ctx context.Context, packetHash []byte) (*api.Packet,
|
||||
}
|
||||
}
|
||||
}
|
||||
// 1-byte source/destination hashes for the payload types that carry a resolvable
|
||||
// endpoint (REQUEST, RESPONSE, TEXT_MESSAGE, PATH, ANON_REQ's destination). Constant
|
||||
// for this packet hash, so parsed once; resolution itself still happens per-observation
|
||||
// below since candidates depend on the observation's IATA, same as the intermediate
|
||||
// hop resolution already does.
|
||||
var sourceHashByte, destHashByte []byte
|
||||
switch row.PayloadType {
|
||||
case int16(meshcore.PayloadTypeAnonReq):
|
||||
if anonReq, err := meshcore.AnonReqFromBytes(row.RawPayload); err == nil {
|
||||
destHashByte = []byte{anonReq.Destination}
|
||||
}
|
||||
case int16(meshcore.PayloadTypeReq):
|
||||
if req, err := meshcore.RequestFromBytes(row.RawPayload); err == nil {
|
||||
sourceHashByte = []byte{req.Source}
|
||||
destHashByte = []byte{req.Destination}
|
||||
}
|
||||
case int16(meshcore.PayloadTypeResponse):
|
||||
if resp, err := meshcore.ResponseFromBytes(row.RawPayload); err == nil {
|
||||
sourceHashByte = []byte{resp.Source}
|
||||
destHashByte = []byte{resp.Destination}
|
||||
}
|
||||
case int16(meshcore.PayloadTypeTxtMsg):
|
||||
if txt, err := meshcore.TextMessageFromBytes(row.RawPayload); err == nil {
|
||||
sourceHashByte = []byte{txt.Source}
|
||||
destHashByte = []byte{txt.Destination}
|
||||
}
|
||||
case int16(meshcore.PayloadTypePath):
|
||||
if path, err := meshcore.PathFromBytes(row.RawPayload); err == nil {
|
||||
sourceHashByte = []byte{path.Source}
|
||||
destHashByte = []byte{path.Destination}
|
||||
}
|
||||
}
|
||||
// ADVERT's source is an exact pubkey match, not ambiguous like the above -- and unlike
|
||||
// them it doesn't depend on IATA, so resolve it once here rather than per observation.
|
||||
var resolvedAdvertSource *api.ResolvedNode
|
||||
if row.PayloadType == int16(meshcore.PayloadTypeAdvert) && row.OriginPubkey != nil {
|
||||
if nodeID, err := s.GetNodeByPubkey(ctx, row.OriginPubkey); err == nil {
|
||||
if nodes, err := s.GetNodesByIDs(ctx, []uuid.UUID{nodeID}); err == nil {
|
||||
resolvedAdvertSource = nodes[nodeID]
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, v := range obsRows {
|
||||
obs := api.PacketObservationDetail{
|
||||
ID: v.ID,
|
||||
@@ -379,6 +421,25 @@ func (s *Store) GetPacket(ctx context.Context, packetHash []byte) (*api.Packet,
|
||||
}
|
||||
}
|
||||
obs.ResolvedPath = resolvedPath
|
||||
if row.PayloadType == int16(meshcore.PayloadTypeAdvert) {
|
||||
hop := api.ResolveExactNode(resolvedAdvertSource)
|
||||
obs.ResolvedSource = &hop
|
||||
} else if len(sourceHashByte) == 1 {
|
||||
if r, err := s.ResolvePathHashes(ctx, v.Iata, [][]byte{sourceHashByte}); err != nil {
|
||||
log.Printf("store: source resolution failed for observation %d: %v", v.ID, err)
|
||||
} else {
|
||||
hop := api.BuildResolvedPath([][]byte{sourceHashByte}, r)[0]
|
||||
obs.ResolvedSource = &hop
|
||||
}
|
||||
}
|
||||
if len(destHashByte) == 1 {
|
||||
if r, err := s.ResolvePathHashes(ctx, v.Iata, [][]byte{destHashByte}); err != nil {
|
||||
log.Printf("store: destination resolution failed for observation %d: %v", v.ID, err)
|
||||
} else {
|
||||
hop := api.BuildResolvedPath([][]byte{destHashByte}, r)[0]
|
||||
obs.ResolvedDestination = &hop
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
@@ -58,6 +58,14 @@ type PacketObservationDetail struct {
|
||||
Radio *PacketRadio `json:"radio,omitempty"`
|
||||
SourceBroker string `json:"sourceBroker"`
|
||||
ResolvedPath []ResolvedHop `json:"resolvedPath"` // per-observation resolved path hashes
|
||||
// ResolvedSource/ResolvedDestination are the packet's endpoints, when the payload type
|
||||
// carries a resolvable one: an exact match for ADVERT's full pubkey, an ambiguous
|
||||
// hash-prefix match (like intermediate hops) for TEXT_MESSAGE/PATH/ANON_REQ's 1-byte
|
||||
// source/destination hashes. Nil when the payload type doesn't carry one at all (e.g.
|
||||
// GRP_TXT/GRP_DATA/TRACE aren't node-to-node addressed) -- see BuildResolvedPath and
|
||||
// ResolveExactNode for how each is built.
|
||||
ResolvedSource *ResolvedHop `json:"resolvedSource,omitempty"`
|
||||
ResolvedDestination *ResolvedHop `json:"resolvedDestination,omitempty"`
|
||||
}
|
||||
|
||||
// PacketRadio holds the radio settings copied from the observer at observation time.
|
||||
@@ -278,3 +286,14 @@ func BuildResolvedPath(hashes [][]byte, resolved map[string][]ResolvedPathEntry)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// ResolveExactNode builds a ResolvedHop for an endpoint resolved by an exact, unambiguous
|
||||
// key -- e.g. an ADVERT's full public key already resolved to a single node -- as opposed
|
||||
// to BuildResolvedPath's hash-prefix matching, which can be ambiguous. Confidence is always
|
||||
// "high" when a node was found, "none" when it wasn't (unknown node, or lookup failed).
|
||||
func ResolveExactNode(node *ResolvedNode) ResolvedHop {
|
||||
if node == nil {
|
||||
return ResolvedHop{Confidence: "none", Nodes: []ResolvedNode{}}
|
||||
}
|
||||
return ResolvedHop{Confidence: "high", Nodes: []ResolvedNode{*node}}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,10 @@ type DB interface {
|
||||
// the observer's own node may not exist until it has advertised.
|
||||
GetNodeByPubkey(ctx context.Context, pubkey []byte) (uuid.UUID, error)
|
||||
|
||||
// GetNodesByIDs returns resolved node details (name, pubkey, coords) for a set of node
|
||||
// IDs. Used with GetNodeByPubkey to resolve an ADVERT's exact-match source endpoint.
|
||||
GetNodesByIDs(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]*api.ResolvedNode, error)
|
||||
|
||||
// UpsertNodeIATA upserts a node_iatas row.
|
||||
UpsertNodeIATA(ctx context.Context, nodeID uuid.UUID, iata string) error
|
||||
|
||||
|
||||
@@ -94,6 +94,10 @@ type packetObservationEvent struct {
|
||||
} `json:"pathLength"`
|
||||
PropagationTimeMs int32 `json:"propagationTimeMs"`
|
||||
ResolvedPath []api.ResolvedHop `json:"resolvedPath"` // only present in the resolvePath-opted-in variant; see hub.Event.PayloadResolved
|
||||
// ResolvedSource/ResolvedDestination mirror api.PacketObservationDetail's fields of
|
||||
// the same name -- nil when this payload type has no resolvable endpoint.
|
||||
ResolvedSource *api.ResolvedHop `json:"resolvedSource,omitempty"`
|
||||
ResolvedDestination *api.ResolvedHop `json:"resolvedDestination,omitempty"`
|
||||
} `json:"observation"`
|
||||
}
|
||||
|
||||
@@ -340,6 +344,10 @@ func (w *Worker) handlePacket(ctx context.Context, iata, pubkeyHex string, raw [
|
||||
// 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
|
||||
// 1-byte source/destination hashes for ambiguous prefix resolution (REQUEST, RESPONSE,
|
||||
// TEXT_MESSAGE, PATH, ANON_REQ's destination). GRP_TXT/GRP_DATA/TRACE have no such
|
||||
// fields and are left nil.
|
||||
var sourceHashByte, destHashByte []byte
|
||||
|
||||
switch packet.PayloadType() {
|
||||
case meshcore.PayloadTypeGrpTxt:
|
||||
@@ -438,6 +446,7 @@ func (w *Worker) handlePacket(ctx context.Context, iata, pubkeyHex string, raw [
|
||||
anonReq, err := meshcore.AnonReqFromBytes(packet.Payload)
|
||||
if err == nil {
|
||||
originPubkey = anonReq.EphemeralPubKey[:]
|
||||
destHashByte = []byte{anonReq.Destination}
|
||||
par := parsedAnonReq{
|
||||
Raw: hex.EncodeToString(packet.Payload),
|
||||
Type: "ANON_REQUEST",
|
||||
@@ -450,6 +459,8 @@ func (w *Worker) handlePacket(ctx context.Context, iata, pubkeyHex string, raw [
|
||||
case meshcore.PayloadTypeReq:
|
||||
req, err := meshcore.RequestFromBytes(packet.Payload)
|
||||
if err == nil {
|
||||
sourceHashByte = []byte{req.Source}
|
||||
destHashByte = []byte{req.Destination}
|
||||
pe := parsedEnvelope{
|
||||
Raw: hex.EncodeToString(packet.Payload),
|
||||
Type: "REQUEST",
|
||||
@@ -465,6 +476,8 @@ func (w *Worker) handlePacket(ctx context.Context, iata, pubkeyHex string, raw [
|
||||
case meshcore.PayloadTypeResponse:
|
||||
resp, err := meshcore.ResponseFromBytes(packet.Payload)
|
||||
if err == nil {
|
||||
sourceHashByte = []byte{resp.Source}
|
||||
destHashByte = []byte{resp.Destination}
|
||||
pe := parsedEnvelope{
|
||||
Raw: hex.EncodeToString(packet.Payload),
|
||||
Type: "RESPONSE",
|
||||
@@ -480,6 +493,8 @@ func (w *Worker) handlePacket(ctx context.Context, iata, pubkeyHex string, raw [
|
||||
case meshcore.PayloadTypeTxtMsg:
|
||||
txt, err := meshcore.TextMessageFromBytes(packet.Payload)
|
||||
if err == nil {
|
||||
sourceHashByte = []byte{txt.Source}
|
||||
destHashByte = []byte{txt.Destination}
|
||||
pe := parsedEnvelope{
|
||||
Raw: hex.EncodeToString(packet.Payload),
|
||||
Type: "TEXT_MESSAGE",
|
||||
@@ -495,6 +510,8 @@ func (w *Worker) handlePacket(ctx context.Context, iata, pubkeyHex string, raw [
|
||||
case meshcore.PayloadTypePath:
|
||||
path, err := meshcore.PathFromBytes(packet.Payload)
|
||||
if err == nil {
|
||||
sourceHashByte = []byte{path.Source}
|
||||
destHashByte = []byte{path.Destination}
|
||||
pe := parsedEnvelope{
|
||||
Raw: hex.EncodeToString(packet.Payload),
|
||||
Type: "PATH",
|
||||
@@ -805,6 +822,30 @@ func (w *Worker) handlePacket(ctx context.Context, iata, pubkeyHex string, raw [
|
||||
}
|
||||
}
|
||||
w.runCapabilityDetection(ctx, packet.PayloadType(), packet.PathHashSize(), resolvedIDs)
|
||||
|
||||
var resolvedSource, resolvedDestination *api.ResolvedHop
|
||||
if packet.PayloadType() == meshcore.PayloadTypeAdvert && originPubkey != nil {
|
||||
// Exact match: ADVERT carries the sender's real identity pubkey, not a
|
||||
// short ambiguous hash prefix like the other resolvable payload types.
|
||||
if nodeID, err := w.db.GetNodeByPubkey(ctx, originPubkey); err == nil {
|
||||
if nodes, err := w.db.GetNodesByIDs(ctx, []uuid.UUID{nodeID}); err == nil {
|
||||
hop := api.ResolveExactNode(nodes[nodeID])
|
||||
resolvedSource = &hop
|
||||
}
|
||||
}
|
||||
} else if len(sourceHashByte) == 1 {
|
||||
if r, err := w.db.ResolvePathHashes(ctx, iata, [][]byte{sourceHashByte}); err == nil {
|
||||
hop := api.BuildResolvedPath([][]byte{sourceHashByte}, r)[0]
|
||||
resolvedSource = &hop
|
||||
}
|
||||
}
|
||||
if len(destHashByte) == 1 {
|
||||
if r, err := w.db.ResolvePathHashes(ctx, iata, [][]byte{destHashByte}); err == nil {
|
||||
hop := api.BuildResolvedPath([][]byte{destHashByte}, r)[0]
|
||||
resolvedDestination = &hop
|
||||
}
|
||||
}
|
||||
|
||||
if inserted {
|
||||
w.handlePayloadTypeSideEffects(ctx, packet, iata, packetHash[:], radio, scopeID, matchedScope, pubkeyBytes, float32(parseNumber(envelope.SNR)))
|
||||
evt := packetObservationEvent{}
|
||||
@@ -836,6 +877,8 @@ func (w *Worker) handlePacket(ctx context.Context, iata, pubkeyHex string, raw [
|
||||
evt.Packet.Scope = matchedScope
|
||||
}
|
||||
resolvedPath := api.BuildResolvedPath(hashes, resolved)
|
||||
evt.Observation.ResolvedSource = resolvedSource
|
||||
evt.Observation.ResolvedDestination = resolvedDestination
|
||||
w.broadcastPacketObservation(iata, packet.PayloadType(), evt, resolvedPath)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user