Skip to content

Enhance message parsing and error handling in MeshCoreConnector#260

Merged
wel97459 merged 3 commits intomainfrom
dev-dmFix
Mar 5, 2026
Merged

Enhance message parsing and error handling in MeshCoreConnector#260
wel97459 merged 3 commits intomainfrom
dev-dmFix

Conversation

@wel97459
Copy link
Collaborator

@wel97459 wel97459 commented Mar 5, 2026

  • Updated _parseContactMessage to improve error logging and handling for empty frames and unknown message types.
  • Refactored readString method in BufferReader to include a fallback for malformed UTF-8 strings.

Copilot AI review requested due to automatic review settings March 5, 2026 03:18
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to harden MeshCoreConnector’s inbound message parsing (better logging/handling for empty frames, unknown types, unknown contacts) and adjusts BufferReader.readString() to be more tolerant of malformed text.

Changes:

  • Refactors BufferReader.readString() to add a fallback decoding path.
  • Rewrites _parseContactMessage() to use BufferReader, adds warning logs, and changes how message text/metadata are extracted.
  • Adds raw RX frame logging in _handleRxData().

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
lib/connector/meshcore_protocol.dart Updates BufferReader.readString() decoding/fallback behavior.
lib/connector/meshcore_connector.dart Refactors contact message parsing and adds additional logging (including raw frame output).
Comments suppressed due to low confidence (1)

lib/connector/meshcore_protocol.dart:44

  • readString() copies the byte buffer again (Uint8List.fromList(value)) even though readRemainingBytes() already returns a Uint8List (and BufferReader copies the input in its constructor). Also, utf8.decode(..., allowMalformed: true) should not throw for malformed sequences, so the try/catch is likely dead code and adds overhead. Consider decoding the existing Uint8List directly and only keeping a fallback if you have a concrete failure mode that can still throw in production.
  String readString() {
    final value = readRemainingBytes();
    try {
      return utf8.decode(Uint8List.fromList(value), allowMalformed: true);
    } catch (e) {
      return String.fromCharCodes(value); // Latin-1 fallback
    }
  }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

reader.skipBytes(4); // Skip extra 4 bytes for signed/plain variants
}

final msgText = reader.readString();
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reader.readString() reads all remaining bytes and will include any trailing NUL terminator(s) in the decoded Dart string (e.g., \u0000), which can leak into UI, equality checks, and reaction hashing. Contact message payloads elsewhere are treated as C-strings (readCString(...).trim() in parseContactMessageText), so this should likely be parsed as a null-terminated string rather than as the entire remaining buffer.

Suggested change
final msgText = reader.readString();
final msgText = reader.readCString().trim();

Copilot uses AI. Check for mistakes.
Comment on lines +2494 to +2496
if (txtType == 2) {
reader.skipBytes(4); // Skip extra 4 bytes for signed/plain variants
}
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The optional 4-byte "extra" handling changed semantics: previously the parser attempted baseTextOffset and, if empty, retried from baseTextOffset + 4 (covering multiple flag values). Now it unconditionally skips 4 bytes only when txtType == 2, but other flag values can still represent a plain/CLI message (shiftedType == txtTypePlain/txtTypeCliData) and may also include the extra field. This can misalign parsing and drop or corrupt messages.

Copilot uses AI. Check for mistakes.
Comment on lines +2540 to +2542
fourByteRoomContactKey: msgText.length >= 4
? Uint8List.fromList(msgText.substring(0, 4).codeUnits)
: null,
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fourByteRoomContactKey is being derived from msgText.substring(0, 4).codeUnits, which is not equivalent to the first 4 bytes of the payload. This can throw (e.g., non-ASCII characters produce code units > 255, which Uint8List.fromList rejects) and it breaks sender resolution for room reactions (_matchesPrefix expects raw prefix bytes). This should be extracted from the raw frame bytes (before UTF-8 decoding) as a 4-byte prefix.

Suggested change
fourByteRoomContactKey: msgText.length >= 4
? Uint8List.fromList(msgText.substring(0, 4).codeUnits)
: null,
fourByteRoomContactKey: () {
final bytes = utf8.encode(msgText);
if (bytes.length < 4) return null;
return Uint8List.fromList(bytes.sublist(0, 4));
}(),

Copilot uses AI. Check for mistakes.
Comment on lines +3707 to +3709
debugPrint(
"Received frame raw: ${frame.map((b) => b.toRadixString(16).padLeft(2, '0')).join()}",
);
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

debugPrint logs the full raw frame bytes for every received message, which can include sensitive user message contents or protocol data that should not be persisted in logs. An attacker or diagnostic system with access to application logs (e.g., device logs, remote log aggregation) could reconstruct private messages or keys from these hex-encoded frames. Restrict this logging to explicit debug-only builds or remove/obfuscate sensitive payload data before logging.

Suggested change
debugPrint(
"Received frame raw: ${frame.map((b) => b.toRadixString(16).padLeft(2, '0')).join()}",
);
if (kDebugMode) {
debugPrint(
"Received frame raw: ${frame.map((b) => b.toRadixString(16).padLeft(2, '0')).join()}",
);
}

Copilot uses AI. Check for mistakes.
@wel97459
Copy link
Collaborator Author

wel97459 commented Mar 5, 2026

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 84079888cb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copilot AI review requested due to automatic review settings March 5, 2026 03:38
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

lib/connector/meshcore_protocol.dart:42

  • readString() is copying the buffer (Uint8List.fromList(value)) even though utf8.decode can decode the existing Uint8List directly. This adds an avoidable allocation on every call; consider decoding value directly and only copying if there’s a demonstrated need.
  String readString() {
    final value = readRemainingBytes();
    try {
      return utf8.decode(Uint8List.fromList(value), allowMalformed: true);
    } catch (e) {
      return String.fromCharCodes(value); // Latin-1 fallback

lib/connector/meshcore_protocol.dart:44

  • The try/catch in BufferReader.readString() is likely unreachable because utf8.decode(..., allowMalformed: true) should not throw for malformed UTF-8 (it replaces invalid sequences). If the goal is a Latin-1/byte-preserving fallback, this should be driven by an explicit heuristic (or use latin1.decode) rather than catching an exception that probably never occurs.
  String readString() {
    final value = readRemainingBytes();
    try {
      return utf8.decode(Uint8List.fromList(value), allowMalformed: true);
    } catch (e) {
      return String.fromCharCodes(value); // Latin-1 fallback
    }
  }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 5, 2026 06:24
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

lib/connector/meshcore_protocol.dart:44

  • readString() makes an extra full copy (Uint8List.fromList(value)) even though readRemainingBytes() already returns a Uint8List, and the try/catch is likely redundant because utf8.decode(..., allowMalformed: true) should not throw for malformed sequences. This adds avoidable allocations on every call; consider decoding value directly and only adding a fallback path where an exception can actually occur.
  String readString() {
    final value = readRemainingBytes();
    try {
      return utf8.decode(Uint8List.fromList(value), allowMalformed: true);
    } catch (e) {
      return String.fromCharCodes(value); // Latin-1 fallback
    }
  }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@wel97459 wel97459 merged commit 7d8e049 into main Mar 5, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants