formatted code

This commit is contained in:
zjs81
2026-03-20 01:55:08 -07:00
parent 4962a48e64
commit 4ad4a93a20
5 changed files with 143 additions and 116 deletions
+51 -43
View File
@@ -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;
+12 -5
View File
@@ -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;
+13 -13
View File
@@ -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) +
+20 -16
View File
@@ -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', () {
+47 -39
View File
@@ -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]));
}); },
);
}); });
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------