Files
StarPilot/openpilot/starpilot/sentry_mode/preferences.py
T
firestar5683 e52e1a6a86 Sentry
Implement Sentry scanning, notification state and worker cancellation through explicit policies and preferences.
2026-10-14 19:56:17 -05:00

83 lines
2.9 KiB
Python

"""Strict optional saved Sentry motion policy; no legacy-key adoption."""
from dataclasses import dataclass, field
import json
import math
from typing import Any
from openpilot.starpilot.saved_source import read_saved
from openpilot.starpilot.sentry_mode.policy import Settings
KEY = "SentryMotionPreferences"
MAX_BYTES = 512
@dataclass(frozen=True)
class Preferences:
enabled: bool = False
settings: Settings = field(default_factory=Settings)
@dataclass(frozen=True)
class SavedPreferences:
raw: bytes | None
readable: bool
valid: bool
preferences: Preferences = field(default_factory=Preferences)
def _unique(pairs: list[tuple[str, object]]) -> dict[str, object]:
result: dict[str, object] = {}
for key, value in pairs:
if key in result:
raise ValueError("duplicate Sentry preference")
result[key] = value
return result
def decode(raw: bytes) -> Preferences | None:
if len(raw) > MAX_BYTES:
return None
try:
data = json.loads(raw, object_pairs_hook=_unique,
parse_constant=lambda _: (_ for _ in ()).throw(ValueError("nonfinite")))
if not isinstance(data, dict) or set(data) != {"version", "enabled", "sensitivity", "warningTimeSeconds"}:
return None
if type(data["version"]) is not int or data["version"] != 1 or type(data["enabled"]) is not bool:
return None
sensitivity, warning = data["sensitivity"], data["warningTimeSeconds"]
if (type(sensitivity) not in (float, int) or type(warning) not in (float, int) or
not math.isfinite(sensitivity) or not math.isfinite(warning)):
return None
return Preferences(data["enabled"], Settings(float(sensitivity), float(warning)))
except (UnicodeDecodeError, ValueError, TypeError, OverflowError, RecursionError):
return None
def encode(preferences: Preferences) -> bytes:
if not isinstance(preferences, Preferences) or type(preferences.enabled) is not bool:
raise ValueError("invalid Sentry preferences")
settings = Settings(preferences.settings.sensitivity, preferences.settings.warning_time_seconds)
data = {"version": 1, "enabled": preferences.enabled,
"sensitivity": settings.sensitivity, "warningTimeSeconds": settings.warning_time_seconds}
raw = json.dumps(data, sort_keys=True, separators=(",", ":"), allow_nan=False).encode()
if len(raw) > MAX_BYTES:
raise ValueError("oversized Sentry preferences")
return raw
def read_preferences(params: Any) -> SavedPreferences:
raw, readable = read_saved(params, KEY, MAX_BYTES)
if not readable:
return SavedPreferences(raw, False, False)
if raw is None:
return SavedPreferences(None, True, True)
preferences = decode(raw)
return SavedPreferences(raw, True, preferences is not None, preferences or Preferences())
def enabled(params: Any) -> bool:
saved = read_preferences(params)
return saved.readable and saved.valid and saved.preferences.enabled