mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-07-11 19:17:04 +10:00
Multibyte: some path fixes
- Validate encoded path hop counts before saving or sending custom paths - Fix multi-byte path reversal by reversing hops instead of raw bytes - Decode raw log path lengths using packed path_len format - Localize path hash mode options and optimize map anchor lookup
This commit is contained in:
@@ -2598,6 +2598,24 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isPathLenValidForCurrentMode(int pathLen, List<int> pathBytes) {
|
||||||
|
return _isPathLenValidForMode(pathLen, pathBytes, _pathHashByteWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
int? _encodePathLenForCurrentMode(int pathLen, List<int> pathBytes) {
|
||||||
|
if (pathLen < 0 || pathLen == 0xFF) return pathLen;
|
||||||
|
if (!_isPathLenValidForCurrentMode(pathLen, pathBytes)) {
|
||||||
|
appLogger.warn(
|
||||||
|
'Invalid path_len for mode: pathLen=$pathLen, '
|
||||||
|
'bytesLen=${pathBytes.length}, width=$_pathHashByteWidth',
|
||||||
|
tag: 'Connector',
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final mode = (_pathHashByteWidth - 1) & 0x03;
|
||||||
|
return (pathLen & 0x3F) | (mode << 6);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> refreshDeviceInfo() async {
|
Future<void> refreshDeviceInfo() async {
|
||||||
if (!isConnected) return;
|
if (!isConnected) return;
|
||||||
if (PlatformInfo.isWeb &&
|
if (PlatformInfo.isWeb &&
|
||||||
@@ -2797,11 +2815,10 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
try {
|
try {
|
||||||
if (!isConnected) return;
|
if (!isConnected) return;
|
||||||
|
|
||||||
final mode = _pathHashByteWidth - 1;
|
final encodedPathLen = _encodePathLenForCurrentMode(pathLen, customPath);
|
||||||
if (pathLen < 0 || pathLen > 0x3F) {
|
if (encodedPathLen == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final encodedPathLen = pathLen | (mode << 6);
|
|
||||||
await sendFrame(
|
await sendFrame(
|
||||||
buildUpdateContactPathFrame(
|
buildUpdateContactPathFrame(
|
||||||
contact.publicKey,
|
contact.publicKey,
|
||||||
@@ -2863,11 +2880,11 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
: (updatedFlags & ~contactFlagTeleEnv))
|
: (updatedFlags & ~contactFlagTeleEnv))
|
||||||
: updatedFlags;
|
: updatedFlags;
|
||||||
|
|
||||||
final mode = _pathHashByteWidth - 1;
|
final encodedPathLen = _encodePathLenForCurrentMode(
|
||||||
final encodedPathLen =
|
latestContact.pathLength,
|
||||||
(latestContact.pathLength >= 0 && latestContact.pathLength != 0xFF)
|
latestContact.path,
|
||||||
? (latestContact.pathLength | (mode << 6))
|
);
|
||||||
: latestContact.pathLength;
|
if (encodedPathLen == null) return;
|
||||||
await sendFrame(
|
await sendFrame(
|
||||||
buildUpdateContactPathFrame(
|
buildUpdateContactPathFrame(
|
||||||
latestContact.publicKey,
|
latestContact.publicKey,
|
||||||
@@ -2955,6 +2972,19 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
tag: 'Connector',
|
tag: 'Connector',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (pathLen != null &&
|
||||||
|
pathLen >= 0 &&
|
||||||
|
(pathBytes == null ||
|
||||||
|
!_isPathLenValidForCurrentMode(pathLen, pathBytes))) {
|
||||||
|
appLogger.warn(
|
||||||
|
'setPathOverride: invalid path for ${contact.name}: '
|
||||||
|
'pathLen=$pathLen, bytesLen=${pathBytes?.length ?? 0}, '
|
||||||
|
'width=$_pathHashByteWidth',
|
||||||
|
tag: 'Connector',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Update contact with new path override
|
// Update contact with new path override
|
||||||
_contacts[index] = _contacts[index].copyWith(
|
_contacts[index] = _contacts[index].copyWith(
|
||||||
pathOverride: pathLen,
|
pathOverride: pathLen,
|
||||||
@@ -3208,11 +3238,11 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
Future<void> importDiscoveredContact(Contact contact) async {
|
Future<void> importDiscoveredContact(Contact contact) async {
|
||||||
if (!isConnected) return;
|
if (!isConnected) return;
|
||||||
|
|
||||||
final mode = _pathHashByteWidth - 1;
|
final encodedPathLen = _encodePathLenForCurrentMode(
|
||||||
final encodedPathLen =
|
contact.pathLength,
|
||||||
(contact.pathLength >= 0 && contact.pathLength != 0xFF)
|
contact.path,
|
||||||
? (contact.pathLength | (mode << 6))
|
);
|
||||||
: contact.pathLength;
|
if (encodedPathLen == null) return;
|
||||||
await sendFrame(
|
await sendFrame(
|
||||||
buildUpdateContactPathFrame(
|
buildUpdateContactPathFrame(
|
||||||
contact.publicKey,
|
contact.publicKey,
|
||||||
@@ -6535,6 +6565,18 @@ int _decodePathHashWidth(int pathLenRaw) {
|
|||||||
return ((pathLenRaw >> 6) & 0x03) + 1;
|
return ((pathLenRaw >> 6) & 0x03) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isPathLenValidForMode(
|
||||||
|
int pathLen,
|
||||||
|
List<int> pathBytes,
|
||||||
|
int pathHashWidth,
|
||||||
|
) {
|
||||||
|
if (pathLen < 0 || pathLen > 0x3F) return false;
|
||||||
|
final width = pathHashWidth.clamp(1, 4).toInt();
|
||||||
|
final maxHopCountByBytes = maxPathSize ~/ width;
|
||||||
|
if (pathLen > maxHopCountByBytes) return false;
|
||||||
|
return pathBytes.length <= maxPathSize && pathBytes.length == pathLen * width;
|
||||||
|
}
|
||||||
|
|
||||||
Uint8List _reversePathByHop(Uint8List pathBytes, int pathHashWidth) {
|
Uint8List _reversePathByHop(Uint8List pathBytes, int pathHashWidth) {
|
||||||
if (pathBytes.isEmpty) return Uint8List(0);
|
if (pathBytes.isEmpty) return Uint8List(0);
|
||||||
final width = pathHashWidth.clamp(1, 4).toInt();
|
final width = pathHashWidth.clamp(1, 4).toInt();
|
||||||
|
|||||||
+23
-14
@@ -700,6 +700,16 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
int pathHashByteWidth,
|
int pathHashByteWidth,
|
||||||
) {
|
) {
|
||||||
final result = <_GuessedLocation>[];
|
final result = <_GuessedLocation>[];
|
||||||
|
final hopWidth = pathHashByteWidth.clamp(1, 4).toInt();
|
||||||
|
final anchorsByPrefix = <String, List<Contact>>{};
|
||||||
|
for (final repeater in withLocation) {
|
||||||
|
if (repeater.type != advTypeRepeater) continue;
|
||||||
|
if (repeater.publicKey.length < hopWidth) continue;
|
||||||
|
final prefix = PathHelper.formatHopHex(
|
||||||
|
repeater.publicKey.sublist(0, hopWidth),
|
||||||
|
);
|
||||||
|
anchorsByPrefix.putIfAbsent(prefix, () => []).add(repeater);
|
||||||
|
}
|
||||||
|
|
||||||
for (final contact in allContacts) {
|
for (final contact in allContacts) {
|
||||||
if (contact.hasLocation) continue;
|
if (contact.hasLocation) continue;
|
||||||
@@ -722,21 +732,13 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
];
|
];
|
||||||
for (final pathBytes in pathSets) {
|
for (final pathBytes in pathSets) {
|
||||||
if (pathBytes.isEmpty) continue;
|
if (pathBytes.isEmpty) continue;
|
||||||
final hopWidth = pathHashByteWidth.clamp(1, 4);
|
|
||||||
final lastHop = pathBytes.sublist(max(0, pathBytes.length - hopWidth));
|
final lastHop = pathBytes.sublist(max(0, pathBytes.length - hopWidth));
|
||||||
if (lastHop.isEmpty) continue;
|
if (lastHop.isEmpty) continue;
|
||||||
|
|
||||||
for (final repeater in withLocation) {
|
final repeaters = anchorsByPrefix[PathHelper.formatHopHex(lastHop)];
|
||||||
if (repeater.type != advTypeRepeater) continue;
|
if (repeaters != null && repeaters.isNotEmpty) {
|
||||||
if (repeater.publicKey.length < lastHop.length) continue;
|
final repeater = repeaters.first;
|
||||||
if (!listEquals(
|
|
||||||
repeater.publicKey.sublist(0, lastHop.length),
|
|
||||||
lastHop,
|
|
||||||
)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
anchorSet.add(LatLng(repeater.latitude!, repeater.longitude!));
|
anchorSet.add(LatLng(repeater.latitude!, repeater.longitude!));
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2304,10 +2306,17 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
connector.pathHashByteWidth.clamp(1, pubKeySize),
|
connector.pathHashByteWidth.clamp(1, pubKeySize),
|
||||||
contact.publicKey.length,
|
contact.publicKey.length,
|
||||||
).toInt();
|
).toInt();
|
||||||
|
final hopPrefix = contact.publicKey.sublist(0, hopWidth);
|
||||||
|
for (final existingHop in PathHelper.splitPathBytes(
|
||||||
|
_pathTrace,
|
||||||
|
connector.pathHashByteWidth,
|
||||||
|
)) {
|
||||||
|
if (listEquals(existingHop, hopPrefix)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_pathTrace.addAll(
|
_pathTrace.addAll(hopPrefix); // Add the hop-width pubkey prefix.
|
||||||
contact.publicKey.sublist(0, hopWidth),
|
|
||||||
); // Add the hop-width prefix of the public key to the trace
|
|
||||||
_pathTraceHopWidths.add(hopWidth);
|
_pathTraceHopWidths.add(hopWidth);
|
||||||
_pathTraceContacts.add(
|
_pathTraceContacts.add(
|
||||||
contact.copyWith(
|
contact.copyWith(
|
||||||
|
|||||||
@@ -580,11 +580,23 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
labelText: l10n.repeater_pathHashMode,
|
labelText: l10n.repeater_pathHashMode,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
items: const [
|
items: [
|
||||||
DropdownMenuItem(value: 0, child: Text('0 - 1 byte')),
|
DropdownMenuItem(
|
||||||
DropdownMenuItem(value: 1, child: Text('1 - 2 bytes')),
|
value: 0,
|
||||||
DropdownMenuItem(value: 2, child: Text('2 - 3 bytes')),
|
child: Text(l10n.repeater_pathHashModeOption0),
|
||||||
DropdownMenuItem(value: 3, child: Text('3 - 4 bytes')),
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: 1,
|
||||||
|
child: Text(l10n.repeater_pathHashModeOption1),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: 2,
|
||||||
|
child: Text(l10n.repeater_pathHashModeOption2),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: 3,
|
||||||
|
child: Text(l10n.repeater_pathHashModeOption3),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
|
|||||||
@@ -126,7 +126,8 @@ class _PathSelectionDialogState extends State<PathSelectionDialog> {
|
|||||||
.toList();
|
.toList();
|
||||||
final pathBytesList = <int>[];
|
final pathBytesList = <int>[];
|
||||||
final invalidPrefixes = <String>[];
|
final invalidPrefixes = <String>[];
|
||||||
final hexCharsPerHop = widget.pathHashByteWidth.clamp(1, 4) * 2;
|
final pathHashByteWidth = widget.pathHashByteWidth.clamp(1, 4).toInt();
|
||||||
|
final hexCharsPerHop = pathHashByteWidth * 2;
|
||||||
|
|
||||||
for (final id in pathIds) {
|
for (final id in pathIds) {
|
||||||
if (id.length < hexCharsPerHop) {
|
if (id.length < hexCharsPerHop) {
|
||||||
@@ -157,8 +158,12 @@ class _PathSelectionDialogState extends State<PathSelectionDialog> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check max path size in bytes, as defined by the protocol.
|
final hopCount = pathBytesList.length ~/ pathHashByteWidth;
|
||||||
if (pathBytesList.length > maxPathSize) {
|
final maxHopCountByBytes = maxPathSize ~/ pathHashByteWidth;
|
||||||
|
final maxHopCount = maxHopCountByBytes < 0x3F ? maxHopCountByBytes : 0x3F;
|
||||||
|
|
||||||
|
// path_len stores hop count in 6 bits and the path buffer is byte-limited.
|
||||||
|
if (hopCount > maxHopCount || pathBytesList.length > maxPathSize) {
|
||||||
showDismissibleSnackBar(
|
showDismissibleSnackBar(
|
||||||
context,
|
context,
|
||||||
content: Text(l10n.path_tooLong),
|
content: Text(l10n.path_tooLong),
|
||||||
@@ -235,7 +240,7 @@ class _PathSelectionDialogState extends State<PathSelectionDialog> {
|
|||||||
helperText: l10n.path_helperMaxHops,
|
helperText: l10n.path_helperMaxHops,
|
||||||
),
|
),
|
||||||
textCapitalization: TextCapitalization.characters,
|
textCapitalization: TextCapitalization.characters,
|
||||||
maxLength: 191, // 64 hops * 2 chars + 63 commas
|
maxLength: 188, // 63 one-byte hops * 2 chars + 62 commas
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
|||||||
Reference in New Issue
Block a user