From 9bbecea70fc4ae07b8b668920ad2ec05d735fdf6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 26 Apr 2026 20:23:18 +0000 Subject: [PATCH] =?UTF-8?q?fix(parser):=20correct=20S3=20frame=20terminato?= =?UTF-8?q?r=20=E2=80=94=20bare=20ETX,=20not=20DLE+ETX?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit parse_s3 had the S3 terminator logic inverted vs the real S3FrameParser in framing.py. It was terminating on DLE+ETX and treating bare ETX as payload, which caused every bare 0x03 to be swallowed — bundling multiple real S3 frames into one giant body until a DLE+ETX sequence happened to appear. Result: 583-byte POLL_RESPONSE 'frames' containing many real frames concatenated, all showing BAD CHK. Fix: mirror S3FrameParser exactly — - Bare ETX (0x03) = real frame terminator - DLE+ETX (0x10 0x03) = inner-frame literal data (A4/E5 sub-frames), appended to body and parsing continues https://claude.ai/code/session_014NczSHUz9uTzCAf4cVASTJ --- parsers/s3_parser.py | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/parsers/s3_parser.py b/parsers/s3_parser.py index 2f32933..4b8a2bc 100644 --- a/parsers/s3_parser.py +++ b/parsers/s3_parser.py @@ -33,7 +33,7 @@ STX = 0x02 ETX = 0x03 ACK = 0x41 -__version__ = "0.2.3" +__version__ = "0.2.4" @dataclass @@ -184,9 +184,9 @@ def validate_bw_body_auto(body: bytes) -> Optional[Tuple[bytes, bytes, str]]: def parse_s3(blob: bytes, trailer_len: int) -> List[Frame]: frames: List[Frame] = [] - IDLE = 0 - IN_FRAME = 1 - AFTER_DLE = 2 + IDLE = 0 + IN_FRAME = 1 + IN_FRAME_DLE = 2 # saw DLE inside frame — waiting for next byte state = IDLE body = bytearray() @@ -206,22 +206,15 @@ def parse_s3(blob: bytes, trailer_len: int) -> List[Frame]: state = IN_FRAME i += 2 continue + # ACK bytes, boot strings, garbage — silently ignored elif state == IN_FRAME: if b == DLE: - state = AFTER_DLE + state = IN_FRAME_DLE i += 1 continue - body.append(b) - - else: # AFTER_DLE - if b == DLE: - body.append(DLE) - state = IN_FRAME - i += 1 - continue - if b == ETX: + # Bare ETX = real S3 frame terminator (confirmed from S3FrameParser) end_offset = i + 1 trailer_start = i + 1 trailer_end = trailer_start + trailer_len @@ -259,13 +252,27 @@ def parse_s3(blob: bytes, trailer_len: int) -> List[Frame]: state = IDLE i = trailer_end continue + body.append(b) + else: # IN_FRAME_DLE + if b == DLE: + # DLE DLE → literal 0x10 in payload + body.append(DLE) + state = IN_FRAME + i += 1 + continue + if b == ETX: + # DLE+ETX inside a frame = inner-frame terminator (A4/E5 sub-frames). + # Treat as literal data, NOT the outer frame end. + body.append(DLE) + body.append(ETX) + state = IN_FRAME + i += 1 + continue # Unexpected DLE + byte → treat as literal data body.append(DLE) body.append(b) state = IN_FRAME - i += 1 - continue i += 1