fix(ingest): honor explicit zero locations in adverts (#139)

This commit is contained in:
n30nex
2026-09-18 06:17:44 -04:00
committed by GitHub
parent b44769bef8
commit ce8c6cdf8b
4 changed files with 130 additions and 3 deletions
+71
View File
@@ -0,0 +1,71 @@
// Copyright 2026 Beacon Contributors
// SPDX-License-Identifier: AGPL-3.0-or-later
package db
import (
"context"
"os"
"testing"
"time"
sqlc "github.com/MeshCore-Beacon/beacon-server/db/sqlc"
"github.com/MeshCore-Beacon/beacon-server/internal/ingest"
"github.com/jackc/pgx/v5"
)
func TestNodeLocationResetPostgres(t *testing.T) {
dsn := os.Getenv("BEACON_TEST_POSTGRES_DSN")
if dsn == "" {
t.Skip("set BEACON_TEST_POSTGRES_DSN for PostgreSQL regression")
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
conn, err := pgx.Connect(ctx, dsn)
if err != nil {
t.Fatal(err)
}
defer conn.Close(context.Background())
tx, err := conn.Begin(ctx)
if err != nil {
t.Fatal(err)
}
defer tx.Rollback(context.Background())
if _, err := tx.Exec(ctx, "CREATE TEMP TABLE nodes (LIKE public.nodes INCLUDING ALL) ON COMMIT DROP"); err != nil {
t.Fatal(err)
}
store := &Store{q: sqlc.New(tx)}
lat, lon := 45.0, -75.0
params := ingest.UpsertNodeParams{PublicKey: []byte{0x97}, NodeType: 2, Name: "location fixture", Latitude: &lat, Longitude: &lon}
id, err := store.UpsertNode(ctx, params, ingest.RadioSettings{})
if err != nil {
t.Fatal(err)
}
for _, tc := range []struct {
name string
present bool
lat, lon float64
}{
{"omission preserves location", false, 45, -75},
{"explicit reset replaces location", true, 0, 0},
{"omission preserves reset", false, 0, 0},
} {
t.Run(tc.name, func(t *testing.T) {
params.Latitude, params.Longitude = nil, nil
if tc.present {
params.Latitude, params.Longitude = &tc.lat, &tc.lon
}
updatedID, err := store.UpsertNode(ctx, params, ingest.RadioSettings{})
if err != nil || updatedID != id {
t.Fatalf("node identity changed: %v", err)
}
var gotLat, gotLon float64
if err := tx.QueryRow(ctx, "SELECT latitude,longitude FROM nodes WHERE id=$1", id).Scan(&gotLat, &gotLon); err != nil {
t.Fatal(err)
}
if gotLat != tc.lat || gotLon != tc.lon {
t.Fatalf("location = (%g,%g), want (%g,%g)", gotLat, gotLon, tc.lat, tc.lon)
}
})
}
}
+3 -1
View File
@@ -175,6 +175,7 @@ type stubDB struct {
setCapabilityCalls []setCapabilityCall
upsertNodeCalls int
upsertNodeParams UpsertNodeParams
upsertChannelCalls int
upsertChannelHashOnlyCalls int
upsertChannelIATACalls int
@@ -209,8 +210,9 @@ func (s *stubDB) InsertObservation(_ context.Context, _ InsertObservationParams)
return s.observationInserted, nil
}
func (s *stubDB) SetNodeDefaultScope(_ context.Context, _ uuid.UUID, _ int32) error { return nil }
func (s *stubDB) UpsertNode(_ context.Context, _ UpsertNodeParams, _ RadioSettings) (uuid.UUID, error) {
func (s *stubDB) UpsertNode(_ context.Context, params UpsertNodeParams, _ RadioSettings) (uuid.UUID, error) {
s.upsertNodeCalls++
s.upsertNodeParams = params
return uuid.Nil, nil
}
func (s *stubDB) UpsertNodeIATA(_ context.Context, _ uuid.UUID, _ string) error { return nil }
+2 -1
View File
@@ -80,7 +80,8 @@ func (w *Worker) handlePayloadTypeSideEffects(ctx context.Context, packet *meshc
return
}
var lat, lon *float64
if advert.AppData().Lat != 0 || advert.AppData().Lon != 0 {
// Presence, not value: an explicit 0/0 advert resets the stored location.
if advert.Flags()&meshcore.AdvertLatLonMask != 0 {
la := float64(advert.AppData().Lat) / 1e6
lo := float64(advert.AppData().Lon) / 1e6
lat = &la
+54 -1
View File
@@ -6,6 +6,7 @@ package ingest
import (
"context"
"crypto/ed25519"
"encoding/binary"
"encoding/hex"
"encoding/json"
"testing"
@@ -31,6 +32,10 @@ func (k *mapKeys) GetKey(hash []byte) []keystore.Entry {
// buildAdvertPacket signs (or, if tamper is true, signs then mutates) an
// advert payload and wraps it in a minimal Packet with no path (zero-hop).
func buildAdvertPacket(t *testing.T, tamper bool) *meshcore.Packet {
return buildAdvertPacketWithData(t, []byte{meshcore.AdvertTypeRepeater}, tamper)
}
func buildAdvertPacketWithData(t *testing.T, data []byte, tamper bool) *meshcore.Packet {
t.Helper()
pub, priv, err := ed25519.GenerateKey(nil)
if err != nil {
@@ -43,7 +48,7 @@ func buildAdvertPacket(t *testing.T, tamper bool) *meshcore.Packet {
advert := &meshcore.Advert{
PublicKey: id,
Timestamp: 12345,
RawAppData: []byte{meshcore.AdvertTypeRepeater}, // flags byte only, no optional fields
RawAppData: data,
}
advert.Sign(priv)
if tamper {
@@ -61,6 +66,54 @@ func buildAdvertPacket(t *testing.T, tamper bool) *meshcore.Packet {
}
}
func TestAdvertLocationPresence(t *testing.T) {
for _, tc := range []struct {
name string
present, tamper bool
lat, lon int32
}{
{"ordinary location", true, false, 45000000, -75000000},
{"explicit reset", true, false, 0, 0},
{"zero latitude", true, false, 0, -75000000},
{"zero longitude", true, false, 45000000, 0},
{"no location", false, false, 0, 0},
{"tampered reset", true, true, 0, 0},
} {
t.Run(tc.name, func(t *testing.T) {
data := []byte{meshcore.AdvertTypeRepeater}
if tc.present {
// Preserve the wire presence bit even for zero coordinates; the
// library's AppData encoder omits location when both values are zero.
data[0] |= meshcore.AdvertLatLonMask
data = binary.LittleEndian.AppendUint32(data, uint32(tc.lat))
data = binary.LittleEndian.AppendUint32(data, uint32(tc.lon))
}
worker, store := newTestWorker()
packet := buildAdvertPacketWithData(t, data, tc.tamper)
worker.handlePayloadTypeSideEffects(context.Background(), packet, "YOW", []byte{1}, RadioSettings{}, nil, nil, nil, 0)
if tc.tamper {
if store.upsertNodeCalls != 0 {
t.Fatal("invalid signature updated the node")
}
return
}
if store.upsertNodeCalls != 1 {
t.Fatal("signed advert did not update the node")
}
got := store.upsertNodeParams
if !tc.present {
if got.Latitude != nil || got.Longitude != nil {
t.Fatal("absent location became an update")
}
return
}
if got.Latitude == nil || got.Longitude == nil || *got.Latitude != float64(tc.lat)/1e6 || *got.Longitude != float64(tc.lon)/1e6 {
t.Fatal("advertised coordinates did not reach the node update")
}
})
}
}
func TestHandlePayloadTypeSideEffects_Advert_ValidSignature_UpsertsNode(t *testing.T) {
w, db := newTestWorker()
packet := buildAdvertPacket(t, false)