RF-to-APRS-IS path does not suppress locally repeated packets #294

Closed
opened 2026-10-05 05:57:55 +02:00 by adam · 3 comments
Owner

Problem

The RF-to-APRS-IS path does not consult the existing 25-second duplicate cache.
The cache is currently used only by the digipeater path.

As a result, one local iGate can upload the same RF packet more than once when
it first hears the original transmission and then hears the same packet again
after a digipeater has changed the path.

Example of one information field arriving through two paths:

F5ZQC-10>APLRG1,WIDE1-1:!L8f?,Nn&"#  GLoRa APRS Batt=4.27V
F5ZQC-10>APLRG1,F4MLV-10*:!L8f?,Nn&"#  GLoRa APRS Batt=4.27V

The source and information field are identical; only the RF path differs.
Both copies are currently eligible for upload by the same iGate.

Cause

The main loop sends a received packet to APRS-IS before passing it to the
digipeater:

APRS_IS_Utils::processLoRaPacket(packet);
DIGI_Utils::processLoRaPacket(packet);

STATION_Utils::isIn25SegHashBuffer() is called only from the digipeater
branch. APRS_IS_Utils::processLoRaPacket() uploads without consulting a
local duplicate cache.

Moving the existing check in front of both consumers would not solve the
problem: whichever consumer checks first would prevent the other destination
from processing the first valid reception.

Expected behavior

For one local node and one duplicate window:

  • the first valid reception may be uploaded once to APRS-IS;
  • the first relayable reception may be repeated once by the digipeater;
  • a later copy with a changed RF path must not be uploaded or repeated again
    by that same node;
  • independent iGates remain independent and may each upload their own copy.

Proposed design

Use one bounded, expiring cache whose entries contain:

struct PacketDedupEntry {
    uint64_t fingerprint;
    uint32_t firstSeenMs;
    uint8_t destinations;
};

constexpr uint8_t DEDUP_DIGI   = 1 << 0;
constexpr uint8_t DEDUP_APRSIS = 1 << 1;

The fingerprint should be based on:

original source + exact information field

The RF path must not be included, and the information field must not be
trimmed. A single atomic-style operation such as
claimDestination(fingerprint, destination) should return true only the first
time that destination claims the packet.

The APRS-IS claim should happen immediately before the effective upload, after
local message/query handling. The digipeater claim should happen only after
the packet has been found relayable, immediately before queueing the RF relay.

Tested implementation

A field-tested implementation using a 64-bit FNV-1a fingerprint, a 25-second
window, a bounded cache and per-destination bits is available here:

https://github.com/moricef/LoRa_APRS_iGate/commit/b1f96425651975813c277b4d18b550285ed29cae

That fork also carries RXT, so its fingerprint removes the RF-only RXT trailer
before hashing. The generic upstream fix does not otherwise depend on RXT.

The implementation includes host tests for independent destination claims,
expiry, capacity bounds, exact whitespace preservation and millis()
wraparound. Field logs confirm that the direct packet is uploaded and the
returned digipeated copy is reported as locally skipped.

