mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-07-17 06:10:47 +10:00
Multibyte: fix map
This commit is contained in:
@@ -2864,7 +2864,8 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
: updatedFlags;
|
: updatedFlags;
|
||||||
|
|
||||||
final mode = _pathHashByteWidth - 1;
|
final mode = _pathHashByteWidth - 1;
|
||||||
final encodedPathLen = (latestContact.pathLength >= 0 && latestContact.pathLength != 0xFF)
|
final encodedPathLen =
|
||||||
|
(latestContact.pathLength >= 0 && latestContact.pathLength != 0xFF)
|
||||||
? (latestContact.pathLength | (mode << 6))
|
? (latestContact.pathLength | (mode << 6))
|
||||||
: latestContact.pathLength;
|
: latestContact.pathLength;
|
||||||
await sendFrame(
|
await sendFrame(
|
||||||
@@ -3208,7 +3209,8 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
if (!isConnected) return;
|
if (!isConnected) return;
|
||||||
|
|
||||||
final mode = _pathHashByteWidth - 1;
|
final mode = _pathHashByteWidth - 1;
|
||||||
final encodedPathLen = (contact.pathLength >= 0 && contact.pathLength != 0xFF)
|
final encodedPathLen =
|
||||||
|
(contact.pathLength >= 0 && contact.pathLength != 0xFF)
|
||||||
? (contact.pathLength | (mode << 6))
|
? (contact.pathLength | (mode << 6))
|
||||||
: contact.pathLength;
|
: contact.pathLength;
|
||||||
await sendFrame(
|
await sendFrame(
|
||||||
@@ -6089,7 +6091,9 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
publicKey: publicKey,
|
publicKey: publicKey,
|
||||||
name: name,
|
name: name,
|
||||||
type: type,
|
type: type,
|
||||||
pathLength: pathBytes.isEmpty ? -1 : (pathBytes.length ~/ pathHashWidth),
|
pathLength: pathBytes.isEmpty
|
||||||
|
? -1
|
||||||
|
: (pathBytes.length ~/ pathHashWidth),
|
||||||
path: Uint8List.fromList(
|
path: Uint8List.fromList(
|
||||||
pathBytes.reversed.toList(),
|
pathBytes.reversed.toList(),
|
||||||
), // Store path in reverse for easier use in outgoing messages
|
), // Store path in reverse for easier use in outgoing messages
|
||||||
|
|||||||
@@ -301,8 +301,7 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
),
|
),
|
||||||
buildDefaultDragHandles: false,
|
buildDefaultDragHandles: false,
|
||||||
itemCount: filteredChannels.length,
|
itemCount: filteredChannels.length,
|
||||||
onReorder: (oldIndex, newIndex) {
|
onReorderItem: (oldIndex, newIndex) {
|
||||||
if (newIndex > oldIndex) newIndex -= 1;
|
|
||||||
final reordered = List<Channel>.from(
|
final reordered = List<Channel>.from(
|
||||||
filteredChannels,
|
filteredChannels,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
bool _hasInitializedMap = false;
|
bool _hasInitializedMap = false;
|
||||||
bool _removedMarkersLoaded = false;
|
bool _removedMarkersLoaded = false;
|
||||||
final List<int> _pathTrace = [];
|
final List<int> _pathTrace = [];
|
||||||
|
final List<int> _pathTraceHopWidths = [];
|
||||||
final List<Contact> _pathTraceContacts = [];
|
final List<Contact> _pathTraceContacts = [];
|
||||||
final List<LatLng> _points = [];
|
final List<LatLng> _points = [];
|
||||||
final List<Polyline> _polylines = [];
|
final List<Polyline> _polylines = [];
|
||||||
@@ -2302,11 +2303,12 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
final hopWidth = min(
|
final hopWidth = min(
|
||||||
connector.pathHashByteWidth.clamp(1, pubKeySize),
|
connector.pathHashByteWidth.clamp(1, pubKeySize),
|
||||||
contact.publicKey.length,
|
contact.publicKey.length,
|
||||||
);
|
).toInt();
|
||||||
setState(() {
|
setState(() {
|
||||||
_pathTrace.addAll(
|
_pathTrace.addAll(
|
||||||
contact.publicKey.sublist(0, hopWidth),
|
contact.publicKey.sublist(0, hopWidth),
|
||||||
); // Add the hop-width prefix of the public key to the trace
|
); // Add the hop-width prefix of the public key to the trace
|
||||||
|
_pathTraceHopWidths.add(hopWidth);
|
||||||
_pathTraceContacts.add(
|
_pathTraceContacts.add(
|
||||||
contact.copyWith(
|
contact.copyWith(
|
||||||
latitude: position?.latitude ?? contact.latitude,
|
latitude: position?.latitude ?? contact.latitude,
|
||||||
@@ -2321,6 +2323,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_isBuildingPathTrace = true;
|
_isBuildingPathTrace = true;
|
||||||
_pathTrace.clear();
|
_pathTrace.clear();
|
||||||
|
_pathTraceHopWidths.clear();
|
||||||
_pathTraceContacts.clear();
|
_pathTraceContacts.clear();
|
||||||
_points.clear();
|
_points.clear();
|
||||||
_polylines.clear();
|
_polylines.clear();
|
||||||
@@ -2330,8 +2333,19 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
|
|
||||||
void _removePath() {
|
void _removePath() {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
final recordedHopWidth = _pathTraceHopWidths.isNotEmpty
|
||||||
|
? _pathTraceHopWidths.removeLast()
|
||||||
|
: context.read<MeshCoreConnector>().pathHashByteWidth.clamp(
|
||||||
|
1,
|
||||||
|
pubKeySize,
|
||||||
|
);
|
||||||
|
final hopByteCount = min(recordedHopWidth, _pathTrace.length).toInt();
|
||||||
_pathTraceContacts.removeLast();
|
_pathTraceContacts.removeLast();
|
||||||
_pathTrace.removeLast(); // Remove last node from path trace
|
// A path trace hop can be wider than one byte; remove the full hash prefix.
|
||||||
|
_pathTrace.removeRange(
|
||||||
|
_pathTrace.length - hopByteCount,
|
||||||
|
_pathTrace.length,
|
||||||
|
);
|
||||||
_points.removeLast(); // Remove last point from points list
|
_points.removeLast(); // Remove last point from points list
|
||||||
_polylines.clear(); // Clear polylines
|
_polylines.clear(); // Clear polylines
|
||||||
});
|
});
|
||||||
@@ -2445,6 +2459,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_isBuildingPathTrace = false;
|
_isBuildingPathTrace = false;
|
||||||
_pathTrace.clear();
|
_pathTrace.clear();
|
||||||
|
_pathTraceHopWidths.clear();
|
||||||
_points.clear();
|
_points.clear();
|
||||||
_polylines.clear();
|
_polylines.clear();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -115,9 +115,10 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||||||
Uint8List? _sentTagBytes;
|
Uint8List? _sentTagBytes;
|
||||||
|
|
||||||
String _formatPathPrefixes(Uint8List pathBytes) {
|
String _formatPathPrefixes(Uint8List pathBytes) {
|
||||||
return PathHelper.splitPathBytes(pathBytes, widget.pathHashByteWidth)
|
return PathHelper.splitPathBytes(
|
||||||
.map(PathHelper.formatHopHex)
|
pathBytes,
|
||||||
.join(',');
|
widget.pathHashByteWidth,
|
||||||
|
).map(PathHelper.formatHopHex).join(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -327,7 +328,8 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||||||
// Check if it's a binary response
|
// Check if it's a binary response
|
||||||
if (frame.length >= 12 &&
|
if (frame.length >= 12 &&
|
||||||
code == pushCodeTraceData &&
|
code == pushCodeTraceData &&
|
||||||
(listEquals(frame.sublist(4, 8), _sentTagBytes) || listEquals(frame.sublist(4, 8), tagData))) {
|
(listEquals(frame.sublist(4, 8), _sentTagBytes) ||
|
||||||
|
listEquals(frame.sublist(4, 8), tagData))) {
|
||||||
_timeoutTimer?.cancel();
|
_timeoutTimer?.cancel();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
_handleTraceResponse(frame);
|
_handleTraceResponse(frame);
|
||||||
@@ -370,10 +372,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||||||
buffer.skipBytes(5); // Skip Flag byte and tag data
|
buffer.skipBytes(5); // Skip Flag byte and tag data
|
||||||
buffer.skipBytes(4); // Skip auth code
|
buffer.skipBytes(4); // Skip auth code
|
||||||
final pathBytes = buffer.readBytes(pathLength);
|
final pathBytes = buffer.readBytes(pathLength);
|
||||||
final pathData = PathHelper.splitPathBytes(
|
final pathData = PathHelper.splitPathBytes(pathBytes, width);
|
||||||
pathBytes,
|
|
||||||
width,
|
|
||||||
);
|
|
||||||
List<double> snrData = buffer
|
List<double> snrData = buffer
|
||||||
.readRemainingBytes()
|
.readRemainingBytes()
|
||||||
.map((snr) => snr.toSigned(8).toDouble() / 4)
|
.map((snr) => snr.toSigned(8).toDouble() / 4)
|
||||||
@@ -414,7 +413,10 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||||||
for (final repeaterData in pathData) {
|
for (final repeaterData in pathData) {
|
||||||
final hopWidth = repeaterData.length;
|
final hopWidth = repeaterData.length;
|
||||||
if (repeater.publicKey.length < hopWidth) continue;
|
if (repeater.publicKey.length < hopWidth) continue;
|
||||||
if (listEquals(repeater.publicKey.sublist(0, hopWidth), repeaterData)) {
|
if (listEquals(
|
||||||
|
repeater.publicKey.sublist(0, hopWidth),
|
||||||
|
repeaterData,
|
||||||
|
)) {
|
||||||
pathContacts[_hopKey(repeaterData)] = repeater;
|
pathContacts[_hopKey(repeaterData)] = repeater;
|
||||||
lastContact = repeater;
|
lastContact = repeater;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ void main() {
|
|||||||
expect(PathHelper.formatHopHex(hops.first), equals('A1A2'));
|
expect(PathHelper.formatHopHex(hops.first), equals('A1A2'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('resolvePathNames ignores chat nodes and keeps repeater/room nodes with 1-byte paths', () {
|
test(
|
||||||
|
'resolvePathNames ignores chat nodes and keeps repeater/room nodes with 1-byte paths',
|
||||||
|
() {
|
||||||
final contacts = [
|
final contacts = [
|
||||||
_contact(firstByte: 0xF2, name: 'MunTui', type: advTypeChat),
|
_contact(firstByte: 0xF2, name: 'MunTui', type: advTypeChat),
|
||||||
_contact(firstByte: 0x7E, name: 'zrepeater', type: advTypeRepeater),
|
_contact(firstByte: 0x7E, name: 'zrepeater', type: advTypeRepeater),
|
||||||
@@ -45,7 +47,8 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(resolved, equals('F2 → zrepeater → USS Ronald Reagan'));
|
expect(resolved, equals('F2 → zrepeater → USS Ronald Reagan'));
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test('resolvePathNames supports multi-byte paths', () {
|
test('resolvePathNames supports multi-byte paths', () {
|
||||||
final contacts = [
|
final contacts = [
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
import 'package:meshcore_open/connector/meshcore_protocol.dart';
|
import 'package:meshcore_open/connector/meshcore_protocol.dart';
|
||||||
import 'package:meshcore_open/models/channel_message.dart';
|
import 'package:meshcore_open/models/channel_message.dart';
|
||||||
import 'package:meshcore_open/models/contact.dart';
|
import 'package:meshcore_open/models/contact.dart';
|
||||||
import 'package:meshcore_open/models/message.dart';
|
|
||||||
import 'package:meshcore_open/models/path_history.dart';
|
import 'package:meshcore_open/models/path_history.dart';
|
||||||
import 'package:meshcore_open/models/app_settings.dart';
|
import 'package:meshcore_open/models/app_settings.dart';
|
||||||
import 'package:meshcore_open/storage/prefs_manager.dart';
|
import 'package:meshcore_open/storage/prefs_manager.dart';
|
||||||
@@ -65,9 +64,7 @@ Uint8List _buildChannelMessageFrameV3({
|
|||||||
|
|
||||||
if (hasPath && hopCount > 0) {
|
if (hasPath && hopCount > 0) {
|
||||||
writer.add(
|
writer.add(
|
||||||
Uint8List.fromList(
|
Uint8List.fromList(List.generate(hopCount * pathHashWidth, (i) => i + 1)),
|
||||||
List.generate(hopCount * pathHashWidth, (i) => i + 1),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +78,8 @@ Uint8List _buildChannelMessageFrameV3({
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('ChannelMessage.fromFrame — V3 packed path decoding', () {
|
group('ChannelMessage.fromFrame — V3 packed path decoding', () {
|
||||||
test('hasPath reads width, hop count, path bytes, and txtType correctly',
|
test(
|
||||||
|
'hasPath reads width, hop count, path bytes, and txtType correctly',
|
||||||
() {
|
() {
|
||||||
final frame = _buildChannelMessageFrameV3(
|
final frame = _buildChannelMessageFrameV3(
|
||||||
pathHashWidth: 2,
|
pathHashWidth: 2,
|
||||||
@@ -97,7 +95,8 @@ void main() {
|
|||||||
expect(message.pathBytes.length, equals(6));
|
expect(message.pathBytes.length, equals(6));
|
||||||
expect(message.senderName, equals('Alice'));
|
expect(message.senderName, equals('Alice'));
|
||||||
expect(message.text, equals('Hello world'));
|
expect(message.text, equals('Hello world'));
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test('no path still reads width, hop count, and txtType correctly', () {
|
test('no path still reads width, hop count, and txtType correctly', () {
|
||||||
final frame = _buildChannelMessageFrameV3(
|
final frame = _buildChannelMessageFrameV3(
|
||||||
@@ -473,7 +472,9 @@ void main() {
|
|||||||
await PrefsManager.initialize();
|
await PrefsManager.initialize();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ContactStore decodes and migrates legacy mode-encoded paths', () async {
|
test(
|
||||||
|
'ContactStore decodes and migrates legacy mode-encoded paths',
|
||||||
|
() async {
|
||||||
final store = ContactStore()..publicKeyHex = '1234567890';
|
final store = ContactStore()..publicKeyHex = '1234567890';
|
||||||
|
|
||||||
// Let's create a contact with legacy 64 path length (mode 1, 0 hops)
|
// Let's create a contact with legacy 64 path length (mode 1, 0 hops)
|
||||||
@@ -489,7 +490,7 @@ void main() {
|
|||||||
'lastSeen': DateTime.now().millisecondsSinceEpoch,
|
'lastSeen': DateTime.now().millisecondsSinceEpoch,
|
||||||
'lastMessageAt': DateTime.now().millisecondsSinceEpoch,
|
'lastMessageAt': DateTime.now().millisecondsSinceEpoch,
|
||||||
'isActive': true,
|
'isActive': true,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
@@ -499,13 +500,18 @@ void main() {
|
|||||||
expect(contacts, hasLength(1));
|
expect(contacts, hasLength(1));
|
||||||
expect(contacts.first.pathLength, equals(0));
|
expect(contacts.first.pathLength, equals(0));
|
||||||
expect(contacts.first.path, isEmpty);
|
expect(contacts.first.path, isEmpty);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test('ContactStore decodes and migrates legacy mode-1 paths with hops', () async {
|
test(
|
||||||
|
'ContactStore decodes and migrates legacy mode-1 paths with hops',
|
||||||
|
() async {
|
||||||
final store = ContactStore()..publicKeyHex = '1234567890';
|
final store = ContactStore()..publicKeyHex = '1234567890';
|
||||||
|
|
||||||
// Contact with legacy 65 path length (mode 1, 1 hop)
|
// Contact with legacy 65 path length (mode 1, 1 hop)
|
||||||
final rawPath = Uint8List(64)..[0] = 0xBB..[1] = 0xCC;
|
final rawPath = Uint8List(64)
|
||||||
|
..[0] = 0xBB
|
||||||
|
..[1] = 0xCC;
|
||||||
final contactJson = [
|
final contactJson = [
|
||||||
{
|
{
|
||||||
'publicKey': base64Encode(Uint8List(32)..[0] = 0xAA),
|
'publicKey': base64Encode(Uint8List(32)..[0] = 0xAA),
|
||||||
@@ -517,7 +523,7 @@ void main() {
|
|||||||
'lastSeen': DateTime.now().millisecondsSinceEpoch,
|
'lastSeen': DateTime.now().millisecondsSinceEpoch,
|
||||||
'lastMessageAt': DateTime.now().millisecondsSinceEpoch,
|
'lastMessageAt': DateTime.now().millisecondsSinceEpoch,
|
||||||
'isActive': true,
|
'isActive': true,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
@@ -527,9 +533,12 @@ void main() {
|
|||||||
expect(contacts, hasLength(1));
|
expect(contacts, hasLength(1));
|
||||||
expect(contacts.first.pathLength, equals(1));
|
expect(contacts.first.pathLength, equals(1));
|
||||||
expect(contacts.first.path, equals(Uint8List.fromList([0xBB, 0xCC])));
|
expect(contacts.first.path, equals(Uint8List.fromList([0xBB, 0xCC])));
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test('MessageStore decodes and migrates legacy mode-encoded message paths', () async {
|
test(
|
||||||
|
'MessageStore decodes and migrates legacy mode-encoded message paths',
|
||||||
|
() async {
|
||||||
final store = MessageStore()..publicKeyHex = '1234567890';
|
final store = MessageStore()..publicKeyHex = '1234567890';
|
||||||
final contactKeyHex = pubKeyToHex(Uint8List(32)..[0] = 0xAA);
|
final contactKeyHex = pubKeyToHex(Uint8List(32)..[0] = 0xAA);
|
||||||
|
|
||||||
@@ -544,23 +553,31 @@ void main() {
|
|||||||
'messageId': 'msg_1',
|
'messageId': 'msg_1',
|
||||||
'pathLength': 64, // encoded pathLength (mode 1, 0 hops)
|
'pathLength': 64, // encoded pathLength (mode 1, 0 hops)
|
||||||
'pathBytes': base64Encode(rawPath),
|
'pathBytes': base64Encode(rawPath),
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
await prefs.setString('${store.keyFor}$contactKeyHex', jsonEncode(messageJson));
|
await prefs.setString(
|
||||||
|
'${store.keyFor}$contactKeyHex',
|
||||||
|
jsonEncode(messageJson),
|
||||||
|
);
|
||||||
|
|
||||||
final messages = await store.loadMessages(contactKeyHex);
|
final messages = await store.loadMessages(contactKeyHex);
|
||||||
expect(messages, hasLength(1));
|
expect(messages, hasLength(1));
|
||||||
expect(messages.first.pathLength, equals(0));
|
expect(messages.first.pathLength, equals(0));
|
||||||
expect(messages.first.pathBytes, isEmpty);
|
expect(messages.first.pathBytes, isEmpty);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test('ChannelMessageStore decodes and migrates legacy mode-encoded paths', () async {
|
test(
|
||||||
|
'ChannelMessageStore decodes and migrates legacy mode-encoded paths',
|
||||||
|
() async {
|
||||||
final store = ChannelMessageStore()..publicKeyHex = '1234567890';
|
final store = ChannelMessageStore()..publicKeyHex = '1234567890';
|
||||||
final channelIndex = 1;
|
final channelIndex = 1;
|
||||||
|
|
||||||
final rawPath = Uint8List(64)..[0] = 0xBB..[1] = 0xCC;
|
final rawPath = Uint8List(64)
|
||||||
|
..[0] = 0xBB
|
||||||
|
..[1] = 0xCC;
|
||||||
final messageJson = [
|
final messageJson = [
|
||||||
{
|
{
|
||||||
'senderName': 'Alice',
|
'senderName': 'Alice',
|
||||||
@@ -571,23 +588,34 @@ void main() {
|
|||||||
'channelIndex': channelIndex,
|
'channelIndex': channelIndex,
|
||||||
'pathLength': 65, // encoded pathLength (mode 1, 1 hop)
|
'pathLength': 65, // encoded pathLength (mode 1, 1 hop)
|
||||||
'pathBytes': base64Encode(rawPath),
|
'pathBytes': base64Encode(rawPath),
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
await prefs.setString('${store.keyFor}$channelIndex', jsonEncode(messageJson));
|
await prefs.setString(
|
||||||
|
'${store.keyFor}$channelIndex',
|
||||||
|
jsonEncode(messageJson),
|
||||||
|
);
|
||||||
|
|
||||||
final messages = await store.loadChannelMessages(channelIndex);
|
final messages = await store.loadChannelMessages(channelIndex);
|
||||||
expect(messages, hasLength(1));
|
expect(messages, hasLength(1));
|
||||||
expect(messages.first.pathLength, equals(1));
|
expect(messages.first.pathLength, equals(1));
|
||||||
expect(messages.first.pathBytes, equals(Uint8List.fromList([0xBB, 0xCC])));
|
expect(
|
||||||
|
messages.first.pathBytes,
|
||||||
|
equals(Uint8List.fromList([0xBB, 0xCC])),
|
||||||
|
);
|
||||||
expect(messages.first.pathHashWidth, equals(2));
|
expect(messages.first.pathHashWidth, equals(2));
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test('ContactDiscoveryStore decodes and migrates legacy mode-encoded paths', () async {
|
test(
|
||||||
|
'ContactDiscoveryStore decodes and migrates legacy mode-encoded paths',
|
||||||
|
() async {
|
||||||
final store = ContactDiscoveryStore();
|
final store = ContactDiscoveryStore();
|
||||||
|
|
||||||
final rawPath = Uint8List(64)..[0] = 0x11..[1] = 0x22;
|
final rawPath = Uint8List(64)
|
||||||
|
..[0] = 0x11
|
||||||
|
..[1] = 0x22;
|
||||||
final contactJson = [
|
final contactJson = [
|
||||||
{
|
{
|
||||||
'publicKey': base64Encode(Uint8List(32)..[0] = 0xBB),
|
'publicKey': base64Encode(Uint8List(32)..[0] = 0xBB),
|
||||||
@@ -598,7 +626,7 @@ void main() {
|
|||||||
'path': base64Encode(rawPath),
|
'path': base64Encode(rawPath),
|
||||||
'lastSeen': DateTime.now().millisecondsSinceEpoch,
|
'lastSeen': DateTime.now().millisecondsSinceEpoch,
|
||||||
'lastMessageAt': DateTime.now().millisecondsSinceEpoch,
|
'lastMessageAt': DateTime.now().millisecondsSinceEpoch,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
@@ -608,6 +636,7 @@ void main() {
|
|||||||
expect(contacts, hasLength(1));
|
expect(contacts, hasLength(1));
|
||||||
expect(contacts.first.pathLength, equals(1));
|
expect(contacts.first.pathLength, equals(1));
|
||||||
expect(contacts.first.path, equals(Uint8List.fromList([0x11, 0x22])));
|
expect(contacts.first.path, equals(Uint8List.fromList([0x11, 0x22])));
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ class _FakeMeshCoreConnector extends MeshCoreConnector {
|
|||||||
Stream<Uint8List> get receivedFrames => _receivedFramesController.stream;
|
Stream<Uint8List> get receivedFrames => _receivedFramesController.stream;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Uint8List? get selfPublicKey => Uint8List.fromList(List.generate(32, (i) => i));
|
Uint8List? get selfPublicKey =>
|
||||||
|
Uint8List.fromList(List.generate(32, (i) => i));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
double? get selfLatitude => 48.8566;
|
double? get selfLatitude => 48.8566;
|
||||||
@@ -60,9 +61,7 @@ Widget _buildTestApp({
|
|||||||
ChangeNotifierProvider<AppSettingsService>(
|
ChangeNotifierProvider<AppSettingsService>(
|
||||||
create: (_) => AppSettingsService(),
|
create: (_) => AppSettingsService(),
|
||||||
),
|
),
|
||||||
Provider<MapTileCacheService>(
|
Provider<MapTileCacheService>(create: (_) => MapTileCacheService()),
|
||||||
create: (_) => MapTileCacheService(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
child: MaterialApp(
|
child: MaterialApp(
|
||||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
@@ -73,7 +72,9 @@ Widget _buildTestApp({
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('PathTraceMapScreen parses response frames correctly', (tester) async {
|
testWidgets('PathTraceMapScreen parses response frames correctly', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final connector = _FakeMeshCoreConnector();
|
final connector = _FakeMeshCoreConnector();
|
||||||
|
|
||||||
final screen = PathTraceMapScreen(
|
final screen = PathTraceMapScreen(
|
||||||
@@ -135,7 +136,9 @@ void main() {
|
|||||||
expect(find.text('Path trace not available.'), findsNothing);
|
expect(find.text('Path trace not available.'), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('PathTraceMapScreen parses multi-byte encoded path trace response correctly', (tester) async {
|
testWidgets(
|
||||||
|
'PathTraceMapScreen parses multi-byte encoded path trace response correctly',
|
||||||
|
(tester) async {
|
||||||
final connector = _FakeMeshCoreConnector();
|
final connector = _FakeMeshCoreConnector();
|
||||||
|
|
||||||
final screen = PathTraceMapScreen(
|
final screen = PathTraceMapScreen(
|
||||||
@@ -144,7 +147,9 @@ void main() {
|
|||||||
pathHashByteWidth: 2,
|
pathHashByteWidth: 2,
|
||||||
);
|
);
|
||||||
|
|
||||||
await tester.pumpWidget(_buildTestApp(connector: connector, child: screen));
|
await tester.pumpWidget(
|
||||||
|
_buildTestApp(connector: connector, child: screen),
|
||||||
|
);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
// Verify a send request was made
|
// Verify a send request was made
|
||||||
@@ -192,9 +197,12 @@ void main() {
|
|||||||
|
|
||||||
// Verify it parsed correctly (should not show the unavailable message)
|
// Verify it parsed correctly (should not show the unavailable message)
|
||||||
expect(find.text('Path trace not available.'), findsNothing);
|
expect(find.text('Path trace not available.'), findsNothing);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
testWidgets('PathTraceMapScreen buildPath: Direct repeater ping (0-hop)', (tester) async {
|
testWidgets('PathTraceMapScreen buildPath: Direct repeater ping (0-hop)', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final connector = _FakeMeshCoreConnector();
|
final connector = _FakeMeshCoreConnector();
|
||||||
final target = Contact(
|
final target = Contact(
|
||||||
publicKey: Uint8List.fromList([0xAA, 0xBB, 0xCC]),
|
publicKey: Uint8List.fromList([0xAA, 0xBB, 0xCC]),
|
||||||
@@ -223,7 +231,9 @@ void main() {
|
|||||||
expect(payload, Uint8List.fromList([0xAA]));
|
expect(payload, Uint8List.fromList([0xAA]));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('PathTraceMapScreen buildPath: 1-hop chat contact trace', (tester) async {
|
testWidgets('PathTraceMapScreen buildPath: 1-hop chat contact trace', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final connector = _FakeMeshCoreConnector();
|
final connector = _FakeMeshCoreConnector();
|
||||||
final target = Contact(
|
final target = Contact(
|
||||||
publicKey: Uint8List.fromList([0xCC, 0xDD, 0xEE]),
|
publicKey: Uint8List.fromList([0xCC, 0xDD, 0xEE]),
|
||||||
@@ -252,7 +262,9 @@ void main() {
|
|||||||
expect(payload, Uint8List.fromList([0xBB, 0xCC, 0xBB]));
|
expect(payload, Uint8List.fromList([0xBB, 0xCC, 0xBB]));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('PathTraceMapScreen buildPath: Multi-hop chat contact trace', (tester) async {
|
testWidgets('PathTraceMapScreen buildPath: Multi-hop chat contact trace', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final connector = _FakeMeshCoreConnector();
|
final connector = _FakeMeshCoreConnector();
|
||||||
final target = Contact(
|
final target = Contact(
|
||||||
publicKey: Uint8List.fromList([0x33, 0x44, 0x55]),
|
publicKey: Uint8List.fromList([0x33, 0x44, 0x55]),
|
||||||
@@ -281,7 +293,9 @@ void main() {
|
|||||||
expect(payload, Uint8List.fromList([0x11, 0x22, 0x33, 0x22, 0x11]));
|
expect(payload, Uint8List.fromList([0x11, 0x22, 0x33, 0x22, 0x11]));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('PathTraceMapScreen buildPath: Map trace with null target contact', (tester) async {
|
testWidgets(
|
||||||
|
'PathTraceMapScreen buildPath: Map trace with null target contact',
|
||||||
|
(tester) async {
|
||||||
final connector = _FakeMeshCoreConnector();
|
final connector = _FakeMeshCoreConnector();
|
||||||
|
|
||||||
final screen = PathTraceMapScreen(
|
final screen = PathTraceMapScreen(
|
||||||
@@ -292,7 +306,9 @@ void main() {
|
|||||||
pathHashByteWidth: 1,
|
pathHashByteWidth: 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
await tester.pumpWidget(_buildTestApp(connector: connector, child: screen));
|
await tester.pumpWidget(
|
||||||
|
_buildTestApp(connector: connector, child: screen),
|
||||||
|
);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
expect(connector.sentFrames.length, 1);
|
expect(connector.sentFrames.length, 1);
|
||||||
@@ -300,9 +316,12 @@ void main() {
|
|||||||
expect(sentFrame[0], cmdSendTracePath);
|
expect(sentFrame[0], cmdSendTracePath);
|
||||||
final payload = sentFrame.sublist(10);
|
final payload = sentFrame.sublist(10);
|
||||||
expect(payload, Uint8List.fromList([0x11, 0x22, 0x11]));
|
expect(payload, Uint8List.fromList([0x11, 0x22, 0x11]));
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
testWidgets('PathTraceMapScreen parses raw byte length fallback correctly', (tester) async {
|
testWidgets('PathTraceMapScreen parses raw byte length fallback correctly', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final connector = _FakeMeshCoreConnector();
|
final connector = _FakeMeshCoreConnector();
|
||||||
|
|
||||||
final screen = PathTraceMapScreen(
|
final screen = PathTraceMapScreen(
|
||||||
|
|||||||
Reference in New Issue
Block a user