Skip to content

fix(heating): refuse WHO 4 DIMENSION 22 only without an actuator token in WHERE (oracle thermo-dim22) - #151

Draft
gdluck wants to merge 1 commit into
OpenWebNet-HA:masterfrom
gdluck:fix/who4-dimension-22-guard-crash-forms
Draft

gdluck wants to merge 1 commit into
OpenWebNet-HA:masterfrom
gdluck:fix/who4-dimension-22-guard-crash-forms

Conversation

@gdluck

@gdluck gdluck commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Requested by A

Summary

Before: since #136 every WHO 4 DIMENSION 22 frame is refused by every send path, in any spelling, the published split-unit forms *#4*3#Z#N*22## / *#4*3#Z#N*#22*MOD*SP*VEL*SWING## included. The evidence behind it was two F461 020011 crash frames of the same shape (*#4*2*22##, *#4*#0*22##) and the live F461 reboot, and the CHANGELOG / docstrings generalised that to "in any spelling".

After: the send paths refuse is_who4_dimension_22_crash_form(): every DIMENSION 22 spelling whose WHERE carries no actuator token, plus every spelling the oracle did not replay. The canonical split forms and the actuator form *#4*Z#N*22## (Z 0..99, N 1..9, request *22 or write *#22*...) are sent. is_who4_dimension_22() keeps its "every spelling" meaning for callers that want the detector.

