diff --git a/src/assets/diagrams/architecture.svg b/src/assets/diagrams/architecture.svg new file mode 100644 index 0000000..ce7cf9a --- /dev/null +++ b/src/assets/diagrams/architecture.svg @@ -0,0 +1,120 @@ + diff --git a/src/assets/diagrams/handshake-sequence.svg b/src/assets/diagrams/handshake-sequence.svg new file mode 100644 index 0000000..afdc5c5 --- /dev/null +++ b/src/assets/diagrams/handshake-sequence.svg @@ -0,0 +1,75 @@ + diff --git a/src/assets/diagrams/packet-structure.svg b/src/assets/diagrams/packet-structure.svg new file mode 100644 index 0000000..f283fe9 --- /dev/null +++ b/src/assets/diagrams/packet-structure.svg @@ -0,0 +1,69 @@ + diff --git a/src/assets/diagrams/pca-file-format.svg b/src/assets/diagrams/pca-file-format.svg new file mode 100644 index 0000000..9ae91a2 --- /dev/null +++ b/src/assets/diagrams/pca-file-format.svg @@ -0,0 +1,73 @@ + diff --git a/src/assets/diagrams/per-block-whitening.svg b/src/assets/diagrams/per-block-whitening.svg new file mode 100644 index 0000000..aabb026 --- /dev/null +++ b/src/assets/diagrams/per-block-whitening.svg @@ -0,0 +1,107 @@ + diff --git a/src/assets/diagrams/session-key-derivation.svg b/src/assets/diagrams/session-key-derivation.svg new file mode 100644 index 0000000..12a508c --- /dev/null +++ b/src/assets/diagrams/session-key-derivation.svg @@ -0,0 +1,103 @@ + diff --git a/src/content/docs/explanation/architecture.md b/src/content/docs/explanation/architecture.mdx similarity index 96% rename from src/content/docs/explanation/architecture.md rename to src/content/docs/explanation/architecture.mdx index e031219..9cbc955 100644 --- a/src/content/docs/explanation/architecture.md +++ b/src/content/docs/explanation/architecture.mdx @@ -3,6 +3,10 @@ title: Architecture overview description: How the library, the Home Assistant integration, the mock panel, and the test stack fit together. --- +import Architecture from '../../../assets/diagrams/architecture.svg?raw'; + +
+ The project has four moving parts: 1. The Python library (`omni_pca`) — protocol, client, models. @@ -85,7 +89,7 @@ method calls. `helpers.py` is a strict no-HA-imports zone. Every translation between Omni's wire encoding and HA's UI encoding (zone-type → device-class, brightness conversion, HVAC mode round-trip, alarm state) lives there as -a pure function. 61 unit tests cover it; they run in <100ms because they +a pure function. 61 unit tests cover it; they run in `<100ms` because they don't have to boot HA. ## The mock panel @@ -184,6 +188,6 @@ What happens when a user toggles a light in the HA UI: 23. HA UI re-renders the light card ``` -Steps 4-13 happen in <50ms over a real TCP socket. Steps 15-22 happen in a +Steps 4-13 happen in `<50ms` over a real TCP socket. Steps 15-22 happen in a similar window. The user sees the light card update on the UI essentially immediately. diff --git a/src/content/docs/explanation/quirks.md b/src/content/docs/explanation/quirks.mdx similarity index 97% rename from src/content/docs/explanation/quirks.md rename to src/content/docs/explanation/quirks.mdx index a9ef4ab..dd5bb30 100644 --- a/src/content/docs/explanation/quirks.md +++ b/src/content/docs/explanation/quirks.mdx @@ -3,6 +3,9 @@ title: The two non-public quirks description: Why public Omni-Link clients silently fail on the first encrypted message — session key XOR mix and per-block pre-whitening before AES. --- +import SessionKey from '../../../assets/diagrams/session-key-derivation.svg?raw'; +import Whitening from '../../../assets/diagrams/per-block-whitening.svg?raw'; + The Omni-Link II protocol, as documented in the publicly-available spec, looks like a textbook AES-128-ECB session over TCP: handshake, derive a key, encrypt everything from then on. As implemented by HAI's PC Access 3.17, it isn't. @@ -38,6 +41,8 @@ exactly to talk to the panel. ## Quirk #1 — session key XOR mix + + The `ControllerKey` is the 16-byte AES-128 key that lives in the panel's NVRAM and inside the encrypted `.pca` config file. The naive expectation is that this key is what AES uses for the session. It isn't. @@ -85,6 +90,8 @@ in practice is always because you didn't apply the XOR mix." ## Quirk #2 — per-block XOR pre-whitening before AES + + This is the headline. Before AES-encrypting any payload block, the *first two bytes of every diff --git a/src/content/docs/reference/file-format.md b/src/content/docs/reference/file-format.mdx similarity index 98% rename from src/content/docs/reference/file-format.md rename to src/content/docs/reference/file-format.mdx index 3e8030f..dcb5577 100644 --- a/src/content/docs/reference/file-format.md +++ b/src/content/docs/reference/file-format.mdx @@ -3,6 +3,10 @@ title: .pca and PCA01.CFG file format description: Borland-Pascal LCG XOR cipher, three keys, and the on-disk layout for HAI's PC Access account export and app-settings files. --- +import PcaFormat from '../../../assets/diagrams/pca-file-format.svg?raw'; + + + The `.pca` and `PCA01.CFG` files written by HAI's PC Access are *not* AES. Despite the existence of `clsAES` in the same binary, both file formats use a Borland-Pascal-style linear-congruential generator (LCG) keystream XORed diff --git a/src/content/docs/reference/protocol.md b/src/content/docs/reference/protocol.mdx similarity index 98% rename from src/content/docs/reference/protocol.md rename to src/content/docs/reference/protocol.mdx index 568859c..bd9c1eb 100644 --- a/src/content/docs/reference/protocol.md +++ b/src/content/docs/reference/protocol.mdx @@ -3,6 +3,11 @@ title: Omni-Link II protocol description: Byte-level handshake, packet layouts, key derivation, and steady-state encryption rules for Omni-Link II as implemented by HAI's PC Access 3.17. --- +import HandshakeSequence from '../../../assets/diagrams/handshake-sequence.svg?raw'; +import PacketStructure from '../../../assets/diagrams/packet-structure.svg?raw'; + + + TCP/v2 PC Access opens a secure session by exchanging two unencrypted control packets to derive a per-session AES-128-ECB key from the panel's 16-byte `ControllerKey` XOR-mixed with a 5-byte controller-supplied `SessionID`, then @@ -71,6 +76,8 @@ UDP / 1808 TCP). ## Packet payload byte layouts + + All offsets are **into the packet payload**, i.e., after the 4-byte outer header (`[seq_hi][seq_lo][type][reserved=0]`).