Reuse exact native qualification across all vehicle makes

This commit is contained in:
firestar5683
2026-10-23 15:42:44 -05:00
parent f9bf710396
commit 37dc1533ef
5 changed files with 234 additions and 8 deletions
@@ -0,0 +1,220 @@
"""All registered makes share exact-packet structural reuse and live vetoes."""
from dataclasses import replace
from unittest.mock import patch
import pytest
from opendbc.car import gen_empty_fingerprint
from opendbc.car.car_helpers import interfaces
from openpilot.cereal import messaging
from openpilot.starpilot.aol import runtime, transport_pause, vehicle
from openpilot.starpilot.aol.tests.test_native_observation_admission import receive
from openpilot.starpilot.aol.tests.test_panda_inventory import NOW, bound_state, sm_for
from openpilot.starpilot.aol.tests.test_runtime import car_state
BRANDS = ('honda', 'hyundai', 'gm', 'ford', 'mazda', 'tesla', 'toyota', 'subaru', 'nissan', 'chrysler', 'rivian', 'volkswagen')
def profile(brand):
if brand in ('honda', 'hyundai', 'gm', 'ford', 'mazda', 'tesla', 'toyota'):
from openpilot.starpilot.aol.tests.test_vehicle_policy_cache import profile as factory
cp = factory(brand)
elif brand == 'chrysler':
from opendbc.car.chrysler.aol_contract import CHRYSLER_AOL_CARS
from openpilot.starpilot.aol.tests.test_chrysler import cp_for
cp = cp_for(sorted(CHRYSLER_AOL_CARS)[0])
elif brand == 'volkswagen':
from opendbc.car.volkswagen.values import CAR
from openpilot.starpilot.aol.tests.test_mqb_topology_contract import params
cp = params(CAR.VOLKSWAGEN_GOLF_MK7, False, False, 4, False)
else:
fp = gen_empty_fingerprint()
if brand == 'subaru':
from opendbc.car.subaru.values import CAR
identity = CAR.SUBARU_OUTBACK_2023
elif brand == 'nissan':
from opendbc.car.nissan.values import CAR
identity = CAR.NISSAN_LEAF
else:
from opendbc.car.rivian.values import CAR
identity = CAR.RIVIAN_R1
fp[0][0x321] = 7
cp = interfaces[identity].get_params(identity, fp, [], False, False, False)
policy = vehicle.policy_for(cp)
assert policy.intent_supported, brand
config = (vehicle.hyundai.aol_safety_config(cp) if brand == 'hyundai' else
vehicle.ford.aol_safety_config(cp) if brand == 'ford' else
vehicle.volkswagen.aol_safety_config(cp) if brand == 'volkswagen' else cp.safetyConfigs[-1])
assert config is not None
config.safetyParam |= policy.safety_param_addition
cp.alternativeExperience |= policy.alternative_experience_addition
assert vehicle.native_matches_cp(cp, int(config.safetyModel.raw), int(config.safetyParam)), brand
return cp
@pytest.fixture(params=BRANDS)
def cp(request):
return profile(request.param)
def full_oracle(sm, cp, *, now=NOW, session=None):
actual = runtime.current_native(sm, cp, now_ns=now, axis_session_id=session)
with patch.object(runtime, 'native_admission_key', return_value=None):
expected = runtime.current_native(sm, cp, now_ns=now, axis_session_id=session)
assert actual == expected
return actual
def test_matrix_covers_every_registered_make():
assert set(BRANDS) == set(vehicle._PORTS)
def test_one_validation_per_original_packet_and_owner_isolation(cp):
before = cp.to_dict()
state = bound_state(cp)
sm = sm_for(cp, state)
other = sm_for(cp, state)
with patch.object(runtime, 'native_matches_cp', wraps=runtime.native_matches_cp) as check:
for tick in range(10):
assert runtime.current_native(sm, cp, now_ns=NOW + tick * 1_000_000) == state
assert check.call_count == 1
assert runtime.current_native(other, cp, now_ns=NOW) == state
assert check.call_count == 2
updated = bound_state(cp, now=NOW + 10_000_000)
receive(sm, updated)
assert runtime.current_native(sm, cp, now_ns=NOW + 10_000_000) == updated
assert check.call_count == 3
assert cp.to_dict() == before
@pytest.mark.parametrize('field', ('brand', 'carFingerprint', 'flags', 'passive', 'notCar', 'dashcamOnly',
'pcmCruise', 'openpilotLongitudinalControl', 'alternativeExperience',
'safetyParam', 'safetyModel', 'safetyConfigs', 'carVin'))
def test_hot_configuration_mutations_match_full_validation(cp, field):
state = bound_state(cp)
sm = sm_for(cp, state)
assert runtime.current_native(sm, cp, now_ns=NOW) == state
key = vehicle.native_admission_key(cp)
if field == 'safetyParam':
cp.safetyConfigs[-1].safetyParam ^= 1
elif field == 'safetyModel':
cp.safetyConfigs[-1].safetyModel = 'noOutput'
elif field == 'safetyConfigs':
cp.safetyConfigs = []
elif field in ('passive', 'notCar', 'dashcamOnly', 'pcmCruise', 'openpilotLongitudinalControl'):
setattr(cp, field, not getattr(cp, field))
else:
setattr(cp, field, {'brand': 'unregistered', 'carFingerprint': 'unregistered', 'flags': int(cp.flags) ^ 1,
'alternativeExperience': int(cp.alternativeExperience) ^ 1, 'carVin': 'MUTATED'}[field])
assert vehicle.native_admission_key(cp) != key
with patch.object(runtime, 'native_matches_cp', wraps=runtime.native_matches_cp) as check:
full_oracle(sm, cp)
assert check.call_count == 2
@pytest.mark.parametrize('change', ('rx', 'heartbeat', 'fault', 'inventory', 'serial', 'lateral', 'longitudinal',
'session', 'expired', 'future', 'invalid', 'not_alive', 'not_seen'))
def test_every_live_change_matches_full_validation(cp, change):
state = bound_state(cp)
sm = sm_for(cp, state)
assert runtime.current_native(sm, cp, now_ns=NOW) == state
now, session = NOW, None
if change in ('rx', 'heartbeat', 'fault', 'inventory'):
field, value = {'rx': ('safetyRxChecksInvalid', True), 'heartbeat': ('heartbeatLost', True),
'fault': ('faults', 1), 'inventory': ('safetyParam', state.pandaInventory[-1].safetyParam ^ 1)}[change]
slots = (*state.pandaInventory[:-1], replace(state.pandaInventory[-1], **{field: value}))
receive(sm, replace(state, pandaInventory=slots))
elif change in ('serial', 'lateral', 'longitudinal'):
field, value = {'serial': ('pandaSerial', 'other'), 'lateral': ('lateralAllowed', False),
'longitudinal': ('longitudinalAllowed', False)}[change]
receive(sm, replace(state, **{field: value}))
elif change == 'session':
session = 'other'
elif change == 'expired':
now = state.validUntilMonoTime + 1
elif change == 'future':
now -= 1
else:
getattr(sm, {'invalid': 'valid', 'not_alive': 'alive', 'not_seen': 'seen'}[change])['aolSafetyWire'] = False
result = full_oracle(sm, cp, now=now, session=session)
if change in ('lateral', 'longitudinal'):
assert result is not None and not getattr(result, 'lateralAllowed' if change == 'lateral' else 'longitudinalAllowed')
else:
assert result is None
def test_latest_panda_veto_is_checked_after_structural_hit(cp):
state = bound_state(cp)
sm = sm_for(cp, state)
assert transport_pause.healthy_transport(sm, cp, car_state(), state.axisSessionId, NOW)
message = messaging.new_message('pandaStates', len(cp.safetyConfigs), valid=True, logMonoTime=state.sourcePandaStatesMonoTime)
for index, panda in enumerate(sm['pandaStates']):
message.pandaStates[index] = panda
message.pandaStates[-1].heartbeatLost = True
sm.update_msgs(NOW / 1e9, [message.as_reader()])
assert runtime.current_native(sm, cp, now_ns=NOW) == state
assert not transport_pause.healthy_transport(sm, cp, car_state(), state.axisSessionId, NOW)
def test_same_builder_mutation_during_miss_cannot_bind_old_key(cp):
state = bound_state(cp)
sm = sm_for(cp, state)
original = runtime.native_inventory_matches_cp
def mutate(native, params):
result = original(native, params)
params.carVin = 'CHANGED-DURING-MISS'
return result
with patch.object(runtime, 'native_inventory_matches_cp', side_effect=mutate):
assert runtime.current_native(sm, cp, now_ns=NOW) == state
assert sm._aol_native_admission is None
full_oracle(sm, cp)
def test_oversize_cp_bypasses_reuse_without_changing_admission(cp):
cp.carVin = 'X' * (vehicle._POLICY_CACHE_MAX_BYTES + 1)
state = bound_state(cp)
sm = sm_for(cp, state)
assert vehicle.native_admission_key(cp) is None
with patch.object(runtime, 'native_matches_cp', wraps=runtime.native_matches_cp) as check:
assert runtime.current_native(sm, cp, now_ns=NOW) == state
assert runtime.current_native(sm, cp, now_ns=NOW) == state
assert check.call_count == 2
def test_snapshot_failure_falls_back_to_full_validation(cp):
state = bound_state(cp)
sm = sm_for(cp, state)
assert runtime.current_native(sm, cp, now_ns=NOW) == state
with patch.object(vehicle, '_car_params_bytes', return_value=None), \
patch.object(runtime, 'native_matches_cp', wraps=runtime.native_matches_cp) as check:
assert runtime.current_native(sm, cp, now_ns=NOW) == state
assert runtime.current_native(sm, cp, now_ns=NOW) == state
assert check.call_count == 2
assert sm._aol_native_admission is None
@pytest.mark.parametrize('legacy', (False, True))
def test_legacy_or_slotted_owner_keeps_full_validation(cp, legacy):
state = bound_state(cp)
if legacy:
state = replace(state, sourcePandaStatesMonoTime=0, pandaInventory=())
original = sm_for(cp, state)
class SlottedOwner:
__slots__ = ('valid', 'alive', 'seen', 'logMonoTime')
def __init__(self):
for name in self.__slots__:
setattr(self, name, getattr(original, name))
def __getitem__(self, name):
return original[name]
sm = original if legacy else SlottedOwner()
expected = state if not legacy or len(cp.safetyConfigs) == 1 else None
with patch.object(runtime, 'native_matches_cp', wraps=runtime.native_matches_cp) as check:
assert runtime.current_native(sm, cp, now_ns=NOW) == expected
assert runtime.current_native(sm, cp, now_ns=NOW) == expected
assert check.call_count == 2
@@ -211,8 +211,8 @@ def test_other_admitted_profiles_use_only_scoped_reuse(profile_name):
with mock.patch.object(runtime, 'native_matches_cp', wraps=runtime.native_matches_cp) as profile:
assert current(sm, cp) == state
assert current(sm, cp) == state
assert profile.call_count == (1 if profile_name == 'ev6_two_pandas' else 2)
assert (sm._aol_native_admission is not None) == (profile_name == 'ev6_two_pandas')
assert profile.call_count == 1
assert sm._aol_native_admission is not None
def test_rejected_observation_then_valid_recovery_cannot_reuse_a_failed_verdict():
+10 -5
View File
@@ -104,12 +104,17 @@ def native_matches_cp(CP, model: int, param: int) -> bool:
def native_admission_key(CP):
"""Bind structural reuse to the complete mutable vehicle configuration."""
try:
if CP.brand == 'hyundai' and CP.carFingerprint in (hyundai.HyundaiCAR.KIA_EV6, hyundai.HyundaiCAR.KIA_EV9):
# Cache only structural admission of one exact wire packet. Full CP bytes
# invalidate every mutable field; unknown types keep full validation.
if CP.brand == 'hyundai':
existing = hyundai.native_admission_key(CP)
if existing is not None:
return existing
if CP.brand in _PORTS:
# Reuse only one exact packet's structural admission, for every owner.
# Full CP bytes invalidate every mutable field; live gates stay per call.
key = _car_params_bytes(CP)
return ('hkg-full-cp', key) if key is not None else None
return hyundai.native_admission_key(CP) if CP.brand == 'hyundai' else None
if key is not None:
return ('vehicle-full-cp', key)
return None
except (AttributeError, TypeError, ValueError, OverflowError, IndexError):
return None
+1 -1
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "9ac218d1b06065b7f3c3d769ab507101e89aeedd5d90b68de21f463b476096f3", "schema_version": 1}
{"protected_inventory_sha256": "4e7069cd3da0a1f7965dac62ce06f6bfc8711c9ca7ae71c48488a1b1ad2be14d", "schema_version": 1}
+1
View File
@@ -24,6 +24,7 @@ from tools.ci import lifecycle_shards, vehicle_pytest
# Representative production callers plus their source/native refusal cases.
# The emitted selected_test_ids record exact parametrized configurations.
AOL_LIFECYCLE_TARGETS = (
'openpilot/starpilot/aol/tests/test_crossmake_native_admission.py',
'openpilot/starpilot/aol/tests/test_hkg_source_policy_cache.py',
'openpilot/starpilot/aol/tests/test_hkg_native_admission.py',
'openpilot/starpilot/aol/tests/test_native_observation_admission.py',