Skip to content

Latest commit

 

History

History
52 lines (41 loc) · 2.23 KB

File metadata and controls

52 lines (41 loc) · 2.23 KB

Adding a brand

The layout separates what is shared from what is LG's:

src/tv_privacy/
  ssdp.py wol.py            discovery and wake, brand-agnostic
  config.py keystore.py     host/brand resolution, client keys, XDG paths
  runlog.py errors.py       JSONL audit log, error taxonomy
  brands/
    __init__.py             registry + fingerprint-based detection
    lg/                     SSAP transport, manifests, eulaStatus, lab tooling
  cli/                      commands, dispatching to a backend

A backend is a module exposing matches_fingerprint(found), open_session(...), DEFAULT_SCOPE, and privacy / power submodules. Register it in brands.BRANDS and tv-privacy discover will identify it automatically.

The interface is deliberately thin, and should stay that way until there are two. Designing an abstract backend against a single implementation is guessing. What is already known to differ:

  • Transport. LG uses SSAP over a WebSocket with an on-screen pairing prompt. Samsung Tizen uses a different WebSocket protocol with token auth; Sony uses HTTP with a pre-shared key or PIN; Vizio uses HTTPS on 7345 with a PIN.
  • Where consent lives. On LG it is one object-valued setting reachable through a generic settings API. No other vendor need expose it that way, or at all — the first thing to establish for a new brand is whether the consents are reachable over the network in any form.
  • Discovery. SSDP is common but not universal, and fingerprints differ.

lab is LG-only and refuses to run against another brand rather than emitting a wall of 404s that look like findings.

Testing without a TV

tests/fake_tv.py is a small webOS TV that replays recorded SSAP responses distilled from the real probe logs, so pytest runs in CI with no hardware:

uv run pytest

It can also be told to misbehave in the two ways that matter — acknowledging a write without persisting it, and closing the socket mid-registration without an error frame — because both are how real firmware fails and neither can be provoked on demand.

The corpus in tests/fixtures/ is scrubbed of MAC addresses, LAN IPs and UPnP UUIDs by tools/distill_logs.py, which refuses to write if anything identifier-shaped survives.