From f6d7eb8765bac64625bc6eb441348f38cc4d1ae1 Mon Sep 17 00:00:00 2001 From: Mohammad Abdul Sahil <127765312+abdulsaheel@users.noreply.github.com> Date: Sat, 19 Sep 2026 01:28:31 +0530 Subject: [PATCH] fix console log reassembler first-chunk/gap ambiguity add() returning false meant two different things: a real gap, or just the first chunk into a fresh/flushed instance. a caller following the doc comment (flush after every false) would nuke _lastIndex on every chunk and never actually reassemble a straddled log line. added hasCompletedRun so callers can tell the difference, and pointed the docs at it instead of add()'s raw return. add()/flush() behavior unchanged. --- lib/src/control.dart | 15 +++++++++++++-- test/control_plane_offsets_test.dart | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/lib/src/control.dart b/lib/src/control.dart index bd75bf8..c9f42ff 100644 --- a/lib/src/control.dart +++ b/lib/src/control.dart @@ -1238,6 +1238,12 @@ ConsoleLogChunk? parseConsoleLog(Uint8List inner) { /// run has accumulated so far. A gap in `record_index` (a dropped/reordered /// frame) flushes what's buffered rather than silently splicing unrelated /// text together. +/// +/// Check [hasCompletedRun] before calling [flush] — don't react to [add]'s +/// raw boolean. `add()` returning false means "not contiguous with the last +/// chunk", which is also true for the very first chunk into a fresh or +/// just-flushed instance; flushing on every `false` would hand back that lone +/// chunk instead of letting the run keep building. class ConsoleLogReassembler { final StringBuffer _buf = StringBuffer(); @@ -1247,9 +1253,14 @@ class ConsoleLogReassembler { String? _completed; int? _lastIndex; + /// True when a gap has parked a finished run for [flush] to return. Use + /// this, not [add]'s return value, to decide whether to call [flush]. + bool get hasCompletedRun => _completed != null; + /// Feed one decoded chunk. Returns true if it extended the current - /// contiguous run; false if it started a new one — call [flush] after a - /// `false` return to get the run the gap ended. + /// contiguous run; false if it started a new one — check + /// [hasCompletedRun] (not this return value) to know when a finished run + /// is ready for [flush]. bool add(ConsoleLogChunk chunk) { final contiguous = _lastIndex != null && // record_index is a u8 — allow wraparound at 0xFF, matching the diff --git a/test/control_plane_offsets_test.dart b/test/control_plane_offsets_test.dart index d1d5dda..f15f7dd 100644 --- a/test/control_plane_offsets_test.dart +++ b/test/control_plane_offsets_test.dart @@ -526,6 +526,28 @@ void main() { expect(r.add(parseConsoleLog(logPacket(10, '!'))!), isTrue); expect(r.flush(), 'after the gap!'); }); + + test( + 'a caller that only flushes on hasCompletedRun keeps a 4-chunk line ' + 'whole, even though every add() returns false on a fresh instance', + () { + // add() returning false on the very first chunk into a fresh instance + // used to be indistinguishable from a real gap. A caller that flushed + // on every `false` (the old documented contract) reset _lastIndex each + // time, so record_index 5/6/7/8 came back as four single-chunk + // fragments instead of one line. + final r = ConsoleLogReassembler(); + for (final chunk in [ + logPacket(5, 'BLE_'), + logPacket(6, 'CMD: '), + logPacket(7, 'Historical '), + logPacket(8, 'Data\n'), + ]) { + r.add(parseConsoleLog(chunk)!); + if (r.hasCompletedRun) r.flush(); + } + expect(r.flush(), 'BLE_CMD: Historical Data\n'); + }); }); group('HELLO battery scan starts on the body, not the status byte', () {