Context and how it was found. A questioned whether all DIMENSION 22 forms crash the daemon or only the zone / central one, and asked for the oracle to settle it. New suite thermo-dim22 (OpenWebNet-HA/own-firmware-oracle#68, finding findings/thermo-dim22.md): 11 forms on the twelve emulated gateways, two byte-identical runs. On the nine gateways that crash at all (F450, F453, F453AV, F454, F459, F460, F461, MH200N, MyHomeServer1) the crash set is exactly the four forms without an actuator token (*#4*2*22##, *#4*2*#22*1*0215*2*0##, *#4*#0*22##, *#4*0*22##; bt_termo SIGSEGV where attributed). No form with an actuator token crashed anywhere: *#4*3#2#1*22## and its write go out on ten of twelve gateways (F461 included) as a D1 telegram to zone 2, actuator 1, dimension 22; *#4*2#1*22## and the touchscreen's *#4*45#1*#22*3**1*1## are nack, silent on all but MH202 (which forwards them). Emulation only: no DIMENSION 22 frame was sent to a physical gateway, and the live F461 supervisor's reboot is not modelled, so this PR narrows the refusal to the evidence and nothing more.

Follows up #136 and #148 (the "in any spelling ... Linux IP gateways" wording).

Changes

  • OWNd/message/heating.py: _WHO4_DIMENSION_22_REGEX unchanged (every spelling, atoi-tolerant, anchored per ## chunk); new _WHO4_DIMENSION_22_SENDABLE_REGEX = ^\*#4\*(?:3#)?(?:0|[1-9][0-9]?)#[1-9](?:\*22\Z|\*#22\*) and is_who4_dimension_22_crash_form(frame): a chunk the detector matches that the sendable pattern does not. Refused therefore: Z, 0, 0ZZ, #0, #Z, #0#C, Z#4#IF, Z#0, 0#0, two-digit or zero-padded N, a parameter after the request (*22*1, *22#1), a bare *#22, and any whitespace, +, leading-zero or trailing-newline spelling (\Z, not $, which matches before a final newline). Allowed: the canonical forms above. The oracle replayed Z 2 and 45 with N 1; the published ranges are allowed on the grammar, and the comment says so.
  • OWNd/connection/command_session.py, OWNd/transport/serial.py, OWNd/transport/base.py: the three send paths call the crash-form detector; DESTRUCTIVE_FRAME_REASON names the WHERE condition. Contract, reason="destructive_frame", logging and send_to_gateway's refusal before connect() unchanged.
  • OWNd/message/__init__.py: exports is_who4_dimension_22_crash_form.
  • tests/test_who4_dimension22_guard.py: DIMENSION_22_FRAMES is now the refused set (41 spellings, the four oracle crash forms and the untested ones); new SPLIT_UNIT_FRAMES (11 canonical forms) asserted detector True / crash form False, written unchanged by OWNCommandSession.send, send_to_gateway (connects) and AsyncSerialTransport.send; the parsed / built refusal fixtures use crash forms.
  • CHANGELOG.md: the [Unreleased] / Fixed bullet rewritten to what is refused and what is sent.

State Diagram

stateDiagram-v2
    direction TB
    state "send paths: OWNCommandSession.send / send_to_gateway, AsyncSerialTransport.send" as Guard {
        [*] --> Chunk
        Chunk --> NotDim22 : _WHO4_DIMENSION_22_REGEX no match\n*#4*22##, *#13**22##, *#2*#4*#11#001*22##
        Chunk --> Sendable : canonical *#4*(3#)?Z#N*22## or *#4*(3#)?Z#N*#22*...##\nZ 0..99, N 1..9
        Chunk --> Refused : every other DIMENSION 22 spelling\n*#4*2*22##, *#4*0*22##, *#4*#0*22##, *#4*2*#22*...##, *#4*#2*22##, *#4*2#0*22##, *#04*2#1*022##
        Refused --> [*] : OWNProtocolError reason destructive_frame, never written
        Sendable --> Validate : OWNMessage.parse / is_valid as any frame
        NotDim22 --> Validate
    }
    state "oracle thermo-dim22, 12 gateways x 2 runs" as Oracle {
        [*] --> NoToken
        NoToken --> Crash : *#4*2*22##, *#4*2*#22*1*0215*2*0##, *#4*#0*22##, *#4*0*22##\nbt_termo SIGSEGV on 9 of 12 (F461, MyHomeServer1 included)
        [*] --> Token
        Token --> Out : *#4*3#2#1*22##, *#4*3#2#1*#22*...## out on 10 of 12\n*#4*2#1*22##, *#4*45#1*#22*3**1*1## silent on 11, out on MH202
    }
Loading

Where are the traces

Subsystem Capture Plant Frames verified by this PR
WHO 4 DIMENSION 22, all forms own-firmware-oracle results/<P>/<V>/oracle/full/thermo-dim22.tsv (OpenWebNet-HA/own-firmware-oracle#68), emulated, two runs twelve emulated gateways the 11 forms of the suite; none sent to a physical gateway
Live F461 reboot on *#4*2*22## as recorded on #136 F461 2.0.11 the refused zone form; unchanged by this PR

What does the Oracle say

Frame crash out silent
*#4*2*22##, *#4*2*#22*1*0215*2*0##, *#4*#0*22##, *#4*0*22## F450, F453, F453AV, F454, F459, F460, F461, MH200N, MyHomeServer1 (9) none H4684, MH200, MH202
*#4*3#2#1*22##, *#4*3#2#1*#22*1*0215*2*0## none the 9 above plus MH202 (10) H4684, MH200
*#4*2#1*22##, *#4*2#1*#22*1*0215*2*0##, *#4*45#1*#22*3**1*1##, *#4*45#1*#22*4**1*1## none MH202 the other 11
*#4*45#1*#22*0***## none none all 12

tools/expected_divergences.yaml gains the zone write and the general read for F461 in the oracle PR. Not settled by the rows, and so still refused here: 0ZZ, #Z, #0#C, Z#4#IF, N 0, other payloads' effect on a real split unit.

Testing

  • pytest tests against this head: 2198 passed (-o asyncio_default_fixture_loop_scope=function locally, as CI sets since ci(pytest): set asyncio_default_fixture_loop_scope so pytest-asyncio 1.4.1 does not abort the suite #150).
  • ruff check OWNd tests clean; mypy OWNd clean.
  • Cross-check of the detector on 41 refused and 11 sendable spellings (including *#4*2#1*22\n##, *#4*2#1*22*1##, *#4*2#1*#22##, found by a second-opinion review and added to the refused set).
  • MyHOME keeps its own _WHO4_DIMENSION_22 regex in services.py / websocket.py, which still blocks every form from the service and the websocket; it does not call OWNd's detector, so the conformance suite is unaffected. Narrowing it is MyHOME's call once this lands.

Companion

🤖 Generated with Claude Code

…n in WHERE

The firmware oracle's thermo-dim22 suite (12 emulated gateways, two
byte-identical runs) crashes bt_termo only on the forms whose WHERE has no
actuator token: *michnovka#4*Z*22##, *michnovka#4*0*22##, *michnovka#4*#0*22## and the zone write.
The published split forms *michnovka#4*3#Z#N*22## / *michnovka#4*3#Z#N*OpenWebNet-HA#22*...## and the
actuator form *michnovka#4*Z#N*22## crashed none of the twelve gateways; ten forward
them to the bus as a telegram to zone Z, actuator N.

is_who4_dimension_22() keeps recognising every DIMENSION 22 spelling; the
send paths now refuse is_who4_dimension_22_crash_form(), which lets through
only the canonical actuator-addressed spellings the oracle replayed (Z 0..99,
N 1..9) and keeps every untested spelling refused.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants