mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-14 22:55:12 +10:00
formatted code
This commit is contained in:
@@ -699,43 +699,44 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
_loadChannelOrder();
|
||||
|
||||
// Initialize retry service callbacks
|
||||
_retryService?.initialize(RetryServiceConfig(
|
||||
sendMessage: _sendMessageDirect,
|
||||
addMessage: _addMessage,
|
||||
updateMessage: _updateMessage,
|
||||
clearContactPath: clearContactPath,
|
||||
setContactPath: setContactPath,
|
||||
calculateTimeout:
|
||||
(pathLength, messageBytes, {String? contactKey}) => calculateTimeout(
|
||||
pathLength: pathLength,
|
||||
messageBytes: messageBytes,
|
||||
contactKey: contactKey,
|
||||
),
|
||||
getSelfPublicKey: () => _selfPublicKey,
|
||||
prepareContactOutboundText: prepareContactOutboundText,
|
||||
appSettingsService: appSettingsService,
|
||||
debugLogService: _appDebugLogService,
|
||||
recordPathResult: _recordPathResult,
|
||||
selectRetryPath:
|
||||
(contactKey, attemptIndex, maxRetries, recentSelections) =>
|
||||
_selectAutoPathForAttempt(
|
||||
contactKey,
|
||||
attemptIndex: attemptIndex,
|
||||
maxRetries: maxRetries,
|
||||
recentSelections: recentSelections,
|
||||
),
|
||||
onDeliveryObserved:
|
||||
(contactKey, pathLength, messageBytes, tripTimeMs) {
|
||||
final secSinceRx = DateTime.now().difference(_lastRxTime).inSeconds;
|
||||
_timeoutPredictionService?.recordObservation(
|
||||
contactKey: contactKey,
|
||||
_retryService?.initialize(
|
||||
RetryServiceConfig(
|
||||
sendMessage: _sendMessageDirect,
|
||||
addMessage: _addMessage,
|
||||
updateMessage: _updateMessage,
|
||||
clearContactPath: clearContactPath,
|
||||
setContactPath: setContactPath,
|
||||
calculateTimeout: (pathLength, messageBytes, {String? contactKey}) =>
|
||||
calculateTimeout(
|
||||
pathLength: pathLength,
|
||||
messageBytes: messageBytes,
|
||||
tripTimeMs: tripTimeMs,
|
||||
secondsSinceLastRx: secSinceRx,
|
||||
);
|
||||
},
|
||||
));
|
||||
contactKey: contactKey,
|
||||
),
|
||||
getSelfPublicKey: () => _selfPublicKey,
|
||||
prepareContactOutboundText: prepareContactOutboundText,
|
||||
appSettingsService: appSettingsService,
|
||||
debugLogService: _appDebugLogService,
|
||||
recordPathResult: _recordPathResult,
|
||||
selectRetryPath:
|
||||
(contactKey, attemptIndex, maxRetries, recentSelections) =>
|
||||
_selectAutoPathForAttempt(
|
||||
contactKey,
|
||||
attemptIndex: attemptIndex,
|
||||
maxRetries: maxRetries,
|
||||
recentSelections: recentSelections,
|
||||
),
|
||||
onDeliveryObserved: (contactKey, pathLength, messageBytes, tripTimeMs) {
|
||||
final secSinceRx = DateTime.now().difference(_lastRxTime).inSeconds;
|
||||
_timeoutPredictionService?.recordObservation(
|
||||
contactKey: contactKey,
|
||||
pathLength: pathLength,
|
||||
messageBytes: messageBytes,
|
||||
tripTimeMs: tripTimeMs,
|
||||
secondsSinceLastRx: secSinceRx,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
final maxRetries = _appSettingsService?.settings.maxMessageRetries ?? 5;
|
||||
_retryService?.setMaxRetries(maxRetries);
|
||||
}
|
||||
@@ -908,7 +909,8 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
List<PathSelection> recentSelections = const [],
|
||||
}) {
|
||||
final hasKnownPaths =
|
||||
_pathHistoryService?.getRecentPaths(contactPubKeyHex).isNotEmpty ?? false;
|
||||
_pathHistoryService?.getRecentPaths(contactPubKeyHex).isNotEmpty ??
|
||||
false;
|
||||
if (!hasKnownPaths) {
|
||||
return null;
|
||||
}
|
||||
@@ -3619,10 +3621,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
void _handleIncomingChannelMessage(Uint8List frame) {
|
||||
final parsed = ChannelMessage.fromFrame(frame);
|
||||
if (parsed != null && parsed.channelIndex != null) {
|
||||
if (_shouldDropSelfChannelMessage(
|
||||
parsed.senderName,
|
||||
parsed.pathBytes,
|
||||
)) {
|
||||
if (_shouldDropSelfChannelMessage(parsed.senderName, parsed.pathBytes)) {
|
||||
return;
|
||||
}
|
||||
final contentHash = _computeContentHash(
|
||||
@@ -4246,7 +4245,9 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
final pathLenRaw = raw[index++];
|
||||
final pathByteLen = _decodePathByteLen(pathLenRaw);
|
||||
if (raw.length < index + pathByteLen) return null;
|
||||
final pathBytes = Uint8List.fromList(raw.sublist(index, index + pathByteLen));
|
||||
final pathBytes = Uint8List.fromList(
|
||||
raw.sublist(index, index + pathByteLen),
|
||||
);
|
||||
index += pathByteLen;
|
||||
if (raw.length <= index) return null;
|
||||
final payload = Uint8List.fromList(raw.sublist(index));
|
||||
@@ -4273,12 +4274,19 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
input[0] = payloadType;
|
||||
input.setRange(1, input.length, payload);
|
||||
final digest = crypto.sha256.convert(input).bytes;
|
||||
return digest.sublist(0, 8).map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
||||
return digest
|
||||
.sublist(0, 8)
|
||||
.map((b) => b.toRadixString(16).padLeft(2, '0'))
|
||||
.join();
|
||||
}
|
||||
|
||||
/// Content-based dedup hash for sync queue messages (no raw payload available).
|
||||
/// Prefixed with 'c:' to avoid collisions with packet hashes.
|
||||
String _computeContentHash(int channelIdx, int timestampSecs, String fullText) {
|
||||
String _computeContentHash(
|
||||
int channelIdx,
|
||||
int timestampSecs,
|
||||
String fullText,
|
||||
) {
|
||||
final textBytes = utf8.encode(fullText);
|
||||
final input = Uint8List(5 + textBytes.length);
|
||||
input[0] = channelIdx;
|
||||
|
||||
@@ -22,7 +22,11 @@ class _AckHistoryEntry {
|
||||
}
|
||||
|
||||
/// (messageId, timestamp, attemptIndex) — stored per ACK hash for O(1) lookup.
|
||||
typedef AckHashMapping = ({String messageId, DateTime timestamp, int attemptIndex});
|
||||
typedef AckHashMapping = ({
|
||||
String messageId,
|
||||
DateTime timestamp,
|
||||
int attemptIndex,
|
||||
});
|
||||
|
||||
class RetryServiceConfig {
|
||||
final void Function(Contact, String, int, int) sendMessage;
|
||||
@@ -31,7 +35,7 @@ class RetryServiceConfig {
|
||||
final Function(Contact)? clearContactPath;
|
||||
final Function(Contact, Uint8List, int)? setContactPath;
|
||||
final int Function(int pathLength, int messageBytes, {String? contactKey})?
|
||||
calculateTimeout;
|
||||
calculateTimeout;
|
||||
final Uint8List? Function()? getSelfPublicKey;
|
||||
final String Function(Contact, String)? prepareContactOutboundText;
|
||||
final AppSettingsService? appSettingsService;
|
||||
@@ -43,7 +47,8 @@ class RetryServiceConfig {
|
||||
int attemptIndex,
|
||||
int maxRetries,
|
||||
List<PathSelection> recentSelections,
|
||||
)? selectRetryPath;
|
||||
)?
|
||||
selectRetryPath;
|
||||
|
||||
const RetryServiceConfig({
|
||||
required this.sendMessage,
|
||||
@@ -132,7 +137,8 @@ class MessageRetryService extends ChangeNotifier {
|
||||
}) async {
|
||||
final messageId = const Uuid().v4();
|
||||
final resolved = resolvePathSelection(contact);
|
||||
final messagePathBytes = pathBytes ?? Uint8List.fromList(resolved.pathBytes);
|
||||
final messagePathBytes =
|
||||
pathBytes ?? Uint8List.fromList(resolved.pathBytes);
|
||||
final messagePathLength =
|
||||
pathLength ?? (resolved.useFlood ? -1 : resolved.hopCount);
|
||||
final message = Message(
|
||||
@@ -262,7 +268,8 @@ class MessageRetryService extends ChangeNotifier {
|
||||
if (config.setContactPath != null && config.clearContactPath != null) {
|
||||
final bool useFlood = currentSelection != null
|
||||
? currentSelection.useFlood
|
||||
: (effectiveMessage.pathLength != null && effectiveMessage.pathLength! < 0);
|
||||
: (effectiveMessage.pathLength != null &&
|
||||
effectiveMessage.pathLength! < 0);
|
||||
final List<int> pathBytes = currentSelection != null
|
||||
? currentSelection.pathBytes
|
||||
: effectiveMessage.pathBytes;
|
||||
|
||||
@@ -469,17 +469,18 @@ class PathHistoryService extends ChangeNotifier {
|
||||
final highestRouteWeight = _getHighestKnownRouteWeight(ranked);
|
||||
|
||||
ranked.sort((a, b) {
|
||||
final scoreCompare = _scorePathRecord(
|
||||
b,
|
||||
fastestTripMs: fastestTripMs,
|
||||
highestRouteWeight: highestRouteWeight,
|
||||
).compareTo(
|
||||
_scorePathRecord(
|
||||
a,
|
||||
fastestTripMs: fastestTripMs,
|
||||
highestRouteWeight: highestRouteWeight,
|
||||
),
|
||||
);
|
||||
final scoreCompare =
|
||||
_scorePathRecord(
|
||||
b,
|
||||
fastestTripMs: fastestTripMs,
|
||||
highestRouteWeight: highestRouteWeight,
|
||||
).compareTo(
|
||||
_scorePathRecord(
|
||||
a,
|
||||
fastestTripMs: fastestTripMs,
|
||||
highestRouteWeight: highestRouteWeight,
|
||||
),
|
||||
);
|
||||
if (scoreCompare != 0) {
|
||||
return scoreCompare;
|
||||
}
|
||||
@@ -531,8 +532,7 @@ class PathHistoryService extends ChangeNotifier {
|
||||
(DateTime.now().difference(path.timestamp!).inMinutes /
|
||||
60.0 /
|
||||
24.0));
|
||||
final routeWeight =
|
||||
(path.routeWeight / highestRouteWeight).clamp(0.0, 1.0);
|
||||
final routeWeight = (path.routeWeight / highestRouteWeight).clamp(0.0, 1.0);
|
||||
|
||||
return (reliability * 0.45) +
|
||||
(latency * 0.25) +
|
||||
|
||||
@@ -15,7 +15,9 @@ Uint8List _buildContactFrame({
|
||||
}) {
|
||||
final writer = BytesBuilder();
|
||||
writer.addByte(respCodeContact); // 3
|
||||
writer.add(pubKey ?? Uint8List.fromList(List.generate(32, (i) => i + 1))); // valid pubkey
|
||||
writer.add(
|
||||
pubKey ?? Uint8List.fromList(List.generate(32, (i) => i + 1)),
|
||||
); // valid pubkey
|
||||
writer.addByte(1); // type
|
||||
writer.addByte(0); // flags
|
||||
writer.addByte(pathLen);
|
||||
@@ -239,21 +241,23 @@ void main() {
|
||||
expect(record.routeWeight, equals(4.0));
|
||||
});
|
||||
|
||||
test('fromJson with missing route_weight defaults to 1.0 (backward compat)',
|
||||
() {
|
||||
final json = {
|
||||
'hop_count': 1,
|
||||
'trip_time_ms': 100,
|
||||
'timestamp': DateTime(2024).toIso8601String(),
|
||||
'was_flood': false,
|
||||
'path_bytes': [],
|
||||
'success_count': 0,
|
||||
'failure_count': 0,
|
||||
// 'route_weight' intentionally omitted
|
||||
};
|
||||
final record = PathRecord.fromJson(json);
|
||||
expect(record.routeWeight, equals(1.0));
|
||||
});
|
||||
test(
|
||||
'fromJson with missing route_weight defaults to 1.0 (backward compat)',
|
||||
() {
|
||||
final json = {
|
||||
'hop_count': 1,
|
||||
'trip_time_ms': 100,
|
||||
'timestamp': DateTime(2024).toIso8601String(),
|
||||
'was_flood': false,
|
||||
'path_bytes': [],
|
||||
'success_count': 0,
|
||||
'failure_count': 0,
|
||||
// 'route_weight' intentionally omitted
|
||||
};
|
||||
final record = PathRecord.fromJson(json);
|
||||
expect(record.routeWeight, equals(1.0));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('AppSettings — new fields', () {
|
||||
|
||||
@@ -140,7 +140,11 @@ void main() {
|
||||
attemptIndex: i,
|
||||
maxRetries: 5,
|
||||
);
|
||||
expect(selection.useFlood, isFalse, reason: 'attempt $i should be path');
|
||||
expect(
|
||||
selection.useFlood,
|
||||
isFalse,
|
||||
reason: 'attempt $i should be path',
|
||||
);
|
||||
expect(selection.pathBytes, equals([0x01, 0x02]));
|
||||
}
|
||||
});
|
||||
@@ -400,45 +404,49 @@ void main() {
|
||||
expect(first.pathBytes, equals([0x02]));
|
||||
});
|
||||
|
||||
test('higher route weight wins when other factors are effectively tied', () async {
|
||||
final pubKey = _hex('rank4');
|
||||
final sharedTimestamp =
|
||||
DateTime.now().subtract(const Duration(minutes: 30));
|
||||
storage._store[pubKey] = ContactPathHistory(
|
||||
contactPubKeyHex: pubKey,
|
||||
recentPaths: [
|
||||
PathRecord(
|
||||
hopCount: 1,
|
||||
tripTimeMs: 750,
|
||||
timestamp: sharedTimestamp,
|
||||
wasFloodDiscovery: false,
|
||||
pathBytes: const [0x01],
|
||||
successCount: 1,
|
||||
failureCount: 0,
|
||||
routeWeight: 4.0,
|
||||
),
|
||||
PathRecord(
|
||||
hopCount: 1,
|
||||
tripTimeMs: 750,
|
||||
timestamp: sharedTimestamp,
|
||||
wasFloodDiscovery: false,
|
||||
pathBytes: const [0x02],
|
||||
successCount: 1,
|
||||
failureCount: 0,
|
||||
routeWeight: 1.0,
|
||||
),
|
||||
],
|
||||
);
|
||||
svc.getRecentPaths(pubKey);
|
||||
await _flush();
|
||||
test(
|
||||
'higher route weight wins when other factors are effectively tied',
|
||||
() async {
|
||||
final pubKey = _hex('rank4');
|
||||
final sharedTimestamp = DateTime.now().subtract(
|
||||
const Duration(minutes: 30),
|
||||
);
|
||||
storage._store[pubKey] = ContactPathHistory(
|
||||
contactPubKeyHex: pubKey,
|
||||
recentPaths: [
|
||||
PathRecord(
|
||||
hopCount: 1,
|
||||
tripTimeMs: 750,
|
||||
timestamp: sharedTimestamp,
|
||||
wasFloodDiscovery: false,
|
||||
pathBytes: const [0x01],
|
||||
successCount: 1,
|
||||
failureCount: 0,
|
||||
routeWeight: 4.0,
|
||||
),
|
||||
PathRecord(
|
||||
hopCount: 1,
|
||||
tripTimeMs: 750,
|
||||
timestamp: sharedTimestamp,
|
||||
wasFloodDiscovery: false,
|
||||
pathBytes: const [0x02],
|
||||
successCount: 1,
|
||||
failureCount: 0,
|
||||
routeWeight: 1.0,
|
||||
),
|
||||
],
|
||||
);
|
||||
svc.getRecentPaths(pubKey);
|
||||
await _flush();
|
||||
|
||||
final first = svc.selectPathForAttempt(
|
||||
pubKey,
|
||||
attemptIndex: 0,
|
||||
maxRetries: 5,
|
||||
);
|
||||
expect(first.pathBytes, equals([0x01]));
|
||||
});
|
||||
final first = svc.selectPathForAttempt(
|
||||
pubKey,
|
||||
attemptIndex: 0,
|
||||
maxRetries: 5,
|
||||
);
|
||||
expect(first.pathBytes, equals([0x01]));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user