## Problem The RF-to-APRS-IS path does not consult the existing 25-second duplicate cache. The cache is currently used only by the digipeater path. As a result, one local iGate can upload the same RF packet more than once when it first hears the original transmission and then hears the same packet again after a digipeater has changed the path. Example of one information field arriving through two paths: ```text F5ZQC-10>APLRG1,WIDE1-1:!L8f?,Nn&"# GLoRa APRS Batt=4.27V F5ZQC-10>APLRG1,F4MLV-10*:!L8f?,Nn&"# GLoRa APRS Batt=4.27V ``` The source and information field are identical; only the RF path differs. Both copies are currently eligible for upload by the same iGate. ## Cause The main loop sends a received packet to APRS-IS before passing it to the digipeater: ```cpp APRS_IS_Utils::processLoRaPacket(packet); DIGI_Utils::processLoRaPacket(packet); ``` `STATION_Utils::isIn25SegHashBuffer()` is called only from the digipeater branch. `APRS_IS_Utils::processLoRaPacket()` uploads without consulting a local duplicate cache. Moving the existing check in front of both consumers would not solve the problem: whichever consumer checks first would prevent the other destination from processing the first valid reception. ## Expected behavior For one local node and one duplicate window: - the first valid reception may be uploaded once to APRS-IS; - the first relayable reception may be repeated once by the digipeater; - a later copy with a changed RF path must not be uploaded or repeated again by that same node; - independent iGates remain independent and may each upload their own copy. ## Proposed design Use one bounded, expiring cache whose entries contain: ```cpp struct PacketDedupEntry { uint64_t fingerprint; uint32_t firstSeenMs; uint8_t destinations; }; constexpr uint8_t DEDUP_DIGI = 1 << 0; constexpr uint8_t DEDUP_APRSIS = 1 << 1; ``` The fingerprint should be based on: ```text original source + exact information field ``` The RF path must not be included, and the information field must not be trimmed. A single atomic-style operation such as `claimDestination(fingerprint, destination)` should return true only the first time that destination claims the packet. The APRS-IS claim should happen immediately before the effective upload, after local message/query handling. The digipeater claim should happen only after the packet has been found relayable, immediately before queueing the RF relay. ## Tested implementation A field-tested implementation using a 64-bit FNV-1a fingerprint, a 25-second window, a bounded cache and per-destination bits is available here: https://github.com/moricef/LoRa_APRS_iGate/commit/b1f96425651975813c277b4d18b550285ed29cae That fork also carries RXT, so its fingerprint removes the RF-only RXT trailer before hashing. The generic upstream fix does not otherwise depend on RXT. The implementation includes host tests for independent destination claims, expiry, capacity bounds, exact whitespace preservation and `millis()` wraparound. Field logs confirm that the direct packet is uploaded and the returned digipeated copy is reported as locally skipped. <!-- github-sync-id:5595387966 -->
adam closed this issue 2026-10-05 05:57:55 +02:00
Author
Owner

Hi, I asked the same question a few years ago and I got as response:

iGates should upload everything they hear, EVERYTHING.

APRS-IS has many ways to work with this tons of packets being uploaded and the deduplication is done on their servers.

Only Digipeaters should do the filtering of those packets being repeated.

PS: the reason is that igates will upload all and this will help to have all the path and info to analyze packets also

Hi, I asked the same question a few years ago and I got as response: iGates should upload everything they hear, EVERYTHING. APRS-IS has many ways to work with this tons of packets being uploaded and the deduplication is done on their servers. Only Digipeaters should do the filtering of those packets being repeated. PS: the reason is that igates will upload all and this will help to have all the path and info to analyze packets also <!-- github-comment-id:5856912337 -->
Author
Owner

Hi Ricardo,

Thank you for the clarification. I misunderstood the intended division of responsibility between IGates and APRS-IS duplicate filtering. After reviewing the APRS-IS server specification, I agree that IGates should upload every valid RF reception and leave duplicate suppression to the servers.

Sorry for the noise, and thank you again for the explanation. I’m closing this issue.

Hi Ricardo, Thank you for the clarification. I misunderstood the intended division of responsibility between IGates and APRS-IS duplicate filtering. After reviewing the APRS-IS server specification, I agree that IGates should upload every valid RF reception and leave duplicate suppression to the servers. Sorry for the noise, and thank you again for the explanation. I’m closing this issue. <!-- github-comment-id:5857406302 -->
Author
Owner

There is no problem on chasing on this topics! and thanks again on helping with this

I'm currently reviewing the PR you just pushed also

There is no problem on chasing on this topics! and thanks again on helping with this I'm currently reviewing the PR you just pushed also <!-- github-comment-id:5857437735 -->
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: adam/LoRa_APRS_iGate#294