mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Investigate Command 0x1f timeout
This commit is contained in:
@@ -293,9 +293,10 @@ class AppProvider with ChangeNotifier {
|
|||||||
if (publicKey == null || publicKey.isEmpty) return null;
|
if (publicKey == null || publicKey.isEmpty) return null;
|
||||||
|
|
||||||
final ownPublicKey = connectionProvider.deviceInfo.publicKey;
|
final ownPublicKey = connectionProvider.deviceInfo.publicKey;
|
||||||
final ownName =
|
final ownName = _preferredSelfDisplayName(
|
||||||
connectionProvider.deviceInfo.deviceName ??
|
deviceName: connectionProvider.deviceInfo.deviceName,
|
||||||
connectionProvider.deviceInfo.selfName;
|
selfName: connectionProvider.deviceInfo.selfName,
|
||||||
|
);
|
||||||
if (_matchesPublicKeyPrefix(publicKey, ownPublicKey) &&
|
if (_matchesPublicKeyPrefix(publicKey, ownPublicKey) &&
|
||||||
ownName != null &&
|
ownName != null &&
|
||||||
ownName.trim().isNotEmpty) {
|
ownName.trim().isNotEmpty) {
|
||||||
@@ -340,6 +341,35 @@ class AppProvider with ChangeNotifier {
|
|||||||
return trimmedSenderName == trimmedOwnName;
|
return trimmedSenderName == trimmedOwnName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String? preferredSelfDisplayName({
|
||||||
|
required String? deviceName,
|
||||||
|
required String? selfName,
|
||||||
|
}) => _preferredSelfDisplayName(deviceName: deviceName, selfName: selfName);
|
||||||
|
|
||||||
|
static String? _preferredSelfDisplayName({
|
||||||
|
required String? deviceName,
|
||||||
|
required String? selfName,
|
||||||
|
}) {
|
||||||
|
final trimmedSelfName = selfName?.trim();
|
||||||
|
if (trimmedSelfName != null && trimmedSelfName.isNotEmpty) {
|
||||||
|
return trimmedSelfName;
|
||||||
|
}
|
||||||
|
|
||||||
|
final trimmedDeviceName = deviceName?.trim();
|
||||||
|
if (trimmedDeviceName == null || trimmedDeviceName.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmedDeviceName.toLowerCase().startsWith('meshcore-')) {
|
||||||
|
final stripped = trimmedDeviceName.substring('meshcore-'.length).trim();
|
||||||
|
if (stripped.isNotEmpty) {
|
||||||
|
return stripped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return trimmedDeviceName;
|
||||||
|
}
|
||||||
|
|
||||||
static bool _matchesPublicKeyPrefix(
|
static bool _matchesPublicKeyPrefix(
|
||||||
Uint8List? candidateKey,
|
Uint8List? candidateKey,
|
||||||
Uint8List? ownPublicKey,
|
Uint8List? ownPublicKey,
|
||||||
@@ -820,10 +850,10 @@ class AppProvider with ChangeNotifier {
|
|||||||
|
|
||||||
final isDeletedChannel = connectionProvider
|
final isDeletedChannel = connectionProvider
|
||||||
.shouldTreatChannelInfoAsDeleted(
|
.shouldTreatChannelInfoAsDeleted(
|
||||||
channelIdx,
|
channelIdx,
|
||||||
channelName,
|
channelName,
|
||||||
secret,
|
secret,
|
||||||
);
|
);
|
||||||
final isEmptyChannelInfo = AppProvider.isDeletedChannelInfo(
|
final isEmptyChannelInfo = AppProvider.isDeletedChannelInfo(
|
||||||
channelIdx,
|
channelIdx,
|
||||||
channelName,
|
channelName,
|
||||||
@@ -930,6 +960,18 @@ class AppProvider with ChangeNotifier {
|
|||||||
|
|
||||||
// When a message is received
|
// When a message is received
|
||||||
connectionProvider.onMessageReceived = (message) {
|
connectionProvider.onMessageReceived = (message) {
|
||||||
|
if (message.isChannelMessage) {
|
||||||
|
debugPrint(
|
||||||
|
'📥 [AppProvider] Channel message received: '
|
||||||
|
'id=${message.id}, '
|
||||||
|
'senderName=${message.senderName ?? "-"}, '
|
||||||
|
'senderKey=${message.senderKeyShort ?? "-"}, '
|
||||||
|
'pathLen=${message.pathLen}, '
|
||||||
|
'ts=${message.senderTimestamp}, '
|
||||||
|
'text=${message.text}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (AppProvider.shouldIgnoreSelfReplay(
|
if (AppProvider.shouldIgnoreSelfReplay(
|
||||||
message: message,
|
message: message,
|
||||||
ownPublicKey: connectionProvider.deviceInfo.publicKey,
|
ownPublicKey: connectionProvider.deviceInfo.publicKey,
|
||||||
|
|||||||
@@ -627,6 +627,13 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
existing.text != message.text) {
|
existing.text != message.text) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (message.isChannelMessage) {
|
||||||
|
debugPrint(
|
||||||
|
'🔁 [MessagesProvider] Channel duplicate match: '
|
||||||
|
'incoming(id=${message.id}, senderName=${message.senderName ?? "-"}, senderKey=${message.senderKeyShort ?? "-"}, pathLen=${message.pathLen}, ts=${message.senderTimestamp}) '
|
||||||
|
'existing(id=${existing.id}, sent=${existing.isSentMessage}, senderName=${existing.senderName ?? "-"}, senderKey=${existing.senderKeyShort ?? "-"}, pathLen=${existing.pathLen}, ts=${existing.senderTimestamp})',
|
||||||
|
);
|
||||||
|
}
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,9 +654,8 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final timestampDeltaSeconds =
|
final withinChannelRepeatWindow =
|
||||||
(existing.senderTimestamp - message.senderTimestamp).abs();
|
(existing.senderTimestamp - message.senderTimestamp).abs() <= 30;
|
||||||
final withinChannelRepeatWindow = timestampDeltaSeconds <= 5;
|
|
||||||
|
|
||||||
final existingSenderKey = existing.senderKeyShort;
|
final existingSenderKey = existing.senderKeyShort;
|
||||||
final incomingSenderKey = message.senderKeyShort;
|
final incomingSenderKey = message.senderKeyShort;
|
||||||
@@ -660,8 +666,16 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final existingSenderName = _normalizeSenderName(existing.senderName);
|
final existingSenderName = _normalizedResolvedSenderName(existing);
|
||||||
final incomingSenderName = _normalizeSenderName(message.senderName);
|
final incomingSenderName = _normalizedResolvedSenderName(message);
|
||||||
|
if (existing.isChannelMessage && message.isChannelMessage) {
|
||||||
|
debugPrint(
|
||||||
|
'🧪 [MessagesProvider] Channel dedupe check: '
|
||||||
|
'existing(id=${existing.id}, sent=${existing.isSentMessage}, senderName=${existing.senderName ?? "-"}, resolvedSender=${existingSenderName ?? "-"}, senderKey=${existing.senderKeyShort ?? "-"}, ts=${existing.senderTimestamp}, pathLen=${existing.pathLen}) '
|
||||||
|
'incoming(id=${message.id}, sent=${message.isSentMessage}, senderName=${message.senderName ?? "-"}, resolvedSender=${incomingSenderName ?? "-"}, senderKey=${message.senderKeyShort ?? "-"}, ts=${message.senderTimestamp}, pathLen=${message.pathLen}) '
|
||||||
|
'withinWindow=$withinChannelRepeatWindow',
|
||||||
|
);
|
||||||
|
}
|
||||||
if (existingSenderName != null &&
|
if (existingSenderName != null &&
|
||||||
incomingSenderName != null &&
|
incomingSenderName != null &&
|
||||||
existingSenderName == incomingSenderName) {
|
existingSenderName == incomingSenderName) {
|
||||||
@@ -733,7 +747,13 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
if (trimmed == null || trimmed.isEmpty) {
|
if (trimmed == null || trimmed.isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return trimmed.toLowerCase();
|
|
||||||
|
var normalized = trimmed.toLowerCase();
|
||||||
|
if (normalized.startsWith('meshcore-')) {
|
||||||
|
normalized = normalized.substring('meshcore-'.length).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized.isEmpty ? null : normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
Message _resolveSenderNameIfNeeded(Message message) {
|
Message _resolveSenderNameIfNeeded(Message message) {
|
||||||
@@ -751,6 +771,22 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
return message.copyWith(senderName: resolvedSenderName.trim());
|
return message.copyWith(senderName: resolvedSenderName.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? _normalizedResolvedSenderName(Message message) {
|
||||||
|
final directName = _normalizeSenderName(message.senderName);
|
||||||
|
if (directName != null) {
|
||||||
|
return directName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.senderPublicKeyPrefix == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final resolved = resolveContactNameCallback?.call(
|
||||||
|
message.senderPublicKeyPrefix,
|
||||||
|
);
|
||||||
|
return _normalizeSenderName(resolved);
|
||||||
|
}
|
||||||
|
|
||||||
/// Trigger urgent notification for SAR marker
|
/// Trigger urgent notification for SAR marker
|
||||||
Future<void> _triggerSarNotification(
|
Future<void> _triggerSarNotification(
|
||||||
Message message,
|
Message message,
|
||||||
|
|||||||
@@ -22,6 +22,16 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
group('AppProvider self replay handling', () {
|
group('AppProvider self replay handling', () {
|
||||||
|
test('prefers self name over meshcore device name', () {
|
||||||
|
expect(
|
||||||
|
AppProvider.preferredSelfDisplayName(
|
||||||
|
deviceName: 'MeshCore-dz0ny (SI)',
|
||||||
|
selfName: 'dz0ny (SI)',
|
||||||
|
),
|
||||||
|
'dz0ny (SI)',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('ignores direct self replay without hops', () {
|
test('ignores direct self replay without hops', () {
|
||||||
final message = Message(
|
final message = Message(
|
||||||
id: 'dm-self',
|
id: 'dm-self',
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ void main() {
|
|||||||
provider.addMessage(
|
provider.addMessage(
|
||||||
_buildReceivedChannelReplay(
|
_buildReceivedChannelReplay(
|
||||||
id: 'c-late-incoming',
|
id: 'c-late-incoming',
|
||||||
senderTimestamp: 1700000306,
|
senderTimestamp: 1700000331,
|
||||||
senderName: 'dz0ny (SI)',
|
senderName: 'dz0ny (SI)',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -241,6 +241,46 @@ void main() {
|
|||||||
expect(provider.messages, hasLength(2));
|
expect(provider.messages, hasLength(2));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('channel replay can dedupe using lazily resolved self name', () {
|
||||||
|
final provider = MessagesProvider();
|
||||||
|
provider.addSentMessage(
|
||||||
|
_buildSentChannelMessage(id: 'c-lazy', senderTimestamp: 1700000400),
|
||||||
|
);
|
||||||
|
provider.markMessageSent('c-lazy', 0, 0);
|
||||||
|
provider.resolveContactNameCallback = (_) => 'dz0ny (SI)';
|
||||||
|
|
||||||
|
provider.addMessage(
|
||||||
|
_buildReceivedChannelReplay(
|
||||||
|
id: 'c-lazy-incoming',
|
||||||
|
senderTimestamp: 1700000406,
|
||||||
|
senderName: 'dz0ny (SI)',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(provider.messages, hasLength(1));
|
||||||
|
expect(provider.messages.single.id, equals('c-lazy'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('channel replay dedupes meshcore-prefixed self sender name', () {
|
||||||
|
final provider = MessagesProvider();
|
||||||
|
provider.resolveContactNameCallback = (_) => 'MeshCore-dz0ny (SI)';
|
||||||
|
provider.addSentMessage(
|
||||||
|
_buildSentChannelMessage(id: 'c-prefix', senderTimestamp: 1700000500),
|
||||||
|
);
|
||||||
|
provider.markMessageSent('c-prefix', 0, 0);
|
||||||
|
|
||||||
|
provider.addMessage(
|
||||||
|
_buildReceivedChannelReplay(
|
||||||
|
id: 'c-prefix-incoming',
|
||||||
|
senderTimestamp: 1700000500,
|
||||||
|
senderName: 'dz0ny (SI)',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(provider.messages, hasLength(1));
|
||||||
|
expect(provider.messages.single.id, equals('c-prefix'));
|
||||||
|
});
|
||||||
|
|
||||||
test('missing ACK schedules a delayed retransmission', () {
|
test('missing ACK schedules a delayed retransmission', () {
|
||||||
fakeAsync((async) {
|
fakeAsync((async) {
|
||||||
final provider = MessagesProvider();
|
final provider = MessagesProvider();
|
||||||
|
|||||||
Reference in New Issue
Block a user