mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-08-04 15:03:00 +10:00
Enhance message parsing and error handling in MeshCoreConnector (#260)
* Enhance readString method to include Latin-1 fallback for decoding errors * Refactor _parseContactMessage to improve error handling and message parsing logic * Update lib/connector/meshcore_connector.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -2459,70 +2459,93 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Message? _parseContactMessage(Uint8List frame) {
|
Message? _parseContactMessage(Uint8List frame) {
|
||||||
if (frame.isEmpty) return null;
|
if (frame.isEmpty) {
|
||||||
final code = frame[0];
|
appLogger.warn('Received empty frame, ignoring');
|
||||||
if (code != respCodeContactMsgRecv && code != respCodeContactMsgRecvV3) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
final reader = BufferReader(frame);
|
||||||
|
|
||||||
// Companion radio layout:
|
try {
|
||||||
// [code][snr?][res?][res?][prefix x6][path_len][txt_type][timestamp x4][extra?][text...]
|
final code = reader.readByte();
|
||||||
final prefixOffset = code == respCodeContactMsgRecvV3 ? 4 : 1;
|
if (code != respCodeContactMsgRecv && code != respCodeContactMsgRecvV3) {
|
||||||
const prefixLen = 6;
|
appLogger.warn(
|
||||||
final pathLenOffset = prefixOffset + prefixLen;
|
'Unexpected message code: $code, expected contact message receive codes',
|
||||||
final txtTypeOffset = pathLenOffset + 1;
|
);
|
||||||
final timestampOffset = txtTypeOffset + 1;
|
return null;
|
||||||
final baseTextOffset = timestampOffset + 4;
|
}
|
||||||
|
|
||||||
if (frame.length <= baseTextOffset) return null;
|
// Companion radio layout:
|
||||||
final fourBytePubMSG = frame.sublist(baseTextOffset, baseTextOffset + 4);
|
// [code][snr?][res?][res?][prefix x6][path_len][txt_type][timestamp x4][extra?][text...]
|
||||||
final senderPrefix = frame.sublist(prefixOffset, prefixOffset + prefixLen);
|
// double snr = 0;
|
||||||
final flags = frame[txtTypeOffset];
|
if (code == respCodeContactMsgRecvV3) {
|
||||||
final shiftedType = flags >> 2;
|
// Older firmware layout with SNR as a signed byte after the code
|
||||||
final rawType = flags;
|
// snr = reader.readInt8().toDouble() * 4; // SNR in dB, scaled by 4
|
||||||
final isPlain = shiftedType == txtTypePlain || rawType == txtTypePlain;
|
reader.skipBytes(1); // Skip SNR byte
|
||||||
final isCli = shiftedType == txtTypeCliData || rawType == txtTypeCliData;
|
reader.skipBytes(2); // Skip reserved bytes
|
||||||
if (!isPlain && !isCli) {
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try base text offset; if empty and there is room for the optional 4-byte extra
|
final senderPrefix = reader.readBytes(6);
|
||||||
// (used by signed/plain variants), try again skipping those bytes.
|
final pathLength = reader.readByte();
|
||||||
var text = readCString(
|
final txtType = reader.readByte();
|
||||||
frame,
|
final timestampRaw = reader.readUInt32LE();
|
||||||
baseTextOffset,
|
final timestamp = DateTime.fromMillisecondsSinceEpoch(
|
||||||
frame.length - baseTextOffset,
|
timestampRaw * 1000,
|
||||||
);
|
|
||||||
if (text.isEmpty && frame.length > baseTextOffset + 4) {
|
|
||||||
text = readCString(
|
|
||||||
frame,
|
|
||||||
baseTextOffset + 4,
|
|
||||||
frame.length - (baseTextOffset + 4),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (txtType == 2) {
|
||||||
|
reader.skipBytes(4); // Skip extra 4 bytes for signed/plain variants
|
||||||
|
}
|
||||||
|
|
||||||
|
final msgText = reader.readString();
|
||||||
|
|
||||||
|
final flags = txtType;
|
||||||
|
final shiftedType = flags >> 2;
|
||||||
|
final rawType = flags;
|
||||||
|
final isPlain = shiftedType == txtTypePlain || rawType == txtTypePlain;
|
||||||
|
final isCli = shiftedType == txtTypeCliData || rawType == txtTypeCliData;
|
||||||
|
if (!isPlain && !isCli) {
|
||||||
|
appLogger.warn(
|
||||||
|
'Unknown message type received: txtType=$txtType, shifted=$shiftedType, raw=$rawType',
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msgText.isEmpty) {
|
||||||
|
appLogger.warn('Received message with empty text, ignoring');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final decodedText = isCli
|
||||||
|
? msgText
|
||||||
|
: (Smaz.tryDecodePrefixed(msgText) ?? msgText);
|
||||||
|
|
||||||
|
final contact = _contacts.cast<Contact?>().firstWhere(
|
||||||
|
(c) => c != null && _matchesPrefix(c.publicKey, senderPrefix),
|
||||||
|
orElse: () => null,
|
||||||
|
);
|
||||||
|
if (contact == null) {
|
||||||
|
appLogger.warn(
|
||||||
|
'Received message from unknown contact with prefix: ${senderPrefix.map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase()).join('')}',
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Message(
|
||||||
|
senderKey: contact.publicKey,
|
||||||
|
text: decodedText,
|
||||||
|
timestamp: timestamp,
|
||||||
|
isOutgoing: false,
|
||||||
|
isCli: isCli,
|
||||||
|
status: MessageStatus.delivered,
|
||||||
|
pathLength: pathLength == 0xFF ? 0 : pathLength,
|
||||||
|
pathBytes: Uint8List(0),
|
||||||
|
fourByteRoomContactKey: msgText.length >= 4
|
||||||
|
? Uint8List.fromList(msgText.substring(0, 4).codeUnits)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
appLogger.warn('Error parsing contact direct message: $e');
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
if (text.isEmpty) return null;
|
|
||||||
final decodedText = isCli ? text : (Smaz.tryDecodePrefixed(text) ?? text);
|
|
||||||
|
|
||||||
final timestampRaw = readUint32LE(frame, timestampOffset);
|
|
||||||
final pathLenByte = frame[pathLenOffset];
|
|
||||||
|
|
||||||
final contact = _contacts.cast<Contact?>().firstWhere(
|
|
||||||
(c) => c != null && _matchesPrefix(c.publicKey, senderPrefix),
|
|
||||||
orElse: () => null,
|
|
||||||
);
|
|
||||||
if (contact == null) return null;
|
|
||||||
|
|
||||||
return Message(
|
|
||||||
senderKey: contact.publicKey,
|
|
||||||
text: decodedText,
|
|
||||||
timestamp: DateTime.fromMillisecondsSinceEpoch(timestampRaw * 1000),
|
|
||||||
isOutgoing: false,
|
|
||||||
isCli: isCli,
|
|
||||||
status: MessageStatus.delivered,
|
|
||||||
pathLength: pathLenByte == 0xFF ? 0 : pathLenByte,
|
|
||||||
pathBytes: Uint8List(0),
|
|
||||||
fourByteRoomContactKey: fourBytePubMSG,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _matchesPrefix(Uint8List fullKey, Uint8List prefix) {
|
bool _matchesPrefix(Uint8List fullKey, Uint8List prefix) {
|
||||||
|
|||||||
@@ -34,8 +34,14 @@ class BufferReader {
|
|||||||
|
|
||||||
Uint8List readRemainingBytes() => readBytes(remaining);
|
Uint8List readRemainingBytes() => readBytes(remaining);
|
||||||
|
|
||||||
String readString() =>
|
String readString() {
|
||||||
utf8.decode(readRemainingBytes(), allowMalformed: true);
|
final value = readRemainingBytes();
|
||||||
|
try {
|
||||||
|
return utf8.decode(Uint8List.fromList(value), allowMalformed: true);
|
||||||
|
} catch (e) {
|
||||||
|
return String.fromCharCodes(value); // Latin-1 fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String readCString(int maxLength) {
|
String readCString(int maxLength) {
|
||||||
final value = <int>[];
|
final value = <int>[];
|
||||||
|
|||||||
Reference in New Issue
Block a user