Per-packet diagrams: every PacketType now has a wire-format visual
src/assets/diagrams/packet-control-empty.svg Covers the five empty-payload control types: NoMessage (0x00), ClientRequestNewSession (0x01), ClientSessionTerminated (0x05), ControllerSessionTerminated (0x06), ControllerCannotStartNewSession (0x07). Single 4-byte header diagram with the type byte highlighted, '(no payload)' shown as a dashed empty box, and all five type values enumerated below. src/assets/diagrams/packet-controller-ack-new-session.svg Type 0x02. 11 bytes total — 4-byte header + 2-byte protocol version (0x00 0x01) + 5-byte SessionID nonce. SessionID cells in accent colour because they're what feed quirk #1's session key XOR mix. Bottom annotation explains the proto-version is hard-coded. src/assets/diagrams/packet-secure-session.svg Types 0x03 and 0x04 share the same shape both directions. Two-row layout: top shows the plaintext (5-byte SessionID echo + 11 zero-pad cells with 'zero pad to 16 bytes'); bottom shows the wire form (header + 16-byte AES ciphertext block). Highlights the symmetric client-up/controller-down design. src/assets/diagrams/packet-omnilink-message.svg Covers all four conversation packet types in one diagram: 0x10 (v1 encrypted), 0x11 (v1 plaintext), 0x20 (v2 encrypted), 0x21 (v2 plaintext). Top row shows the encrypted variant with N x 16-byte ciphertext blocks; brace down to bottom row showing the inner Message format (start byte + length + opcode + data + CRC u16 LE). Same diagram serves all 4 since the layout is identical except for the type byte and whether AES is applied. src/content/docs/reference/protocol.mdx Added five new sections so every PacketType is now documented: - NoMessage (0x00) folded into the ClientRequestNewSession heading (same empty-payload layout) - ClientSessionTerminated (0x05) / ControllerSessionTerminated (0x06) / ControllerCannotStartNewSession (0x07) get a combined section with a one-paragraph explanation of when each fires - OmniLinkMessage (0x10), OmniLinkUnencryptedMessage (0x11), OmniLink2Message (0x20), OmniLink2UnencryptedMessage (0x21) consolidated into one section with the diagram + a note about which TCP/PC-Access actually uses ClientRequestSecureSession + ControllerAckSecureSession merged into one section since they share the diagram; original prose preserved as #### subsections beneath. Build: 23 pages clean. Protocol page now 118 KB (was 92 KB) — six inline SVG titles confirmed via grep on the rendered HTML. Every packet type defined in omni_pca.opcodes.PacketType (12 values) is now visualized in the docs.
This commit is contained in:
parent
d5d2ea3d32
commit
0c245b0af5
49
src/assets/diagrams/packet-control-empty.svg
Normal file
49
src/assets/diagrams/packet-control-empty.svg
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 660 200" role="img" aria-labelledby="pce-title pce-desc" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace">
|
||||||
|
<title id="pce-title">Empty-payload control packets</title>
|
||||||
|
<desc id="pce-desc">Five packet types share an identical four-byte structure with no payload: NoMessage (0x00), ClientRequestNewSession (0x01), ClientSessionTerminated (0x05), ControllerSessionTerminated (0x06), and ControllerCannotStartNewSession (0x07). The wire packet is exactly four bytes total.</desc>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.label { font-size: 12px; font-weight: 700; fill: currentColor; }
|
||||||
|
.meta { font-size: 10px; opacity: 0.75; font-style: italic; fill: currentColor; }
|
||||||
|
.byte { font-size: 11px; font-weight: 600; fill: currentColor; text-anchor: middle; }
|
||||||
|
.cell { stroke: currentColor; stroke-width: 1; fill: currentColor; fill-opacity: 0.06; }
|
||||||
|
.cell-type { stroke: currentColor; stroke-width: 1; fill: currentColor; fill-opacity: 0.13; }
|
||||||
|
.cell-empty { stroke: currentColor; stroke-width: 1; fill: none; stroke-dasharray: 4 3; stroke-opacity: 0.5; }
|
||||||
|
.group-label { font-size: 10px; font-weight: 700; fill: currentColor; opacity: 0.55; letter-spacing: 0.06em; text-anchor: middle; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<text x="20" y="28" class="label">Empty-payload control packets</text>
|
||||||
|
<text x="20" y="46" class="meta">4 bytes total on the wire — header only, no payload.</text>
|
||||||
|
|
||||||
|
<!-- Header row -->
|
||||||
|
<g transform="translate(20, 70)">
|
||||||
|
<!-- seq u16 BE: two cells -->
|
||||||
|
<rect x="0" y="0" width="60" height="48" class="cell"/>
|
||||||
|
<rect x="60" y="0" width="60" height="48" class="cell"/>
|
||||||
|
<!-- type -->
|
||||||
|
<rect x="120" y="0" width="80" height="48" class="cell-type"/>
|
||||||
|
<!-- reserved -->
|
||||||
|
<rect x="200" y="0" width="60" height="48" class="cell"/>
|
||||||
|
<!-- payload -->
|
||||||
|
<rect x="260" y="0" width="380" height="48" class="cell-empty"/>
|
||||||
|
|
||||||
|
<text x="30" y="20" class="byte">seq_hi</text>
|
||||||
|
<text x="30" y="36" class="byte">u8</text>
|
||||||
|
<text x="90" y="20" class="byte">seq_lo</text>
|
||||||
|
<text x="90" y="36" class="byte">u8</text>
|
||||||
|
<text x="160" y="20" class="byte">type</text>
|
||||||
|
<text x="160" y="36" class="byte">u8</text>
|
||||||
|
<text x="230" y="20" class="byte">reserved</text>
|
||||||
|
<text x="230" y="36" class="byte">0x00</text>
|
||||||
|
<text x="450" y="30" class="byte" font-style="italic" opacity="0.6">(no payload)</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Group label brackets -->
|
||||||
|
<text x="90" y="142" class="group-label">SEQ NUMBER · u16 BE</text>
|
||||||
|
<text x="160" y="142" class="group-label">PACKET TYPE</text>
|
||||||
|
|
||||||
|
<!-- Type byte values -->
|
||||||
|
<text x="20" y="170" class="meta" font-style="normal" font-weight="700">type =</text>
|
||||||
|
<text x="70" y="170" class="meta">0x00 NoMessage · 0x01 ClientRequestNewSession · 0x05 ClientSessionTerminated</text>
|
||||||
|
<text x="70" y="186" class="meta">0x06 ControllerSessionTerminated · 0x07 ControllerCannotStartNewSession</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.9 KiB |
66
src/assets/diagrams/packet-controller-ack-new-session.svg
Normal file
66
src/assets/diagrams/packet-controller-ack-new-session.svg
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 660 200" role="img" aria-labelledby="pcans-title pcans-desc" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace">
|
||||||
|
<title id="pcans-title">ControllerAckNewSession (type 0x02)</title>
|
||||||
|
<desc id="pcans-desc">Reply from the controller to a new session request. Carries a 7-byte payload: a hard-coded protocol version (00 01) followed by a 5-byte random SessionID nonce. The SessionID is what gets XOR-mixed with the ControllerKey to derive the AES session key.</desc>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.label { font-size: 12px; font-weight: 700; fill: currentColor; }
|
||||||
|
.meta { font-size: 10px; opacity: 0.75; font-style: italic; fill: currentColor; }
|
||||||
|
.byte { font-size: 11px; font-weight: 600; fill: currentColor; text-anchor: middle; }
|
||||||
|
.byte-acc { font-size: 11px; font-weight: 700; fill: var(--sl-color-accent, #d97706); text-anchor: middle; }
|
||||||
|
.cell { stroke: currentColor; stroke-width: 1; fill: currentColor; fill-opacity: 0.06; }
|
||||||
|
.cell-type { stroke: currentColor; stroke-width: 1; fill: currentColor; fill-opacity: 0.13; }
|
||||||
|
.cell-acc { stroke: var(--sl-color-accent, #d97706); stroke-width: 1; fill: var(--sl-color-accent, #d97706); fill-opacity: 0.18; }
|
||||||
|
.group-label { font-size: 10px; font-weight: 700; fill: currentColor; opacity: 0.55; letter-spacing: 0.06em; text-anchor: middle; }
|
||||||
|
.group-label-acc { font-size: 10px; font-weight: 700; fill: var(--sl-color-accent, #d97706); opacity: 0.95; letter-spacing: 0.06em; text-anchor: middle; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<text x="20" y="28" class="label">ControllerAckNewSession (type 0x02)</text>
|
||||||
|
<text x="20" y="46" class="meta">11 bytes total. Plaintext on the wire.</text>
|
||||||
|
|
||||||
|
<g transform="translate(20, 70)">
|
||||||
|
<!-- header -->
|
||||||
|
<rect x="0" y="0" width="40" height="48" class="cell"/>
|
||||||
|
<rect x="40" y="0" width="40" height="48" class="cell"/>
|
||||||
|
<rect x="80" y="0" width="40" height="48" class="cell-type"/>
|
||||||
|
<rect x="120" y="0" width="40" height="48" class="cell"/>
|
||||||
|
<text x="20" y="20" class="byte">seq_hi</text>
|
||||||
|
<text x="20" y="36" class="byte">u8</text>
|
||||||
|
<text x="60" y="20" class="byte">seq_lo</text>
|
||||||
|
<text x="60" y="36" class="byte">u8</text>
|
||||||
|
<text x="100" y="20" class="byte">type</text>
|
||||||
|
<text x="100" y="36" class="byte">0x02</text>
|
||||||
|
<text x="140" y="20" class="byte">resv</text>
|
||||||
|
<text x="140" y="36" class="byte">0x00</text>
|
||||||
|
|
||||||
|
<!-- protocol version 2 bytes -->
|
||||||
|
<rect x="170" y="0" width="40" height="48" class="cell"/>
|
||||||
|
<rect x="210" y="0" width="40" height="48" class="cell"/>
|
||||||
|
<text x="190" y="20" class="byte">proto_hi</text>
|
||||||
|
<text x="190" y="36" class="byte">0x00</text>
|
||||||
|
<text x="230" y="20" class="byte">proto_lo</text>
|
||||||
|
<text x="230" y="36" class="byte">0x01</text>
|
||||||
|
|
||||||
|
<!-- session id 5 bytes -->
|
||||||
|
<rect x="260" y="0" width="40" height="48" class="cell-acc"/>
|
||||||
|
<rect x="300" y="0" width="40" height="48" class="cell-acc"/>
|
||||||
|
<rect x="340" y="0" width="40" height="48" class="cell-acc"/>
|
||||||
|
<rect x="380" y="0" width="40" height="48" class="cell-acc"/>
|
||||||
|
<rect x="420" y="0" width="40" height="48" class="cell-acc"/>
|
||||||
|
<text x="280" y="20" class="byte-acc">sid 0</text>
|
||||||
|
<text x="280" y="36" class="byte-acc">u8</text>
|
||||||
|
<text x="320" y="20" class="byte-acc">sid 1</text>
|
||||||
|
<text x="320" y="36" class="byte-acc">u8</text>
|
||||||
|
<text x="360" y="20" class="byte-acc">sid 2</text>
|
||||||
|
<text x="360" y="36" class="byte-acc">u8</text>
|
||||||
|
<text x="400" y="20" class="byte-acc">sid 3</text>
|
||||||
|
<text x="400" y="36" class="byte-acc">u8</text>
|
||||||
|
<text x="440" y="20" class="byte-acc">sid 4</text>
|
||||||
|
<text x="440" y="36" class="byte-acc">u8</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<text x="80" y="142" class="group-label">HEADER (4 bytes)</text>
|
||||||
|
<text x="210" y="142" class="group-label">PROTO VERSION</text>
|
||||||
|
<text x="360" y="142" class="group-label-acc">SESSION ID (5-byte nonce) — feeds session key XOR mix</text>
|
||||||
|
|
||||||
|
<text x="20" y="180" class="meta">PC Access hard-rejects any proto version other than 00 01. The SessionID is freshly random per session.</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
77
src/assets/diagrams/packet-omnilink-message.svg
Normal file
77
src/assets/diagrams/packet-omnilink-message.svg
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 660 280" role="img" aria-labelledby="polm-title polm-desc" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace">
|
||||||
|
<title id="polm-title">OmniLinkMessage and OmniLink2Message — encrypted vs unencrypted variants</title>
|
||||||
|
<desc id="polm-desc">Four packet types share this layout. v1 encrypted (0x10), v1 plaintext (0x11), v2 encrypted (0x20), and v2 plaintext (0x21). The payload is one or more 16-byte AES blocks for encrypted variants, or the inner Message bytes laid out directly for unencrypted variants. The inner Message starts with a start byte (0x41 for v1, 0x21 for v2), a length, the opcode, data, and a two-byte CRC.</desc>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.label { font-size: 12px; font-weight: 700; fill: currentColor; }
|
||||||
|
.meta { font-size: 10px; opacity: 0.75; font-style: italic; fill: currentColor; }
|
||||||
|
.byte { font-size: 11px; font-weight: 600; fill: currentColor; text-anchor: middle; }
|
||||||
|
.byte-acc { font-size: 11px; font-weight: 700; fill: var(--sl-color-accent, #d97706); text-anchor: middle; }
|
||||||
|
.cell { stroke: currentColor; stroke-width: 1; fill: currentColor; fill-opacity: 0.06; }
|
||||||
|
.cell-type { stroke: currentColor; stroke-width: 1; fill: currentColor; fill-opacity: 0.13; }
|
||||||
|
.cell-enc { stroke: var(--sl-color-accent, #d97706); stroke-width: 1.2; fill: var(--sl-color-accent, #d97706); fill-opacity: 0.15; }
|
||||||
|
.cell-msg { stroke: currentColor; stroke-width: 1; fill: currentColor; fill-opacity: 0.04; }
|
||||||
|
.group-label { font-size: 10px; font-weight: 700; fill: currentColor; opacity: 0.55; letter-spacing: 0.06em; text-anchor: middle; }
|
||||||
|
.group-label-acc { font-size: 10px; font-weight: 700; fill: var(--sl-color-accent, #d97706); opacity: 0.95; letter-spacing: 0.06em; text-anchor: middle; }
|
||||||
|
.brace { stroke: currentColor; stroke-width: 1.2; fill: none; opacity: 0.45; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<text x="20" y="28" class="label">OmniLinkMessage / OmniLink2Message — four variants</text>
|
||||||
|
<text x="20" y="46" class="meta">v1 = 0x10 encrypted · 0x11 plaintext · v2 = 0x20 encrypted · 0x21 plaintext · same shape, different framing.</text>
|
||||||
|
|
||||||
|
<!-- Encrypted variant -->
|
||||||
|
<text x="20" y="78" class="meta" font-weight="700" font-style="normal">encrypted variants (0x10 / 0x20) — payload is N × 16 bytes of AES ciphertext:</text>
|
||||||
|
|
||||||
|
<g transform="translate(20, 90)">
|
||||||
|
<rect x="0" y="0" width="40" height="44" class="cell"/>
|
||||||
|
<rect x="40" y="0" width="40" height="44" class="cell"/>
|
||||||
|
<rect x="80" y="0" width="40" height="44" class="cell-type"/>
|
||||||
|
<rect x="120" y="0" width="40" height="44" class="cell"/>
|
||||||
|
<text x="20" y="20" class="byte">seq_hi</text>
|
||||||
|
<text x="60" y="20" class="byte">seq_lo</text>
|
||||||
|
<text x="100" y="20" class="byte">type</text>
|
||||||
|
<text x="100" y="36" class="byte">10 / 20</text>
|
||||||
|
<text x="140" y="20" class="byte">resv</text>
|
||||||
|
|
||||||
|
<rect x="170" y="0" width="120" height="44" class="cell-enc"/>
|
||||||
|
<rect x="290" y="0" width="120" height="44" class="cell-enc"/>
|
||||||
|
<rect x="410" y="0" width="120" height="44" class="cell-enc"/>
|
||||||
|
<rect x="530" y="0" width="110" height="44" class="cell-enc"/>
|
||||||
|
<text x="230" y="20" class="byte-acc">block 1</text>
|
||||||
|
<text x="230" y="36" class="byte-acc">16 B</text>
|
||||||
|
<text x="350" y="20" class="byte-acc">block 2</text>
|
||||||
|
<text x="350" y="36" class="byte-acc">16 B</text>
|
||||||
|
<text x="470" y="20" class="byte-acc">block 3</text>
|
||||||
|
<text x="470" y="36" class="byte-acc">…</text>
|
||||||
|
<text x="585" y="20" class="byte-acc">block N</text>
|
||||||
|
<text x="585" y="36" class="byte-acc">16 B</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<text x="80" y="155" class="group-label">HEADER (4 B)</text>
|
||||||
|
<text x="405" y="155" class="group-label-acc">CIPHERTEXT (per-block whitened)</text>
|
||||||
|
|
||||||
|
<!-- Brace down to inner message -->
|
||||||
|
<path d="M 175 162 Q 175 180 195 188 L 460 188 Q 480 188 480 200" class="brace"/>
|
||||||
|
<text x="480" y="210" text-anchor="middle" class="meta">decrypts + unwhitens to →</text>
|
||||||
|
|
||||||
|
<!-- Inner message -->
|
||||||
|
<text x="20" y="232" class="meta" font-weight="700" font-style="normal">inner Message (after AES + unwhiten, or directly on the wire for 0x11 / 0x21):</text>
|
||||||
|
|
||||||
|
<g transform="translate(20, 244)">
|
||||||
|
<rect x="0" y="0" width="60" height="32" class="cell-msg"/>
|
||||||
|
<rect x="60" y="0" width="60" height="32" class="cell-msg"/>
|
||||||
|
<rect x="120" y="0" width="60" height="32" class="cell-msg"/>
|
||||||
|
<rect x="180" y="0" width="320" height="32" class="cell-msg"/>
|
||||||
|
<rect x="500" y="0" width="100" height="32" class="cell-msg"/>
|
||||||
|
<text x="30" y="14" class="byte">start</text>
|
||||||
|
<text x="30" y="26" class="byte">41/21</text>
|
||||||
|
<text x="90" y="14" class="byte">length</text>
|
||||||
|
<text x="90" y="26" class="byte">u8</text>
|
||||||
|
<text x="150" y="14" class="byte">opcode</text>
|
||||||
|
<text x="150" y="26" class="byte">u8</text>
|
||||||
|
<text x="340" y="14" class="byte">data</text>
|
||||||
|
<text x="340" y="26" class="byte">N bytes</text>
|
||||||
|
<text x="550" y="14" class="byte">CRC u16</text>
|
||||||
|
<text x="550" y="26" class="byte">LE · MODBUS</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.9 KiB |
81
src/assets/diagrams/packet-secure-session.svg
Normal file
81
src/assets/diagrams/packet-secure-session.svg
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 660 240" role="img" aria-labelledby="pss-title pss-desc" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace">
|
||||||
|
<title id="pss-title">ClientRequestSecureSession (0x03) and ControllerAckSecureSession (0x04)</title>
|
||||||
|
<desc id="pss-desc">Both secure-session packets carry the same payload shape: the 5-byte SessionID echoed back, zero-padded to a 16-byte AES block, encrypted with the freshly-derived session key, and per-block whitened. 20 bytes total on the wire.</desc>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.label { font-size: 12px; font-weight: 700; fill: currentColor; }
|
||||||
|
.meta { font-size: 10px; opacity: 0.75; font-style: italic; fill: currentColor; }
|
||||||
|
.byte { font-size: 11px; font-weight: 600; fill: currentColor; text-anchor: middle; }
|
||||||
|
.byte-acc { font-size: 11px; font-weight: 700; fill: var(--sl-color-accent, #d97706); text-anchor: middle; }
|
||||||
|
.cell { stroke: currentColor; stroke-width: 1; fill: currentColor; fill-opacity: 0.06; }
|
||||||
|
.cell-type { stroke: currentColor; stroke-width: 1; fill: currentColor; fill-opacity: 0.13; }
|
||||||
|
.cell-enc { stroke: var(--sl-color-accent, #d97706); stroke-width: 1.2; fill: var(--sl-color-accent, #d97706); fill-opacity: 0.15; }
|
||||||
|
.cell-pad { stroke: var(--sl-color-accent, #d97706); stroke-width: 1; fill: var(--sl-color-accent, #d97706); fill-opacity: 0.06; stroke-dasharray: 3 2; }
|
||||||
|
.group-label { font-size: 10px; font-weight: 700; fill: currentColor; opacity: 0.55; letter-spacing: 0.06em; text-anchor: middle; }
|
||||||
|
.group-label-acc { font-size: 10px; font-weight: 700; fill: var(--sl-color-accent, #d97706); opacity: 0.95; letter-spacing: 0.06em; text-anchor: middle; }
|
||||||
|
.arrow { stroke: currentColor; stroke-width: 1.4; fill: none; opacity: 0.45; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<text x="20" y="28" class="label">ClientRequestSecureSession (0x03) · ControllerAckSecureSession (0x04)</text>
|
||||||
|
<text x="20" y="46" class="meta">Same shape both directions. 20 bytes total. Encrypted payload = SessionID echo, zero-padded, AES-encrypted with per-block whitening.</text>
|
||||||
|
|
||||||
|
<!-- Plaintext form -->
|
||||||
|
<text x="20" y="76" class="meta" font-weight="700" font-style="normal">plaintext (before AES + whitening):</text>
|
||||||
|
|
||||||
|
<g transform="translate(20, 86)">
|
||||||
|
<rect x="0" y="0" width="40" height="40" class="cell-enc"/>
|
||||||
|
<rect x="40" y="0" width="40" height="40" class="cell-enc"/>
|
||||||
|
<rect x="80" y="0" width="40" height="40" class="cell-enc"/>
|
||||||
|
<rect x="120" y="0" width="40" height="40" class="cell-enc"/>
|
||||||
|
<rect x="160" y="0" width="40" height="40" class="cell-enc"/>
|
||||||
|
<text x="20" y="18" class="byte-acc">sid 0</text>
|
||||||
|
<text x="60" y="18" class="byte-acc">sid 1</text>
|
||||||
|
<text x="100" y="18" class="byte-acc">sid 2</text>
|
||||||
|
<text x="140" y="18" class="byte-acc">sid 3</text>
|
||||||
|
<text x="180" y="18" class="byte-acc">sid 4</text>
|
||||||
|
<text x="20" y="32" class="byte-acc">u8</text>
|
||||||
|
<text x="60" y="32" class="byte-acc">u8</text>
|
||||||
|
<text x="100" y="32" class="byte-acc">u8</text>
|
||||||
|
<text x="140" y="32" class="byte-acc">u8</text>
|
||||||
|
<text x="180" y="32" class="byte-acc">u8</text>
|
||||||
|
|
||||||
|
<!-- 11 zero-pad cells -->
|
||||||
|
<rect x="200" y="0" width="40" height="40" class="cell-pad"/>
|
||||||
|
<rect x="240" y="0" width="40" height="40" class="cell-pad"/>
|
||||||
|
<rect x="280" y="0" width="40" height="40" class="cell-pad"/>
|
||||||
|
<rect x="320" y="0" width="40" height="40" class="cell-pad"/>
|
||||||
|
<rect x="360" y="0" width="40" height="40" class="cell-pad"/>
|
||||||
|
<rect x="400" y="0" width="40" height="40" class="cell-pad"/>
|
||||||
|
<rect x="440" y="0" width="40" height="40" class="cell-pad"/>
|
||||||
|
<rect x="480" y="0" width="40" height="40" class="cell-pad"/>
|
||||||
|
<rect x="520" y="0" width="40" height="40" class="cell-pad"/>
|
||||||
|
<rect x="560" y="0" width="40" height="40" class="cell-pad"/>
|
||||||
|
<rect x="600" y="0" width="40" height="40" class="cell-pad"/>
|
||||||
|
<text x="420" y="20" class="byte-acc" font-style="italic" opacity="0.7">…zero pad to 16 bytes…</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Brackets -->
|
||||||
|
<text x="100" y="146" class="group-label-acc">SESSION ID ECHO</text>
|
||||||
|
<text x="420" y="146" class="group-label">ZERO PADDING</text>
|
||||||
|
|
||||||
|
<!-- Wire form -->
|
||||||
|
<text x="20" y="178" class="meta" font-weight="700" font-style="normal">on the wire (after AES + whitening):</text>
|
||||||
|
|
||||||
|
<g transform="translate(20, 188)">
|
||||||
|
<!-- header -->
|
||||||
|
<rect x="0" y="0" width="40" height="40" class="cell"/>
|
||||||
|
<rect x="40" y="0" width="40" height="40" class="cell"/>
|
||||||
|
<rect x="80" y="0" width="40" height="40" class="cell-type"/>
|
||||||
|
<rect x="120" y="0" width="40" height="40" class="cell"/>
|
||||||
|
<text x="20" y="18" class="byte">seq_hi</text>
|
||||||
|
<text x="60" y="18" class="byte">seq_lo</text>
|
||||||
|
<text x="100" y="18" class="byte">type</text>
|
||||||
|
<text x="100" y="32" class="byte">03/04</text>
|
||||||
|
<text x="140" y="18" class="byte">resv</text>
|
||||||
|
<text x="140" y="32" class="byte">0x00</text>
|
||||||
|
|
||||||
|
<!-- ciphertext -->
|
||||||
|
<rect x="170" y="0" width="470" height="40" class="cell-enc"/>
|
||||||
|
<text x="405" y="20" class="byte-acc">16-byte AES-128 ciphertext (one block)</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.0 KiB |
@ -5,6 +5,10 @@ description: Byte-level handshake, packet layouts, key derivation, and steady-st
|
|||||||
|
|
||||||
import HandshakeSequence from '../../../assets/diagrams/handshake-sequence.svg?raw';
|
import HandshakeSequence from '../../../assets/diagrams/handshake-sequence.svg?raw';
|
||||||
import PacketStructure from '../../../assets/diagrams/packet-structure.svg?raw';
|
import PacketStructure from '../../../assets/diagrams/packet-structure.svg?raw';
|
||||||
|
import PacketEmpty from '../../../assets/diagrams/packet-control-empty.svg?raw';
|
||||||
|
import PacketAckNew from '../../../assets/diagrams/packet-controller-ack-new-session.svg?raw';
|
||||||
|
import PacketSecureSession from '../../../assets/diagrams/packet-secure-session.svg?raw';
|
||||||
|
import PacketOmniLink from '../../../assets/diagrams/packet-omnilink-message.svg?raw';
|
||||||
|
|
||||||
<div style="margin: 1.5rem 0;" set:html={HandshakeSequence} />
|
<div style="margin: 1.5rem 0;" set:html={HandshakeSequence} />
|
||||||
|
|
||||||
@ -81,14 +85,23 @@ UDP / 1808 TCP).
|
|||||||
All offsets are **into the packet payload**, i.e., after the 4-byte outer
|
All offsets are **into the packet payload**, i.e., after the 4-byte outer
|
||||||
header (`[seq_hi][seq_lo][type][reserved=0]`).
|
header (`[seq_hi][seq_lo][type][reserved=0]`).
|
||||||
|
|
||||||
### `ClientRequestNewSession` (type 0x01)
|
### `NoMessage` (type 0x00) and `ClientRequestNewSession` (type 0x01)
|
||||||
|
|
||||||
|
<div style="margin: 1rem 0;" set:html={PacketEmpty} />
|
||||||
|
|
||||||
|
Both have empty payloads. `NoMessage` is the protocol's keepalive — sent
|
||||||
|
when the client wants to confirm the TCP connection is alive without doing
|
||||||
|
anything. `ClientRequestNewSession` is step 1 of the handshake. Wire = 4
|
||||||
|
bytes total either way (just the header).
|
||||||
|
|
||||||
| Offset | Size | Field | Notes |
|
| Offset | Size | Field | Notes |
|
||||||
|-------:|-----:|-------|-------|
|
|-------:|-----:|-------|-------|
|
||||||
| — | 0 | (no payload) | `clsOmniLinkPacket.Data == null` (line 1283/1688). Wire = 4 bytes total: `00 01 01 00`. |
|
| — | 0 | (no payload) | `clsOmniLinkPacket.Data == null` (line 1283/1688). |
|
||||||
|
|
||||||
### `ControllerAckNewSession` (type 0x02)
|
### `ControllerAckNewSession` (type 0x02)
|
||||||
|
|
||||||
|
<div style="margin: 1rem 0;" set:html={PacketAckNew} />
|
||||||
|
|
||||||
Payload size **7 bytes** (TCP reader hardcodes `tcpReadBytes(array, 7)` on this
|
Payload size **7 bytes** (TCP reader hardcodes `tcpReadBytes(array, 7)` on this
|
||||||
type, line 1714).
|
type, line 1714).
|
||||||
|
|
||||||
@ -100,7 +113,19 @@ type, line 1714).
|
|||||||
|
|
||||||
Total wire packet: 4-byte header + 7-byte payload = 11 bytes.
|
Total wire packet: 4-byte header + 7-byte payload = 11 bytes.
|
||||||
|
|
||||||
### `ClientRequestSecureSession` (type 0x03)
|
### `ClientRequestSecureSession` (type 0x03) and `ControllerAckSecureSession` (type 0x04)
|
||||||
|
|
||||||
|
<div style="margin: 1rem 0;" set:html={PacketSecureSession} />
|
||||||
|
|
||||||
|
Both directions carry the same 5-byte SessionID echo, zero-padded to a
|
||||||
|
16-byte AES block, encrypted, and per-block whitened. The client builds
|
||||||
|
step 3 with the SessionID it received in step 2; the controller replies in
|
||||||
|
step 4 with the same SessionID re-encrypted. The implicit "did the AES
|
||||||
|
round-trip succeed?" is the only proof both sides have the same key — ECB
|
||||||
|
provides no integrity check, so the wrong key produces 16 bytes of garbage
|
||||||
|
that the receiver will dutifully accept (see notes below the next table).
|
||||||
|
|
||||||
|
#### `ClientRequestSecureSession` plaintext layout
|
||||||
|
|
||||||
Payload **before encryption** is 5 bytes; **on the wire** it is 16 bytes (one
|
Payload **before encryption** is 5 bytes; **on the wire** it is 16 bytes (one
|
||||||
AES block).
|
AES block).
|
||||||
@ -118,7 +143,7 @@ Then `EncryptPacket` runs (lines 396-401):
|
|||||||
controller can only decrypt this if it computed the same key from its own
|
controller can only decrypt this if it computed the same key from its own
|
||||||
`ControllerKey` and the SessionID it generated.
|
`ControllerKey` and the SessionID it generated.
|
||||||
|
|
||||||
### `ControllerAckSecureSession` (type 0x04)
|
#### `ControllerAckSecureSession` (type 0x04)
|
||||||
|
|
||||||
Payload size **16 bytes** on the wire (TCP reader hardcodes
|
Payload size **16 bytes** on the wire (TCP reader hardcodes
|
||||||
`tcpReadBytes(array, 16)`, line 1722-1729).
|
`tcpReadBytes(array, 16)`, line 1722-1729).
|
||||||
@ -136,6 +161,37 @@ bytes of garbage (no exception), so the *only* thing that protects the client
|
|||||||
from accepting a wrong-key ack is the controller pre-validating step 3 and
|
from accepting a wrong-key ack is the controller pre-validating step 3 and
|
||||||
sending `ControllerSessionTerminated` instead.
|
sending `ControllerSessionTerminated` instead.
|
||||||
|
|
||||||
|
### `ClientSessionTerminated` (type 0x05), `ControllerSessionTerminated` (type 0x06), `ControllerCannotStartNewSession` (type 0x07)
|
||||||
|
|
||||||
|
All three share the empty-payload layout. `ClientSessionTerminated` is sent
|
||||||
|
by `OmniConnection.close()` for a graceful shutdown. `ControllerSessionTerminated`
|
||||||
|
is what the panel sends on a wrong key, a session timeout, or when it kicks
|
||||||
|
an idle client. `ControllerCannotStartNewSession` is the panel saying "I'm
|
||||||
|
already talking to someone; the protocol is single-client" — it arrives in
|
||||||
|
response to a `ClientRequestNewSession` when an existing session is open.
|
||||||
|
|
||||||
|
The four-byte wire form for all three is just `[seq_hi seq_lo TYPE 0x00]`.
|
||||||
|
|
||||||
|
### `OmniLinkMessage` (type 0x10), `OmniLinkUnencryptedMessage` (type 0x11), `OmniLink2Message` (type 0x20), `OmniLink2UnencryptedMessage` (type 0x21)
|
||||||
|
|
||||||
|
<div style="margin: 1rem 0;" set:html={PacketOmniLink} />
|
||||||
|
|
||||||
|
These four packet types are the actual conversation — every command,
|
||||||
|
status query, and unsolicited push event flows through one of them. The
|
||||||
|
shape is the same; the type byte tells you whether the payload is
|
||||||
|
encrypted (`0x10` / `0x20`) or laid bare on the wire (`0x11` / `0x21`),
|
||||||
|
and which protocol version (`v1` for `0x1x`, `v2` for `0x2x`).
|
||||||
|
|
||||||
|
The encrypted variants on TCP/v2 are what PC Access actually uses
|
||||||
|
day-to-day. The unencrypted variants exist for the serial path and for
|
||||||
|
diagnostics. PC Access never sends `OmniLink2UnencryptedMessage` over
|
||||||
|
TCP — the panel would accept it but no production deployment uses it.
|
||||||
|
|
||||||
|
The inner Message format is documented at the top of this page (start
|
||||||
|
byte, length, opcode, data, CRC-16/MODBUS). The opcode tables for v1 and
|
||||||
|
v2 live in [`omni_pca.opcodes`](https://github.com/rsp2k/omni-pca/blob/main/src/omni_pca/opcodes.py)
|
||||||
|
— too long to reproduce here.
|
||||||
|
|
||||||
### v2 `Login` (inner opcode 42) — *defined but unused on TCP*
|
### v2 `Login` (inner opcode 42) — *defined but unused on TCP*
|
||||||
|
|
||||||
If the protocol ever calls for it, the layout is (`clsOL2MsgLogin.cs`):
|
If the protocol ever calls for it, the layout is (`clsOL2MsgLogin.cs`):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user