Prevent duplicate channel messages

This commit is contained in:
Janez T
2026-03-13 19:39:03 +01:00
parent 63aa9619e6
commit 249268dd00
2 changed files with 40 additions and 18 deletions

View File

@@ -646,11 +646,18 @@ class MessagesProvider with ChangeNotifier {
if (existing.channelIdx != message.channelIdx) {
return false;
}
final timestampDeltaSeconds = (existing.senderTimestamp -
message.senderTimestamp)
.abs();
final withinChannelRepeatWindow = timestampDeltaSeconds <= 5;
final existingSenderKey = existing.senderKeyShort;
final incomingSenderKey = message.senderKeyShort;
if (existingSenderKey != null &&
incomingSenderKey != null &&
existingSenderKey == incomingSenderKey) {
existingSenderKey == incomingSenderKey &&
withinChannelRepeatWindow) {
return true;
}
@@ -663,23 +670,20 @@ class MessagesProvider with ChangeNotifier {
return true;
}
// When we send to a channel we add a local "sent" bubble immediately,
// then firmware may later sync back the same message as a received
// channel item under our public handle. Only fold that replay into the
// original bubble after LOG_RX_DATA has already confirmed it as our
// transmitted packet.
if (!withinChannelRepeatWindow) {
return false;
}
// Mirror meshcore-open behavior for self-authored channel messages:
// zero-hop self replays are dropped earlier in AppProvider, while
// routed self replays should fold into the local sent bubble when the
// public sender name matches within a short window.
if (existing.isSentMessage &&
existing.echoCount > 0 &&
!message.isSentMessage &&
(existing.senderTimestamp - message.senderTimestamp).abs() <= 1) {
existingSenderName == incomingSenderName) {
return true;
}
}
if (existing.isSentMessage &&
existing.echoCount > 0 &&
!message.isSentMessage &&
existing.senderTimestamp == message.senderTimestamp) {
return true;
}

View File

@@ -177,7 +177,7 @@ void main() {
});
test(
'channel replay is deduped after raw echo detection confirms our send',
'channel replay is deduped for self sender within repeat window',
() {
final provider = MessagesProvider();
provider.resolveContactNameCallback = (_) => 'dz0ny (SI)';
@@ -185,12 +185,11 @@ void main() {
_buildSentChannelMessage(id: 'c-echo', senderTimestamp: 1700000100),
);
provider.markMessageSent('c-echo', 0, 0);
provider.handleMessageEcho('c-echo', 1, 12, -90);
provider.addMessage(
_buildReceivedChannelReplay(
id: 'c-echo-incoming',
senderTimestamp: 1700000101,
senderTimestamp: 1700000104,
senderName: 'dz0ny (SI)',
),
);
@@ -202,7 +201,7 @@ void main() {
);
test(
'channel replay is not deduped before raw echo detection confirms it',
'channel replay is not deduped for different sender with same text',
() {
final provider = MessagesProvider();
provider.resolveContactNameCallback = (_) => 'dz0ny (SI)';
@@ -218,7 +217,7 @@ void main() {
_buildReceivedChannelReplay(
id: 'c-no-echo-incoming',
senderTimestamp: 1700000201,
senderName: 'dz0ny (SI)',
senderName: 'Radio Alpha',
),
);
@@ -226,6 +225,25 @@ void main() {
},
);
test('channel replay is not deduped outside repeat window', () {
final provider = MessagesProvider();
provider.resolveContactNameCallback = (_) => 'dz0ny (SI)';
provider.addSentMessage(
_buildSentChannelMessage(id: 'c-late', senderTimestamp: 1700000300),
);
provider.markMessageSent('c-late', 0, 0);
provider.addMessage(
_buildReceivedChannelReplay(
id: 'c-late-incoming',
senderTimestamp: 1700000306,
senderName: 'dz0ny (SI)',
),
);
expect(provider.messages, hasLength(2));
});
test('missing ACK schedules a delayed retransmission', () {
fakeAsync((async) {
final provider = MessagesProvider();