diff --git a/backend/src/mqtt/client.ts b/backend/src/mqtt/client.ts index fe406ce..8d4178d 100644 --- a/backend/src/mqtt/client.ts +++ b/backend/src/mqtt/client.ts @@ -101,6 +101,33 @@ function isEmptyPacketEnvelope(json: Record, rawHex: string, pa && (payloadLen ?? 0) <= 0; } +/** + * Per-observer packet dedup — prevents relay copies of the same packet from being + * ingested multiple times when they arrive at the same observer with the same hop count. + * Keyed by "packetHash:observerKey:hopCount". Entries expire after 120 seconds. + */ +const seenPackets = new Map(); +const SEEN_PACKETS_MAX = 50_000; +const SEEN_PACKETS_TTL_MS = 120_000; + +function isDuplicatePacket(packetHash: string, observerKey: string, hopCount: number | undefined): boolean { + const now = Date.now(); + // Periodic cleanup + if (seenPackets.size > SEEN_PACKETS_MAX / 2) { + for (const [k, ts] of seenPackets) { + if (now - ts > SEEN_PACKETS_TTL_MS) seenPackets.delete(k); + } + } + const key = `${packetHash}:${observerKey}:${hopCount ?? '?'}`; + if (seenPackets.has(key)) return true; + if (seenPackets.size >= SEEN_PACKETS_MAX) { + const oldest = seenPackets.keys().next().value; + if (oldest !== undefined) seenPackets.delete(oldest); + } + seenPackets.set(key, now); + return false; +} + /** * Dedup map for advert counts — prevents relay copies of the same advert packet * from incrementing the count multiple times. Keyed by decoded message hash. @@ -424,6 +451,10 @@ async function handleMessage(topic: string, rawPayload: Buffer): Promise { const finalHash = decodedHash ?? (json['hash'] as string | undefined) ?? crypto.randomUUID(); + if (isDuplicatePacket(finalHash, observerKey, decodedHops)) { + return; + } + void upsertNode(observerKey, { iata, network }); emitNode(observerKey, { network, observerId: observerKey }); diff --git a/docker-compose.yml b/docker-compose.yml index f6d5db4..d26c8b7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,7 @@ services: timescaledb: image: timescale/timescaledb:latest-pg16 restart: unless-stopped + command: ["postgres", "-c", "max_connections=300"] environment: POSTGRES_DB: ${POSTGRES_DB:-meshcore} POSTGRES_USER: ${POSTGRES_USER:-meshcore}