From 945912adc153ac0d4ad8c7ebd1f52a87e9fafc46 Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Sat, 14 Mar 2026 08:45:24 +0100 Subject: [PATCH] [fix](trx-server): guard APRS-IS uplink against history replays Skip APRS packets whose ts_ms is older than 120 seconds. Live RF-decoded packets arrive within milliseconds; history replay items can be up to 24 hours old and must not be re-uploaded to APRS-IS as live traffic. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Stan Grams --- src/trx-server/src/aprsfi.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/trx-server/src/aprsfi.rs b/src/trx-server/src/aprsfi.rs index 6353180..fdbac2d 100644 --- a/src/trx-server/src/aprsfi.rs +++ b/src/trx-server/src/aprsfi.rs @@ -4,6 +4,8 @@ //! APRS-IS IGate uplink — forwards RF-decoded APRS packets to APRS-IS (aprs.fi etc.). +use std::time::{SystemTime, UNIX_EPOCH}; + use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::net::TcpStream; use tokio::sync::broadcast; @@ -209,6 +211,20 @@ pub async fn run_aprsfi_uplink( stats_skipped += 1; continue 'forward; } + // Guard against history replays: skip packets whose timestamp + // indicates they are older than 2 minutes. Live RF-decoded + // packets arrive within milliseconds; history items can be + // up to 24 hours old. + if let Some(ts_ms) = pkt.ts_ms { + let now_ms = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|d| d.as_millis() as i64) + .unwrap_or(0); + if now_ms.saturating_sub(ts_ms) > 120_000 { + stats_skipped += 1; + continue 'forward; + } + } let tnc2 = format_tnc2(&pkt); debug!("APRS-IS: forwarded {}>{},...", pkt.src_call, pkt.dest_call); if let Err(e) = write_half.write_all(tnc2.as_bytes()).await {