From c2671ac2aea8edcf5e44f12cfd070a851e9096eb Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Sat, 7 Mar 2026 01:23:46 -0800 Subject: [PATCH 1/2] Refactor data handling of contacts (#267) * Refactor data handling in MeshCoreConnector and BufferReader for improved readability and efficiency * Update lib/connector/meshcore_connector.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix pointer tracking in BufferReader's readCString method --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/connector/meshcore_connector.dart | 53 +++++++++++++++------------ lib/connector/meshcore_protocol.dart | 28 +++++++++++++- lib/models/contact.dart | 39 ++++++++++---------- 3 files changed, 76 insertions(+), 44 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index c00b4ca..75b5287 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -3820,40 +3820,40 @@ class MeshCoreConnector extends ChangeNotifier { void _handleRxData(Uint8List frame) { final packet = BufferReader(frame); - double snr = 0.0; - int routeType = 0; - int payloadType = 0; - Uint8List pathBytes = Uint8List(0); - Uint8List payload = Uint8List(0); try { packet.skipBytes(1); // Skip frame type byte - snr = packet.readInt8() / 4.0; + final snr = packet.readInt8() / 4.0; packet.skipBytes(1); // Skip RSSI byte //final rssi = packet.readByte(); final header = packet.readByte(); - routeType = header & 0x03; - payloadType = (header >> 2) & 0x0F; + final routeType = header & 0x03; + final payloadType = (header >> 2) & 0x0F; + if (routeType == _routeTransportFlood || + routeType == _routeTransportDirect) { + packet.skipBytes(4); // Skip transport-specific bytes + } //final payloadVer = (header >> 6) & 0x03; final pathLen = packet.readByte(); - pathBytes = packet.readBytes(pathLen); - payload = packet.readBytes(packet.remaining); + final pathBytes = packet.readBytes(pathLen); + final payload = packet.readBytes(packet.remaining); + + final rawPacket = frame.sublist(3); + switch (payloadType) { + case payloadTypeADVERT: + _handlePayloadAdvertReceived( + rawPacket, + payload, + pathBytes, + routeType, + snr, + ); + break; + default: + } } catch (e) { appLogger.warn('Malformed RX frame: $e', tag: 'Connector'); return; } - final rawPacket = frame.sublist(3); - switch (payloadType) { - case payloadTypeADVERT: - _handlePayloadAdvertReceived( - rawPacket, - payload, - pathBytes, - routeType, - snr, - ); - break; - default: - } } void importContact(Uint8List frame) { @@ -3865,7 +3865,12 @@ class MeshCoreConnector extends ChangeNotifier { packet.skipBytes(1); // Skip SNR byte packet.skipBytes(1); // Skip RSSI byte final header = packet.readByte(); + final routeType = header & 0x03; payloadType = (header >> 2) & 0x0F; + if (routeType == _routeTransportFlood || + routeType == _routeTransportDirect) { + packet.skipBytes(4); // Skip transport-specific bytes + } //final payloadVer = (header >> 6) & 0x03; final pathLen = packet.readByte(); pathBytes = packet.readBytes(pathLen); @@ -3915,7 +3920,7 @@ class MeshCoreConnector extends ChangeNotifier { publicKey: publicKey, name: name, type: type, - pathLength: pathBytes.length, + pathLength: pathBytes.isEmpty ? -1 : pathBytes.length, path: Uint8List.fromList( pathBytes.reversed.toList(), ), // Store path in reverse for easier use in outgoing messages diff --git a/lib/connector/meshcore_protocol.dart b/lib/connector/meshcore_protocol.dart index 58abf6f..3484d47 100644 --- a/lib/connector/meshcore_protocol.dart +++ b/lib/connector/meshcore_protocol.dart @@ -4,6 +4,7 @@ import 'dart:typed_data'; // Buffer Reader - sequential binary data reader with pointer tracking class BufferReader { int _pointer = 0; + int _lastPointer = 0; final Uint8List _buffer; BufferReader(Uint8List data) : _buffer = Uint8List.fromList(data); @@ -13,6 +14,7 @@ class BufferReader { int readByte() => readBytes(1)[0]; Uint8List readBytes(int count) { + _lastPointer = _pointer; if (_pointer + count > _buffer.length) { throw RangeError( 'Attempted to read $count bytes at offset $_pointer, but only $remaining bytes remaining in buffer of length ${_buffer.length}', @@ -24,6 +26,7 @@ class BufferReader { } void skipBytes(int count) { + _lastPointer = _pointer; if (_pointer + count > _buffer.length) { throw RangeError( 'Attempted to skip $count bytes at offset $_pointer, but only $remaining bytes remaining in buffer of length ${_buffer.length}', @@ -35,6 +38,7 @@ class BufferReader { Uint8List readRemainingBytes() => readBytes(remaining); String readString() { + _lastPointer = _pointer; final value = readRemainingBytes(); try { return utf8.decode(Uint8List.fromList(value), allowMalformed: true); @@ -43,7 +47,8 @@ class BufferReader { } } - String readCString(int maxLength) { + String readCStringGreedy(int maxLength) { + _lastPointer = _pointer; final value = []; final bytes = readBytes(maxLength); for (final byte in bytes) { @@ -57,6 +62,24 @@ class BufferReader { } } + String readCString(int maxLength) { + final backupPointer = _pointer; + final value = []; + int counter = 0; + while (counter < maxLength) { + final byte = readByte(); + if (byte == 0) break; + value.add(byte); + counter++; + } + _lastPointer = backupPointer; + try { + return utf8.decode(Uint8List.fromList(value), allowMalformed: true); + } catch (e) { + return String.fromCharCodes(value); // Latin-1 fallback + } + } + int readUInt8() => readBytes(1).buffer.asByteData().getUint8(0); int readInt8() => readBytes(1).buffer.asByteData().getInt8(0); int readUInt16LE() => @@ -78,6 +101,9 @@ class BufferReader { if ((value & 0x800000) != 0) value -= 0x1000000; return value; } + + void resetPointer() => _pointer = 0; + void rewind() => _pointer = _lastPointer; } // Buffer Writer - accumulating binary data builder diff --git a/lib/models/contact.dart b/lib/models/contact.dart index 7d8e011..b4acff7 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -1,4 +1,6 @@ import 'dart:typed_data'; +import 'package:meshcore_open/utils/app_logger.dart'; + import '../connector/meshcore_protocol.dart'; class Contact { @@ -166,28 +168,27 @@ class Contact { static Contact? fromFrame(Uint8List data) { if (data.isEmpty) return null; - if (data[0] != respCodeContact) return null; + final reader = BufferReader(data); try { - final pubKey = Uint8List.fromList( - data.sublist(contactPubKeyOffset, contactPubKeyOffset + pubKeySize), - ); - final type = data[contactTypeOffset]; - final flags = data[contactFlagsOffset]; - final pathLen = data[contactPathLenOffset].toSigned(8); + final respCode = reader.readByte(); + if (respCode != respCodeContact && respCode != pushCodeNewAdvert) { + return null; + } + final pubKey = reader.readBytes(pubKeySize); + final type = reader.readByte(); + final flags = reader.readByte(); + final pathLen = reader.readByte(); final safePathLen = pathLen > 0 ? (pathLen > maxPathSize ? maxPathSize : pathLen) : 0; - final pathBytes = safePathLen > 0 - ? Uint8List.fromList( - data.sublist(contactPathOffset, contactPathOffset + safePathLen), - ) - : Uint8List(0); - final name = readCString(data, contactNameOffset, maxNameSize); - final lastmod = readUint32LE(data, contactLastModOffset); + final pathBytes = reader.readBytes(maxPathSize).sublist(0, safePathLen); + final name = reader.readCStringGreedy(maxNameSize); + + final lastMod = reader.readUInt32LE(); double? lat, lon; - final latRaw = readInt32LE(data, contactLatOffset); - final lonRaw = readInt32LE(data, contactLonOffset); + final latRaw = reader.readInt32LE(); + final lonRaw = reader.readInt32LE(); if (latRaw != 0 || lonRaw != 0) { lat = latRaw / 1e6; lon = lonRaw / 1e6; @@ -198,14 +199,14 @@ class Contact { name: name.isEmpty ? 'Unknown' : name, type: type, flags: flags, - pathLength: pathLen, + pathLength: pathLen > 0 ? (pathLen > maxPathSize ? -1 : pathLen) : -1, path: pathBytes, latitude: lat, longitude: lon, - lastSeen: DateTime.fromMillisecondsSinceEpoch(lastmod * 1000), + lastSeen: DateTime.fromMillisecondsSinceEpoch(lastMod * 1000), ); } catch (e) { - // If parsing fails, return null + appLogger.error('Failed to parse contact frame: $e'); return null; } } From b748b96237679f450049229ac5a1e985fdfa8aec Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Sat, 7 Mar 2026 01:45:53 -0800 Subject: [PATCH 2/2] Enhance contact handling logic in MeshCoreConnector to support conditional addition based on auto-add settings (#268) --- lib/connector/meshcore_connector.dart | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 75b5287..7aa0e5d 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -1926,7 +1926,7 @@ class MeshCoreConnector extends ChangeNotifier { case pushCodeNewAdvert: debugPrint('Got New CONTACT'); // It's the same format as respCodeContact, so we can reuse the handler - _handleContact(frame); + _handleContact(frame, isContact: false); break; case respCodeContact: debugPrint('Got CONTACT'); @@ -2217,7 +2217,7 @@ class MeshCoreConnector extends ChangeNotifier { } } - void _handleContact(Uint8List frame) { + void _handleContact(Uint8List frame, {bool isContact = true}) { final contact = Contact.fromFrame(frame); if (contact != null) { if (contact.type == advTypeRepeater) { @@ -2256,11 +2256,23 @@ class MeshCoreConnector extends ChangeNotifier { tag: 'Connector', ); } else { - _contacts.add(contact); - appLogger.info( - 'Added new contact ${contact.name}: pathLen=${contact.pathLength}', - tag: 'Connector', - ); + if ((_autoAddUsers && contact.type == advTypeChat) || + (_autoAddRepeaters && contact.type == advTypeRepeater) || + (_autoAddRoomServers && contact.type == advTypeRoom) || + (_autoAddSensors && contact.type == advTypeSensor) || + isContact) { + _contacts.add(contact); + appLogger.info( + 'Added new contact ${contact.name}: pathLen=${contact.pathLength}', + tag: 'Connector', + ); + } else { + appLogger.info( + "Discovered contact ${contact.name} (type ${contact.typeLabel}) not added due to auto-add settings", + tag: 'Connector', + ); + return; + } } _knownContactKeys.add(contact.publicKeyHex); _loadMessagesForContact(contact.publicKeyHex);