mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
feat: Add location-based DM retries
This commit is contained in:
@@ -9,6 +9,11 @@ class PathRecord {
|
|||||||
final int failureCount;
|
final int failureCount;
|
||||||
final int lastRoundTripTimeMs;
|
final int lastRoundTripTimeMs;
|
||||||
final DateTime lastUsedAt;
|
final DateTime lastUsedAt;
|
||||||
|
final DateTime? lastSucceededAt;
|
||||||
|
final double? senderLatitude;
|
||||||
|
final double? senderLongitude;
|
||||||
|
final double? recipientLatitude;
|
||||||
|
final double? recipientLongitude;
|
||||||
|
|
||||||
const PathRecord({
|
const PathRecord({
|
||||||
required this.pathBytes,
|
required this.pathBytes,
|
||||||
@@ -19,6 +24,11 @@ class PathRecord {
|
|||||||
required this.failureCount,
|
required this.failureCount,
|
||||||
required this.lastRoundTripTimeMs,
|
required this.lastRoundTripTimeMs,
|
||||||
required this.lastUsedAt,
|
required this.lastUsedAt,
|
||||||
|
required this.lastSucceededAt,
|
||||||
|
required this.senderLatitude,
|
||||||
|
required this.senderLongitude,
|
||||||
|
required this.recipientLatitude,
|
||||||
|
required this.recipientLongitude,
|
||||||
});
|
});
|
||||||
|
|
||||||
String get signature =>
|
String get signature =>
|
||||||
@@ -36,6 +46,11 @@ class PathRecord {
|
|||||||
int? failureCount,
|
int? failureCount,
|
||||||
int? lastRoundTripTimeMs,
|
int? lastRoundTripTimeMs,
|
||||||
DateTime? lastUsedAt,
|
DateTime? lastUsedAt,
|
||||||
|
DateTime? lastSucceededAt,
|
||||||
|
double? senderLatitude,
|
||||||
|
double? senderLongitude,
|
||||||
|
double? recipientLatitude,
|
||||||
|
double? recipientLongitude,
|
||||||
}) {
|
}) {
|
||||||
return PathRecord(
|
return PathRecord(
|
||||||
pathBytes: pathBytes ?? this.pathBytes,
|
pathBytes: pathBytes ?? this.pathBytes,
|
||||||
@@ -46,6 +61,11 @@ class PathRecord {
|
|||||||
failureCount: failureCount ?? this.failureCount,
|
failureCount: failureCount ?? this.failureCount,
|
||||||
lastRoundTripTimeMs: lastRoundTripTimeMs ?? this.lastRoundTripTimeMs,
|
lastRoundTripTimeMs: lastRoundTripTimeMs ?? this.lastRoundTripTimeMs,
|
||||||
lastUsedAt: lastUsedAt ?? this.lastUsedAt,
|
lastUsedAt: lastUsedAt ?? this.lastUsedAt,
|
||||||
|
lastSucceededAt: lastSucceededAt ?? this.lastSucceededAt,
|
||||||
|
senderLatitude: senderLatitude ?? this.senderLatitude,
|
||||||
|
senderLongitude: senderLongitude ?? this.senderLongitude,
|
||||||
|
recipientLatitude: recipientLatitude ?? this.recipientLatitude,
|
||||||
|
recipientLongitude: recipientLongitude ?? this.recipientLongitude,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +79,11 @@ class PathRecord {
|
|||||||
'failure_count': failureCount,
|
'failure_count': failureCount,
|
||||||
'last_round_trip_time_ms': lastRoundTripTimeMs,
|
'last_round_trip_time_ms': lastRoundTripTimeMs,
|
||||||
'last_used_at': lastUsedAt.toIso8601String(),
|
'last_used_at': lastUsedAt.toIso8601String(),
|
||||||
|
'last_succeeded_at': lastSucceededAt?.toIso8601String(),
|
||||||
|
'sender_latitude': senderLatitude,
|
||||||
|
'sender_longitude': senderLongitude,
|
||||||
|
'recipient_latitude': recipientLatitude,
|
||||||
|
'recipient_longitude': recipientLongitude,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +104,13 @@ class PathRecord {
|
|||||||
lastUsedAt:
|
lastUsedAt:
|
||||||
DateTime.tryParse(json['last_used_at'] as String? ?? '') ??
|
DateTime.tryParse(json['last_used_at'] as String? ?? '') ??
|
||||||
DateTime.fromMillisecondsSinceEpoch(0),
|
DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
|
lastSucceededAt: DateTime.tryParse(
|
||||||
|
json['last_succeeded_at'] as String? ?? '',
|
||||||
|
),
|
||||||
|
senderLatitude: (json['sender_latitude'] as num?)?.toDouble(),
|
||||||
|
senderLongitude: (json['sender_longitude'] as num?)?.toDouble(),
|
||||||
|
recipientLatitude: (json['recipient_latitude'] as num?)?.toDouble(),
|
||||||
|
recipientLongitude: (json['recipient_longitude'] as num?)?.toDouble(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1761,6 +1761,7 @@ class AppProvider with ChangeNotifier {
|
|||||||
return _prepareDirectMessageSend(
|
return _prepareDirectMessageSend(
|
||||||
messageId: messageId,
|
messageId: messageId,
|
||||||
contact: contact,
|
contact: contact,
|
||||||
|
retryAttempt: retryAttempt,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1830,23 +1831,47 @@ class AppProvider with ChangeNotifier {
|
|||||||
Future<Contact> _prepareDirectMessageSend({
|
Future<Contact> _prepareDirectMessageSend({
|
||||||
required String messageId,
|
required String messageId,
|
||||||
required Contact contact,
|
required Contact contact,
|
||||||
|
required int retryAttempt,
|
||||||
}) async {
|
}) async {
|
||||||
final latestContact =
|
final latestContact =
|
||||||
contactsProvider.findContactByKey(contact.publicKey) ?? contact;
|
contactsProvider.findContactByKey(contact.publicKey) ?? contact;
|
||||||
var session = _directMessageRouteSessions[messageId];
|
var session = _directMessageRouteSessions[messageId];
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
final selection = await _pathHistoryService.getSelectionForContact(
|
final selection = latestContact.routeHasPath && latestContact.routeHopCount > 0
|
||||||
latestContact,
|
? PathSelection(
|
||||||
autoRouteRotationEnabled: _autoRouteRotationEnabled,
|
mode: PathSelectionMode.directCurrent,
|
||||||
);
|
pathBytes: Uint8List.fromList(latestContact.routePathBytes),
|
||||||
|
hopCount: latestContact.routeHopCount,
|
||||||
|
hashSize: latestContact.routeHashSize,
|
||||||
|
)
|
||||||
|
: await _pathHistoryService.getSelectionForContact(
|
||||||
|
latestContact,
|
||||||
|
autoRouteRotationEnabled: _autoRouteRotationEnabled,
|
||||||
|
);
|
||||||
session = _DirectMessageRouteSession(
|
session = _DirectMessageRouteSession(
|
||||||
currentSelection: selection,
|
currentSelection: selection,
|
||||||
originalRoute: ContactRouteCodec.fromContact(latestContact),
|
originalRoute: ContactRouteCodec.fromContact(latestContact),
|
||||||
routerFallbackAttempted: false,
|
routerFallbackAttempted: false,
|
||||||
);
|
);
|
||||||
_directMessageRouteSessions[messageId] = session;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!session.routerFallbackAttempted) {
|
||||||
|
final currentSignature =
|
||||||
|
latestContact.routeHasPath && latestContact.routeHopCount > 0
|
||||||
|
? latestContact.routePathBytes
|
||||||
|
.map((byte) => byte.toRadixString(16).padLeft(2, '0'))
|
||||||
|
.join()
|
||||||
|
: null;
|
||||||
|
final selection = await _resolveDirectMessageSelectionForRetry(
|
||||||
|
latestContact,
|
||||||
|
retryAttempt: retryAttempt,
|
||||||
|
currentSignature: currentSignature,
|
||||||
|
fallbackSelection: session.currentSelection,
|
||||||
|
);
|
||||||
|
session = session.copyWith(currentSelection: selection);
|
||||||
|
}
|
||||||
|
_directMessageRouteSessions[messageId] = session;
|
||||||
|
|
||||||
await _applyPathSelection(
|
await _applyPathSelection(
|
||||||
latestContact,
|
latestContact,
|
||||||
session.currentSelection,
|
session.currentSelection,
|
||||||
@@ -1857,6 +1882,43 @@ class AppProvider with ChangeNotifier {
|
|||||||
latestContact;
|
latestContact;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<PathSelection> _resolveDirectMessageSelectionForRetry(
|
||||||
|
Contact contact, {
|
||||||
|
required int retryAttempt,
|
||||||
|
required String? currentSignature,
|
||||||
|
required PathSelection fallbackSelection,
|
||||||
|
}) async {
|
||||||
|
if (contact.routeHasPath && contact.routeHopCount > 0 && retryAttempt <= 1) {
|
||||||
|
return PathSelection(
|
||||||
|
mode: PathSelectionMode.directCurrent,
|
||||||
|
pathBytes: Uint8List.fromList(contact.routePathBytes),
|
||||||
|
hopCount: contact.routeHopCount,
|
||||||
|
hashSize: contact.routeHashSize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retryAttempt == 2) {
|
||||||
|
return PathSelection.flood();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retryAttempt >= 3) {
|
||||||
|
final historicalSelection = await _pathHistoryService
|
||||||
|
.getLastSuccessfulDirectSelection(
|
||||||
|
contact,
|
||||||
|
excludeSignature: currentSignature,
|
||||||
|
senderLatitude: locationTrackingService.currentPosition?.latitude,
|
||||||
|
senderLongitude: locationTrackingService.currentPosition?.longitude,
|
||||||
|
recipientLatitude: contact.displayLocation?.latitude,
|
||||||
|
recipientLongitude: contact.displayLocation?.longitude,
|
||||||
|
);
|
||||||
|
if (historicalSelection != null) {
|
||||||
|
return historicalSelection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallbackSelection;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _applyPathSelection(
|
Future<void> _applyPathSelection(
|
||||||
Contact contact,
|
Contact contact,
|
||||||
PathSelection selection, {
|
PathSelection selection, {
|
||||||
@@ -2031,6 +2093,10 @@ class AppProvider with ChangeNotifier {
|
|||||||
session.currentSelection,
|
session.currentSelection,
|
||||||
success: true,
|
success: true,
|
||||||
roundTripTimeMs: roundTripTimeMs,
|
roundTripTimeMs: roundTripTimeMs,
|
||||||
|
senderLatitude: locationTrackingService.currentPosition?.latitude,
|
||||||
|
senderLongitude: locationTrackingService.currentPosition?.longitude,
|
||||||
|
recipientLatitude: contact.displayLocation?.latitude,
|
||||||
|
recipientLongitude: contact.displayLocation?.longitude,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ class MessageRetryManager {
|
|||||||
final Map<String, int> _pathFailureStreaks = {};
|
final Map<String, int> _pathFailureStreaks = {};
|
||||||
|
|
||||||
/// Max retry attempts when the contact has a known path.
|
/// Max retry attempts when the contact has a known path.
|
||||||
/// Official MeshCore app uses 5 (with auto-retry) or 3 (without).
|
/// Sequence: 2 direct attempts, flood, then last successful route.
|
||||||
static const int maxRetryAttemptsWithPath = 5;
|
static const int maxRetryAttemptsWithPath = 3;
|
||||||
|
|
||||||
/// No retries for flood-only contacts (no known path).
|
/// No retries for flood-only contacts (no known path).
|
||||||
/// Value 0 means: don't retry at all, go straight to fallback/fail.
|
/// Value 0 means: don't retry at all, go straight to fallback/fail.
|
||||||
|
|||||||
@@ -2354,17 +2354,6 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// On the last attempt, reset the path to force flood mode
|
|
||||||
// (matches official MeshCore app behaviour)
|
|
||||||
if (_retryManager.isLastAttempt(currentMessage, contact)) {
|
|
||||||
debugPrint(
|
|
||||||
'🔄 [MessagesProvider] Last attempt — resetting path to flood for $messageId',
|
|
||||||
);
|
|
||||||
if (resetPathBeforeLastRetryCallback != null) {
|
|
||||||
await resetPathBeforeLastRetryCallback!(contact);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sendMessageCallback != null) {
|
if (sendMessageCallback != null) {
|
||||||
final queued = await sendMessageCallback!(
|
final queued = await sendMessageCallback!(
|
||||||
contactPublicKey: contact.publicKey,
|
contactPublicKey: contact.publicKey,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:geolocator/geolocator.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import '../models/contact.dart';
|
import '../models/contact.dart';
|
||||||
@@ -9,7 +10,7 @@ import '../models/path_selection.dart';
|
|||||||
import '../utils/log_rx_route_decoder.dart';
|
import '../utils/log_rx_route_decoder.dart';
|
||||||
|
|
||||||
class PathHistoryService {
|
class PathHistoryService {
|
||||||
static const String _storageKey = 'contact_path_history_v1';
|
static const String _storageKey = 'contact_path_history_v2';
|
||||||
static const int _maxDirectPaths = 20;
|
static const int _maxDirectPaths = 20;
|
||||||
static const int _topRotationCount = 3;
|
static const int _topRotationCount = 3;
|
||||||
|
|
||||||
@@ -59,6 +60,11 @@ class PathHistoryService {
|
|||||||
failureCount: existing?.failureCount ?? 0,
|
failureCount: existing?.failureCount ?? 0,
|
||||||
lastRoundTripTimeMs: existing?.lastRoundTripTimeMs ?? 0,
|
lastRoundTripTimeMs: existing?.lastRoundTripTimeMs ?? 0,
|
||||||
lastUsedAt: DateTime.now(),
|
lastUsedAt: DateTime.now(),
|
||||||
|
lastSucceededAt: existing?.lastSucceededAt,
|
||||||
|
senderLatitude: existing?.senderLatitude,
|
||||||
|
senderLongitude: existing?.senderLongitude,
|
||||||
|
recipientLatitude: existing?.recipientLatitude,
|
||||||
|
recipientLongitude: existing?.recipientLongitude,
|
||||||
);
|
);
|
||||||
|
|
||||||
await _saveHistory(
|
await _saveHistory(
|
||||||
@@ -104,6 +110,11 @@ class PathHistoryService {
|
|||||||
failureCount: existing?.failureCount ?? 0,
|
failureCount: existing?.failureCount ?? 0,
|
||||||
lastRoundTripTimeMs: existing?.lastRoundTripTimeMs ?? 0,
|
lastRoundTripTimeMs: existing?.lastRoundTripTimeMs ?? 0,
|
||||||
lastUsedAt: DateTime.now(),
|
lastUsedAt: DateTime.now(),
|
||||||
|
lastSucceededAt: existing?.lastSucceededAt,
|
||||||
|
senderLatitude: existing?.senderLatitude,
|
||||||
|
senderLongitude: existing?.senderLongitude,
|
||||||
|
recipientLatitude: existing?.recipientLatitude,
|
||||||
|
recipientLongitude: existing?.recipientLongitude,
|
||||||
);
|
);
|
||||||
|
|
||||||
await _saveHistory(
|
await _saveHistory(
|
||||||
@@ -172,6 +183,10 @@ class PathHistoryService {
|
|||||||
PathSelection selection, {
|
PathSelection selection, {
|
||||||
required bool success,
|
required bool success,
|
||||||
int? roundTripTimeMs,
|
int? roundTripTimeMs,
|
||||||
|
double? senderLatitude,
|
||||||
|
double? senderLongitude,
|
||||||
|
double? recipientLatitude,
|
||||||
|
double? recipientLongitude,
|
||||||
}) async {
|
}) async {
|
||||||
await initialize();
|
await initialize();
|
||||||
final history = _historyFor(contactPublicKeyHex);
|
final history = _historyFor(contactPublicKeyHex);
|
||||||
@@ -206,6 +221,13 @@ class PathHistoryService {
|
|||||||
? (roundTripTimeMs ?? existing?.lastRoundTripTimeMs ?? 0)
|
? (roundTripTimeMs ?? existing?.lastRoundTripTimeMs ?? 0)
|
||||||
: (existing?.lastRoundTripTimeMs ?? 0),
|
: (existing?.lastRoundTripTimeMs ?? 0),
|
||||||
lastUsedAt: DateTime.now(),
|
lastUsedAt: DateTime.now(),
|
||||||
|
lastSucceededAt: success ? DateTime.now() : existing?.lastSucceededAt,
|
||||||
|
senderLatitude: success ? senderLatitude : existing?.senderLatitude,
|
||||||
|
senderLongitude: success ? senderLongitude : existing?.senderLongitude,
|
||||||
|
recipientLatitude:
|
||||||
|
success ? recipientLatitude : existing?.recipientLatitude,
|
||||||
|
recipientLongitude:
|
||||||
|
success ? recipientLongitude : existing?.recipientLongitude,
|
||||||
);
|
);
|
||||||
await _saveHistory(
|
await _saveHistory(
|
||||||
contactPublicKeyHex,
|
contactPublicKeyHex,
|
||||||
@@ -215,6 +237,54 @@ class PathHistoryService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<PathSelection?> getLastSuccessfulDirectSelection(
|
||||||
|
Contact contact, {
|
||||||
|
String? excludeSignature,
|
||||||
|
double? senderLatitude,
|
||||||
|
double? senderLongitude,
|
||||||
|
double? recipientLatitude,
|
||||||
|
double? recipientLongitude,
|
||||||
|
}) async {
|
||||||
|
await initialize();
|
||||||
|
final history = _historyFor(contact.publicKeyHex);
|
||||||
|
final ranked = history.directPaths
|
||||||
|
.where(
|
||||||
|
(record) =>
|
||||||
|
record.successCount > 0 &&
|
||||||
|
record.lastSucceededAt != null &&
|
||||||
|
record.signature != excludeSignature,
|
||||||
|
)
|
||||||
|
.toList()
|
||||||
|
..sort((a, b) {
|
||||||
|
final locationCompare = _compareLocationFit(
|
||||||
|
a,
|
||||||
|
b,
|
||||||
|
senderLatitude: senderLatitude,
|
||||||
|
senderLongitude: senderLongitude,
|
||||||
|
recipientLatitude: recipientLatitude,
|
||||||
|
recipientLongitude: recipientLongitude,
|
||||||
|
);
|
||||||
|
if (locationCompare != 0) return locationCompare;
|
||||||
|
final succeededCompare = b.lastSucceededAt!.compareTo(
|
||||||
|
a.lastSucceededAt!,
|
||||||
|
);
|
||||||
|
if (succeededCompare != 0) return succeededCompare;
|
||||||
|
return _comparePathRecords(a, b);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ranked.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final record = ranked.first;
|
||||||
|
return PathSelection(
|
||||||
|
mode: PathSelectionMode.directHistorical,
|
||||||
|
pathBytes: Uint8List.fromList(record.pathBytes),
|
||||||
|
hopCount: record.hopCount,
|
||||||
|
hashSize: record.hashSize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
ContactPathHistory historyFor(String contactPublicKeyHex) {
|
ContactPathHistory historyFor(String contactPublicKeyHex) {
|
||||||
return _cache[contactPublicKeyHex] ??
|
return _cache[contactPublicKeyHex] ??
|
||||||
ContactPathHistory.empty(contactPublicKeyHex);
|
ContactPathHistory.empty(contactPublicKeyHex);
|
||||||
@@ -279,4 +349,68 @@ class PathHistoryService {
|
|||||||
|
|
||||||
String _signature(Uint8List bytes) =>
|
String _signature(Uint8List bytes) =>
|
||||||
bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
|
bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
|
||||||
|
|
||||||
|
int _compareLocationFit(
|
||||||
|
PathRecord a,
|
||||||
|
PathRecord b, {
|
||||||
|
required double? senderLatitude,
|
||||||
|
required double? senderLongitude,
|
||||||
|
required double? recipientLatitude,
|
||||||
|
required double? recipientLongitude,
|
||||||
|
}) {
|
||||||
|
final aDistance = _locationDistanceScore(
|
||||||
|
a,
|
||||||
|
senderLatitude: senderLatitude,
|
||||||
|
senderLongitude: senderLongitude,
|
||||||
|
recipientLatitude: recipientLatitude,
|
||||||
|
recipientLongitude: recipientLongitude,
|
||||||
|
);
|
||||||
|
final bDistance = _locationDistanceScore(
|
||||||
|
b,
|
||||||
|
senderLatitude: senderLatitude,
|
||||||
|
senderLongitude: senderLongitude,
|
||||||
|
recipientLatitude: recipientLatitude,
|
||||||
|
recipientLongitude: recipientLongitude,
|
||||||
|
);
|
||||||
|
return aDistance.compareTo(bDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
double _locationDistanceScore(
|
||||||
|
PathRecord record, {
|
||||||
|
required double? senderLatitude,
|
||||||
|
required double? senderLongitude,
|
||||||
|
required double? recipientLatitude,
|
||||||
|
required double? recipientLongitude,
|
||||||
|
}) {
|
||||||
|
var total = 0.0;
|
||||||
|
var matched = false;
|
||||||
|
|
||||||
|
if (senderLatitude != null &&
|
||||||
|
senderLongitude != null &&
|
||||||
|
record.senderLatitude != null &&
|
||||||
|
record.senderLongitude != null) {
|
||||||
|
matched = true;
|
||||||
|
total += Geolocator.distanceBetween(
|
||||||
|
senderLatitude,
|
||||||
|
senderLongitude,
|
||||||
|
record.senderLatitude!,
|
||||||
|
record.senderLongitude!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipientLatitude != null &&
|
||||||
|
recipientLongitude != null &&
|
||||||
|
record.recipientLatitude != null &&
|
||||||
|
record.recipientLongitude != null) {
|
||||||
|
matched = true;
|
||||||
|
total += Geolocator.distanceBetween(
|
||||||
|
recipientLatitude,
|
||||||
|
recipientLongitude,
|
||||||
|
record.recipientLatitude!,
|
||||||
|
record.recipientLongitude!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return matched ? total : double.infinity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,4 +206,59 @@ void main() {
|
|||||||
expect(history.directPaths.single.source, PathRecordSource.observed);
|
expect(history.directPaths.single.source, PathRecordSource.observed);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test('last successful direct path is chosen by location fit', () async {
|
||||||
|
final service = PathHistoryService();
|
||||||
|
final contact = _buildContact(
|
||||||
|
seed: 7,
|
||||||
|
pathBytes: [0xAA],
|
||||||
|
hopCount: 1,
|
||||||
|
hashSize: 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
await service.initialize();
|
||||||
|
await service.recordPathResult(
|
||||||
|
contact.publicKeyHex,
|
||||||
|
PathSelection(
|
||||||
|
mode: PathSelectionMode.directHistorical,
|
||||||
|
pathBytes: Uint8List.fromList([0x11]),
|
||||||
|
hopCount: 1,
|
||||||
|
hashSize: 1,
|
||||||
|
),
|
||||||
|
success: true,
|
||||||
|
roundTripTimeMs: 120,
|
||||||
|
senderLatitude: 46.0,
|
||||||
|
senderLongitude: 14.0,
|
||||||
|
recipientLatitude: 46.1,
|
||||||
|
recipientLongitude: 14.1,
|
||||||
|
);
|
||||||
|
await service.recordPathResult(
|
||||||
|
contact.publicKeyHex,
|
||||||
|
PathSelection(
|
||||||
|
mode: PathSelectionMode.directHistorical,
|
||||||
|
pathBytes: Uint8List.fromList([0x22]),
|
||||||
|
hopCount: 1,
|
||||||
|
hashSize: 1,
|
||||||
|
),
|
||||||
|
success: true,
|
||||||
|
roundTripTimeMs: 90,
|
||||||
|
senderLatitude: 46.0001,
|
||||||
|
senderLongitude: 14.0001,
|
||||||
|
recipientLatitude: 46.1001,
|
||||||
|
recipientLongitude: 14.1001,
|
||||||
|
);
|
||||||
|
|
||||||
|
final selection = await service.getLastSuccessfulDirectSelection(
|
||||||
|
contact,
|
||||||
|
excludeSignature: 'aa',
|
||||||
|
senderLatitude: 46.0002,
|
||||||
|
senderLongitude: 14.0002,
|
||||||
|
recipientLatitude: 46.1002,
|
||||||
|
recipientLongitude: 14.1002,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(selection, isNotNull);
|
||||||
|
expect(selection!.mode, PathSelectionMode.directHistorical);
|
||||||
|
expect(selection.canonicalPath, '22');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user