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