fix(parser): correct S3 frame terminator — bare ETX, not DLE+ETX

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
This commit is contained in:
Claude
2026-04-26 20:23:18 +00:00
parent 34df9ec5fa
commit 9bbecea70f
+23 -16
View File
@@ -33,7 +33,7 @@ STX = 0x02
ETX = 0x03 ETX = 0x03
ACK = 0x41 ACK = 0x41
__version__ = "0.2.3" __version__ = "0.2.4"
@dataclass @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]: def parse_s3(blob: bytes, trailer_len: int) -> List[Frame]:
frames: List[Frame] = [] frames: List[Frame] = []
IDLE = 0 IDLE = 0
IN_FRAME = 1 IN_FRAME = 1
AFTER_DLE = 2 IN_FRAME_DLE = 2 # saw DLE inside frame — waiting for next byte
state = IDLE state = IDLE
body = bytearray() body = bytearray()
@@ -206,22 +206,15 @@ def parse_s3(blob: bytes, trailer_len: int) -> List[Frame]:
state = IN_FRAME state = IN_FRAME
i += 2 i += 2
continue continue
# ACK bytes, boot strings, garbage — silently ignored
elif state == IN_FRAME: elif state == IN_FRAME:
if b == DLE: if b == DLE:
state = AFTER_DLE state = IN_FRAME_DLE
i += 1 i += 1
continue continue
body.append(b)
else: # AFTER_DLE
if b == DLE:
body.append(DLE)
state = IN_FRAME
i += 1
continue
if b == ETX: if b == ETX:
# Bare ETX = real S3 frame terminator (confirmed from S3FrameParser)
end_offset = i + 1 end_offset = i + 1
trailer_start = i + 1 trailer_start = i + 1
trailer_end = trailer_start + trailer_len trailer_end = trailer_start + trailer_len
@@ -259,13 +252,27 @@ def parse_s3(blob: bytes, trailer_len: int) -> List[Frame]:
state = IDLE state = IDLE
i = trailer_end i = trailer_end
continue 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 # Unexpected DLE + byte → treat as literal data
body.append(DLE) body.append(DLE)
body.append(b) body.append(b)
state = IN_FRAME state = IN_FRAME
i += 1
continue
i += 1 i += 1