This commit is contained in:
Ayzen
2026-06-13 12:07:23 +03:00
parent f0d095de80
commit e7f2d25585
20 changed files with 903 additions and 148 deletions
@@ -79,11 +79,21 @@ def decode_frame(frame: bytes) -> Packet:
class FrameScanner:
"""Incremental frame scanner for raw USB byte streams."""
"""Incremental frame scanner for raw USB byte streams.
Tolerant of corruption: a ``0x5A`` that opens a frame which does not decode
(bad CRC, unknown packet type, length mismatch) is treated as a false header.
Such a byte is skipped one at a time and scanning resyncs on the next
candidate header, so a malformed or partially-lost frame costs only a brief
resync — ``feed`` never raises and never permanently desynchronizes the
stream. ``discarded_byte_count`` exposes how many bytes were skipped this way
for diagnostics.
"""
def __init__(self) -> None:
"""Initialize internal undecoded byte buffer."""
"""Initialize internal undecoded byte buffer and resync counter."""
self._buffer = bytearray()
self.discarded_byte_count = 0
def clear(self) -> None:
"""Drop all buffered undecoded bytes."""
@@ -109,15 +119,31 @@ class FrameScanner:
(length,) = struct.unpack_from("<H", self._buffer, 1)
if length < _FRAME_OVERHEAD or length > _MAX_FRAME_LENGTH:
logger.debug("Discarding byte due to invalid frame length=%d", length)
del self._buffer[0]
self._discard_false_header(f"invalid frame length={length}")
continue
if len(self._buffer) < length:
break
frame = bytes(self._buffer[:length])
try:
packet = decode_frame(frame)
except (ParseError, CRCError) as exc:
# The 0x5A was a false header — e.g. a byte inside a CRC-less
# VNADatapoint payload, or a frame mangled by USB byte loss. Skip
# one byte and resync on the next candidate header rather than
# propagating (which would otherwise kill the whole transport).
self._discard_false_header(str(exc))
continue
del self._buffer[:length]
decoded.append(decode_frame(frame))
decoded.append(packet)
return decoded
def _discard_false_header(self, reason: str) -> None:
"""Drop one buffered byte past a false frame header and count the resync."""
self.discarded_byte_count += 1
if logger.isEnabledFor(logging.DEBUG):
logger.debug("Resyncing frame stream past false header: %s", reason)
del self._buffer[0]