#2 (framework): bi-directional / service-function engine
Profile-defined UDS action sequences, run safely -- the framework for #2 (real per-vehicle actuator tests/resets are follow-on, added as verified profile data). - obdcore/actions.py: Action model + run_action() executing session (Mode 10) -> security (Mode 27 seed->key) -> command steps (2F/31/11/3E/... any hex) with positive/negative response checks. Security KEY algorithms are per-vehicle secrets and NOT bundled -- only trivial transforms (xor-ff/invert/add-ff) known; an action naming an unknown algorithm is BLOCKED (fails safe). Never synthesizes bytes -- runs only what the profile defines. validate_action() rejects malformed hex at load. - profile.py: load/save an actions[] block; ElmLink/MockLink read_raw(hex). - GUI: Diagnostics -> Service & Bi-directional dialog -- lists the profile's actions with risk badges; caution/danger gated behind a warning confirmation. - generic-obd2: two safe STANDARD actions (Tester-Present ping; ECU-Reset, caution + engine-off warning). PROFILE_SPEC.md documents the actions schema + safety rules. - tests/test_actions.py: runner, session+reset, security handshake, unknown-algo block, hex validation, profile load. All 5 suites pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016yT89n4zR4qbrySoSiEyZs
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
"""Bi-directional action framework tests (against MockLink, no hardware)."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from obdcore import load_default, load_profile, profiles_dir
|
||||
from obdcore.actions import Action, ActionStep, run_action, validate_action
|
||||
from obdcore.mock import MockLink
|
||||
import time
|
||||
|
||||
|
||||
def _mock():
|
||||
return MockLink(clock=time.time)
|
||||
|
||||
|
||||
def test_simple_action():
|
||||
a = Action("PING", "Tester Present", steps=[ActionStep("3E00")])
|
||||
r = run_action(a, _mock())
|
||||
assert r["ok"], r
|
||||
print(" simple action (3E00): OK")
|
||||
|
||||
|
||||
def test_session_and_reset():
|
||||
a = Action("RESET", "ECU Reset", session="03", steps=[ActionStep("1101")])
|
||||
r = run_action(a, _mock())
|
||||
assert r["ok"], r
|
||||
print(" session + reset: OK")
|
||||
|
||||
|
||||
def test_security_known_algo():
|
||||
a = Action("LOCKED", "Secured routine", security={"level": "01", "algorithm": "xor-ff"},
|
||||
steps=[ActionStep("31010203")]) # start routine
|
||||
r = run_action(a, _mock())
|
||||
assert r["ok"], r
|
||||
print(" security handshake (xor-ff seed->key): OK")
|
||||
|
||||
|
||||
def test_security_unknown_algo_blocked():
|
||||
a = Action("SECRET", "Vendor routine", security={"level": "01", "algorithm": "ford-2005-secret"},
|
||||
steps=[ActionStep("31010203")])
|
||||
r = run_action(a, _mock())
|
||||
assert not r["ok"] and "not available" in r["message"], r
|
||||
print(" unknown security algorithm blocked (fails safe): OK")
|
||||
|
||||
|
||||
def test_validate_rejects_bad_hex():
|
||||
bad = Action("BAD", "bad", steps=[ActionStep("ZZ")])
|
||||
try:
|
||||
validate_action(bad)
|
||||
raise AssertionError("should reject non-hex send")
|
||||
except ValueError:
|
||||
pass
|
||||
print(" malformed hex rejected at load: OK")
|
||||
|
||||
|
||||
def test_profile_actions_load():
|
||||
prof = load_profile(os.path.join(profiles_dir(), "generic-obd2.json"))
|
||||
keys = {a.key for a in (prof.actions or [])}
|
||||
assert "TESTER_PRESENT" in keys and "ECU_RESET" in keys, keys
|
||||
# the reset is risk-gated
|
||||
reset = next(a for a in prof.actions if a.key == "ECU_RESET")
|
||||
assert reset.risk == "caution" and reset.warning
|
||||
print(f" generic profile loads {len(prof.actions)} actions (risk-tagged): OK")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
for fn in [test_simple_action, test_session_and_reset, test_security_known_algo,
|
||||
test_security_unknown_algo_blocked, test_validate_rejects_bad_hex,
|
||||
test_profile_actions_load]:
|
||||
fn()
|
||||
print("\nALL ACTION TESTS PASS")
|
||||
Reference in New Issue
Block a user