Skip to content

EXPECTED_MESSAGE_LENGTH_MAP declares fixed length 64 for null-terminated string commands 0x11/0x12, desyncing the frame parser #1

Description

@theharshl

Summary

EXPECTED_MESSAGE_LENGTH_MAP declares a fixed payload length of 64 for MP3_FILE_NAME_RECEIVE_COMMAND (0x11) and MP3_ARTIST_NAME_RECEIVE_COMMAND (0x12), but the controller sends these as variable-length, null-terminated strings. Observed frames are 7 and 16 bytes total, not 69.

This desyncs the frame parser. It is not latent_parse_frame consults the length map to determine framing before dispatching to a handler, so the bug is active today even though the 0x11/0x12 handlers are commented out at base_client.py:367-371.

Evidence

Captured from a Lync 12 (firmware responds to model probe as Lync) with a USB drive attached, via a passive TCP sniffer on port 10006.

02 00 00 11 31 00 44                                      cmd=0x11, payload "1\0"           (7 bytes)
02 00 00 12 41 57 4f 4c 4e 41 54 49 4f 4e 00 10           cmd=0x12, payload "AWOLNATION\0"  (16 bytes)
02 00 00 12 41 64 65 6c 65 00 ef                          cmd=0x12, payload "Adele\0"       (11 bytes)
02 00 00 12 fe ff 00 11                                   cmd=0x12, payload BOM only        (8 bytes)

The lengths are confirmed by the transmitted checksums, not merely by a parser's interpretation:

  • 0x02 + 0x00 + 0x00 + 0x11 + 0x31 = 0x44 — matches the checksum on the 7-byte frame.
  • 0x02 + 0x00 + 0x00 + 0x12 + "Adele" + 0x00 = 0xEF — matches the checksum on the 11-byte frame.

A 64-byte payload would produce entirely different checksums. The frames really are this short, and they vary in length with the string.

Note the third case: for some tags the controller emits a bare UTF-16 BOM (fe ff) and no text at all — a 1-byte payload plus terminator. Any fix needs to handle that without raising.

Failure mode

At base_client.py:266:

if len(data) <= data_idx + expected_length:
    return None, 0   # "wait for more data"

With expected_length = 64 against a 7-byte frame:

  1. The parser stalls at the head of the buffer waiting for 65 bytes of payload that will never belong to this frame.
  2. Because parsing proceeds from start_message_index, this head-of-line blocks every frame queued behind it — including zone-status updates — until enough unrelated traffic accumulates.
  3. Once 65 bytes do accumulate, the parser reads a 64-byte window spanning several real frames, the checksum fails, and it logs Bad checksum and resyncs by MESSAGE_HEADER_LENGTH.
  4. The swallowed frames are then re-scanned from the resync point, so state does eventually recover — but with delay, log noise, and dropped updates in between.

On a Lync with a USB drive this triggers on every track change, since 0x11 and 0x12 are emitted as a pair each time the track advances (whether by soft key or naturally).

Suggested fix

0x11 and 0x12 need length-by-terminator rather than a fixed length: scan forward from data_idx for the 0x00 terminator and derive the payload length from it, with a sanity bound so a corrupt buffer cannot scan unboundedly.

The same approach was applied to the diagnostic sniffer used to gather these captures, and it resolved the misparses cleanly across all four capture sessions.

Two adjacent entries carry # should be 11 comments:

ZONE_NAME_RECEIVE_COMMAND: 13,    # should be 11
SOURCE_NAME_RECEIVE_COMMAND: 13,  # should be 11

These may be a related symptom of the same fixed-length assumption applied to string-valued commands. I have not captured either of them, so I am flagging the resemblance rather than asserting they are the same bug.

For contrast, the two MP3 commands whose payloads genuinely are fixed-length are correct as declared, and both were confirmed on the wire in the same session:

  • MP3_ON_RECEIVE_COMMAND (0x13) = 1 — observed 02 00 00 13 00 15
  • MP3_OFF_RECEIVE_COMMAND (0x14) = 17 — observed with payload "Device Not Found\0", exactly 17 bytes

So the map is not wrong in general; the defect is specific to the variable-length string commands.

Context

Found while reverse-engineering the MP3 soft keys for the Home Assistant integration. Full protocol analysis and the capture logs are in theharshl/htd-home-assistant#29.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions