Fix #7: derive action risk from UDS SIDs; fix response parsing
Untrusted profiles could bypass the confirmation and responses were mis-parsed: - effective_risk(action): risk is now DERIVED from the actual service IDs the steps send — any write/actuator/reset/transfer SID (2F/31/11/14/2E/27/34-37/…) forces 'danger'; unknown SID / non-default session / security block force 'caution'. A profile can only RAISE risk, never label a reflash 'safe'. GUI gates the confirmation (and the risk badge) on this derived value. - Response checks use CONTIGUOUS subsequence matching + a hard '7F <sid>' negative-response guard, so an NRC data byte (e.g. 0x7E) can't false-pass as a positive response; applied to session/security/step checks. - 0x78 (responsePending) is treated as in-progress, not terminal failure. - controller.run_action restores slow ELM timing for the run (0x78 window). - Tests: risk-cannot-be-downgraded, NRC false-positive rejected, pending handled. Closes #7 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:
+17
-1
@@ -159,7 +159,23 @@ class Controller:
|
||||
|
||||
def run_action(self, action):
|
||||
from obdcore.actions import run_action
|
||||
return self._oneoff(lambda: run_action(action, self.link), timeout=20.0)
|
||||
|
||||
def go():
|
||||
# actions/routines can take longer than the fast polling window and
|
||||
# may reply 0x78 (pending) — restore slow ELM timing for the run
|
||||
try:
|
||||
self.link.fast_timing(False)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
return run_action(action, self.link)
|
||||
finally:
|
||||
try:
|
||||
self.link.fast_timing(True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return self._oneoff(go, timeout=25.0)
|
||||
|
||||
# -- trip / performance (fed from the live store each GUI tick) --
|
||||
def update_trip(self):
|
||||
|
||||
+11
-4
@@ -506,13 +506,15 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
"<b>Caution:</b> these send commands to the vehicle. Read each warning."))
|
||||
scroll = QtWidgets.QScrollArea(); scroll.setWidgetResizable(True)
|
||||
inner = QtWidgets.QWidget(); il = QtWidgets.QVBoxLayout(inner)
|
||||
from obdcore.actions import effective_risk
|
||||
for a in acts:
|
||||
row = QtWidgets.QFrame()
|
||||
row.setStyleSheet("QFrame{border:1px solid #333;border-radius:6px;}")
|
||||
rl = QtWidgets.QHBoxLayout(row)
|
||||
er = effective_risk(a)
|
||||
txt = QtWidgets.QLabel(
|
||||
f"<b>{a.name}</b> "
|
||||
f"<span style='color:{self._RISK_COLOR.get(a.risk,'#999')}'>[{a.risk}]</span>"
|
||||
f"<span style='color:{self._RISK_COLOR.get(er,'#999')}'>[{er}]</span>"
|
||||
f"<br><span style='color:#999'>{a.description}</span>")
|
||||
txt.setWordWrap(True)
|
||||
rl.addWidget(txt, 1)
|
||||
@@ -527,11 +529,16 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
dlg.exec()
|
||||
|
||||
def _run_action(self, action):
|
||||
if action.risk in ("caution", "danger"):
|
||||
msg = (action.warning or "This sends a command to the vehicle.") + \
|
||||
from obdcore.actions import effective_risk
|
||||
risk = effective_risk(action) # derived from the actual UDS SIDs
|
||||
if risk != "safe":
|
||||
note = ("" if risk == action.risk else
|
||||
f"\n\n(The profile labels this \"{action.risk}\", but its commands are "
|
||||
f"{risk}-level — confirming anyway.)")
|
||||
msg = (action.warning or "This sends a command to the vehicle.") + note + \
|
||||
"\n\nProceed?"
|
||||
btn = QtWidgets.QMessageBox.warning(
|
||||
self, f"Run: {action.name}", msg,
|
||||
self, f"Run [{risk}]: {action.name}", msg,
|
||||
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
|
||||
QtWidgets.QMessageBox.No)
|
||||
if btn != QtWidgets.QMessageBox.Yes:
|
||||
|
||||
Reference in New Issue
Block a user