diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index d4a183a..537312e 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -2311,7 +2311,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'); @@ -2660,7 +2660,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) { @@ -2699,11 +2699,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); @@ -4275,44 +4287,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) { @@ -4379,7 +4387,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; } }