Improve limitless packet parsing + sync improvements#3776
Conversation
There was a problem hiding this comment.
Code Review
The pull request introduces significant changes to the Limitless device connection, primarily focusing on improving packet parsing and synchronization for both real-time and batch modes. New data structures (_fragmentBuffer, _completedFlashPages) are added to handle fragmented BLE packets and reassemble flash pages. The _handleNotification method has been refactored to incorporate a new _parseBlePacket function, which extracts fragmentation information. New helper methods like _handlePendantMessage, _handleStorageBuffer, _parseFlashPageInfo, _extractOpusFramesFromFlashPage, and _extractOpusRecursive are introduced to process the fragmented data and extract Opus frames and session information. The enableBatchMode, disableBatchMode, and clearBuffer methods are updated to manage the new buffers correctly. Finally, extractFramesWithSessionInfo is enhanced to aggregate data from completed flash pages when in batch mode.
| debugPrint( | ||
| 'Limitless: Batch mode - packet parse failed, data=${data.length}b, first bytes: ${data.take(10).toList()}'); | ||
| } | ||
| _rawDataBuffer.addAll(data); |
There was a problem hiding this comment.
If _parseBlePacket returns null in batch mode, the raw data is added to _rawDataBuffer. This seems to contradict the new fragmentation logic, which uses _fragmentBuffer for batch mode. If _parseBlePacket fails, it implies the data is not a fragmented packet, but _rawDataBuffer is typically used for real-time mode's frame extraction. This could lead to _rawDataBuffer accumulating unparsed data indefinitely in batch mode, potentially causing memory issues or incorrect data processing if this data was intended to be handled differently.
| final fragment = _fragmentBuffer[index]![i]; | ||
| if (fragment != null) { |
There was a problem hiding this comment.
The logic if (fragment != null) silently skips fragments if they are missing from _fragmentBuffer[index]!. This can lead to incomplete or corrupted completePayload being passed to _handlePendantMessage without any explicit error or re-request mechanism. Missing fragments in a reassembly process should ideally trigger an error, a re-request, or at least a warning, rather than silently omitting data.
| if (opusFrames.isNotEmpty) { | ||
| final flashPage = { | ||
| 'opus_frames': opusFrames, | ||
| 'timestamp_ms': pageInfo['timestamp_ms'] ?? DateTime.now().millisecondsSinceEpoch, |
There was a problem hiding this comment.
Using DateTime.now().millisecondsSinceEpoch as a fallback timestamp when pageInfo['timestamp_ms'] is null can lead to inconsistencies. If the device's original timestamp is missing or invalid, substituting it with the current app time can misrepresent the actual timing of historical data, especially if device and app clocks are not synchronized. Consider explicitly marking the timestamp as missing or invalid instead of providing a potentially misleading current time.
| } catch (e) { | ||
| debugPrint('Limitless: Error handling storage buffer: $e'); | ||
| } |
There was a problem hiding this comment.
Silently ignoring parsing errors in _handleStorageBuffer can hide critical issues related to data corruption or unexpected packet formats. While debugPrint logs the error, the application continues as if no issue occurred, which might lead to incorrect state or data processing. Consider a more robust error handling strategy, such as returning null or throwing a specific exception, to ensure that upstream logic is aware of parsing failures.
| } catch (e) { | ||
| // Silently ignore parsing errors | ||
| } |
There was a problem hiding this comment.
Silently ignoring parsing errors in _parseFlashPageInfo can obscure problems with the flash page data structure. This can make debugging difficult and lead to incorrect or incomplete session information being processed without any clear indication of failure. It's recommended to at least log these errors with debugPrint to aid in diagnosing issues.
| } catch (e) { | ||
| debugPrint('Limitless: Error extracting Opus frames from flash page: $e'); | ||
| } |
There was a problem hiding this comment.
Silently ignoring parsing errors in _extractOpusFramesFromFlashPage can lead to missing or corrupted Opus frames without any clear indication of the problem. This can severely impact audio quality or lead to incomplete recordings. It's recommended to at least log these errors with debugPrint to aid in diagnosing issues.
| // Unknown wire type | ||
| break; | ||
| } |
There was a problem hiding this comment.
The _extractOpusRecursive function uses a break statement when an "Unknown wire type" is encountered. This effectively stops parsing without logging or indicating that an unexpected data format was found. This silent failure can lead to incomplete extraction of Opus frames, potentially causing data loss or corruption that is difficult to diagnose. Consider logging this event or handling it more explicitly.
| try { | ||
| int pos = 0; | ||
| int? index; | ||
| int seq = 0; |
There was a problem hiding this comment.
The seq variable is initialized to 0 and is not checked for nullability in the final return condition (line 599), unlike index, numFrags, and payload. If fieldNum == 2 (which corresponds to seq) is never encountered in the packet, seq will remain 0. If seq is a mandatory part of the packet structure for reassembly, a packet missing this field would be processed with an incorrect sequence number, potentially leading to incorrect fragment reassembly. Consider explicitly checking for its presence or ensuring 0 is a valid default for a missing sequence number.
|
|
||
| return { | ||
| 'opus_frames': allFrames, | ||
| 'timestamp_ms': timestampMs ?? _firstFlashPageTimestampMs ?? DateTime.now().millisecondsSinceEpoch, |
There was a problem hiding this comment.
Similar to the issue in _handleStorageBuffer, using DateTime.now().millisecondsSinceEpoch as a fallback timestamp in extractFramesWithSessionInfo when timestampMs and _firstFlashPageTimestampMs are null can lead to incorrect or misleading timestamps for aggregated flash pages. This can negatively impact the accurate sequencing and interpretation of recorded data. It's advisable to provide a more explicit indication of a missing or invalid timestamp.
| 'did_stop_session': didStopSession, | ||
| 'did_start_recording': didStartRecording, | ||
| 'did_stop_recording': didStopRecording, | ||
| 'max_index': maxIndex, |
There was a problem hiding this comment.
The max_index is calculated and included in the returned map, but there is no subsequent logic to acknowledge this index. If max_index is intended to be used for acknowledging processed data, the acknowledgment mechanism needs to be triggered after extractFramesWithSessionInfo is called. Without proper acknowledgment, the device might resend the same data, leading to redundant processing or potential buffer overflows.
No description provided.