Files
StarPilot/openpilot/starpilot/galaxy/tests/test_pairing_migration.py
T
firestar5683 a82935f326 User interfaces and Android Auto
Integrate onroad views, configurable widgets, local settings, steering-profile contracts, Bluetooth and Android Auto with their device and runtime services.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Daniel Koepping <elkoled@gmail.com>
Co-authored-by: Danny <dannyirwin31@yahoo.com>
Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: Shane Smiskol <shane@smiskol.com>
Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
Co-authored-by: stef <19478336+stefpi@users.noreply.github.com>
Co-authored-by: whoisdomi <dcantu83@gmail.com>

Co-authored-by: Intelli <contact@intelli.software>
Co-authored-by: pharmacomaniac <blittle65@gmail.com>

Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

151 lines
6.8 KiB
Python

import hashlib
import os
from pathlib import Path
import tempfile
import threading
import unittest
from unittest.mock import patch
from openpilot.starpilot.galaxy.remote import RemotePairing, default_remote_pairing, gateway_cookie_valid
class TestPairingMigration(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory()
self.addCleanup(temporary.cleanup)
self.root = Path(temporary.name)
self.legacy = self.root / 'legacy'
self.legacy.mkdir()
self.values = {'glxyslug': 'ExistingGalaxy01', 'glxyauth': hashlib.sha256(b'old-password').hexdigest(),
'glxysession': 'a' * 64}
for key, value in self.values.items():
(self.legacy / key).write_text(value + '\n')
self.pairing = RemotePairing(self.root / 'new')
def test_default_startup_keeps_link_password_and_session_without_password_entry(self):
original = {path.name: path.read_bytes() for path in self.legacy.iterdir()}
with patch('openpilot.starpilot.storage.galaxy_storage_root', return_value=self.pairing.root), \
patch('openpilot.starpilot.galaxy.access.legacy_galaxy_root', return_value=self.legacy):
pairing = default_remote_pairing()
record = pairing.read()
assert record is not None
self.assertEqual(record, {'version': 1, 'slug': self.values['glxyslug'], 'authHash': self.values['glxyauth'],
'session': self.values['glxysession']})
self.assertEqual(pairing.url(record['slug']), 'https://galaxy.firestar.link/ExistingGalaxy01')
self.assertTrue(gateway_cookie_valid(record['slug'] + ':' + record['session'], record))
saved = (pairing.root / pairing.FILE).read_bytes()
self.assertEqual(default_remote_pairing().read(), record)
self.assertEqual((pairing.root / pairing.FILE).read_bytes(), saved)
self.assertEqual((pairing.root / pairing.FILE).stat().st_mode & 0o777, 0o600)
self.assertEqual({path.name: path.read_bytes() for path in self.legacy.iterdir()}, original)
self.assertFalse((self.pairing.root / 'access-v1.json').exists())
def test_unpair_survives_restart_and_manual_repair_rotates_identity(self):
self.assertTrue(self.pairing.migrate_legacy(self.legacy))
old = self.pairing.read()
assert old is not None
self.assertTrue(self.pairing.unpair())
restarted = RemotePairing(self.pairing.root)
self.assertFalse(restarted.migrate_legacy(self.legacy))
self.assertIsNone(restarted.read())
assert restarted.pair(old['authHash'], legacy_root=self.legacy) is not None
new = restarted.read()
assert new is not None
self.assertNotEqual(new['slug'], old['slug'])
self.assertNotEqual(new['session'], old['session'])
self.assertFalse(gateway_cookie_valid(old['slug'] + ':' + old['session'], new))
def test_existing_and_malformed_destination_are_never_replaced(self):
assert self.pairing.pair('b' * 64) is not None
path = self.pairing.root / self.pairing.FILE
before = path.read_bytes()
self.assertFalse(self.pairing.migrate_legacy(self.legacy))
self.assertEqual(path.read_bytes(), before)
path.write_text('incomplete')
self.assertFalse(self.pairing.migrate_legacy(self.legacy))
self.assertIsNone(self.pairing.pair('c' * 64))
self.assertEqual(path.read_text(), 'incomplete')
def test_partial_or_invalid_legacy_does_not_create_pairing(self):
for name in self.values:
path = self.legacy / name
original = path.read_bytes()
for raw in (None, b'bad', b'x' * 129, b'\xff'):
with self.subTest(name=name, raw=raw):
path.unlink(missing_ok=True)
if raw is not None:
path.write_bytes(raw)
self.assertFalse(self.pairing.migrate_legacy(self.legacy))
self.assertIsNone(self.pairing.read())
path.write_bytes(original)
def test_previous_local_setup_requires_explicit_pairing(self):
from openpilot.starpilot.galaxy.access import GalaxyAccessOwner
owner = GalaxyAccessOwner(self.pairing.root)
self.assertTrue(owner.configure('new-password', lambda: True))
self.assertFalse(self.pairing.migrate_legacy(self.legacy))
self.assertIsNone(self.pairing.read())
self.assertTrue(owner.verify('new-password'))
assert self.pairing.pair(self.values['glxyauth'], legacy_root=self.legacy) is not None
def test_unsafe_legacy_and_destination_paths_are_not_followed(self):
path = self.legacy / 'glxyslug'
original = path.read_bytes()
outside = self.root / 'outside'
outside.write_bytes(original)
path.unlink()
path.symlink_to(outside)
self.assertFalse(self.pairing.migrate_legacy(self.legacy))
path.unlink()
os.mkfifo(path)
self.assertFalse(self.pairing.migrate_legacy(self.legacy))
path.unlink()
path.write_bytes(original)
outside_dir = self.root / 'outside-dir'
outside_dir.mkdir(mode=0o700)
symlink = self.root / 'linked'
symlink.symlink_to(outside_dir, target_is_directory=True)
self.assertFalse(RemotePairing(symlink).migrate_legacy(self.legacy))
self.assertEqual(list(outside_dir.iterdir()), [])
def test_storage_failure_leaves_original_pairing_for_retry(self):
with patch('openpilot.starpilot.galaxy.remote.os.link', side_effect=OSError('disk full')):
self.assertFalse(self.pairing.migrate_legacy(self.legacy))
self.assertIsNone(self.pairing.read())
self.assertTrue(self.pairing.migrate_legacy(self.legacy))
def test_concurrent_startups_do_not_resurrect_an_explicit_unpair(self):
self.assertTrue(self.pairing.migrate_legacy(self.legacy))
start = threading.Barrier(3)
results = []
def import_again():
start.wait()
results.append(RemotePairing(self.pairing.root).migrate_legacy(self.legacy))
def unpair():
start.wait()
results.append(RemotePairing(self.pairing.root).unpair())
workers = [threading.Thread(target=import_again), threading.Thread(target=unpair)]
for worker in workers:
worker.start()
start.wait()
for worker in workers:
worker.join(2)
self.assertFalse(worker.is_alive())
self.assertCountEqual(results, [False, True])
self.assertIsNone(self.pairing.read())
self.assertFalse(self.pairing.migrate_legacy(self.legacy))
def test_named_namespace_cannot_import_normal_pairing(self):
from openpilot.starpilot.galaxy.access import legacy_galaxy_root
for desktop in (False, True):
with self.subTest(desktop=desktop), patch.dict(os.environ, {'OPENPILOT_PREFIX': 'isolated-desk'}), \
patch('openpilot.common.hardware.PC', desktop), \
patch('openpilot.starpilot.storage.galaxy_storage_root', return_value=self.pairing.root), \
patch.object(RemotePairing, '_legacy_record', side_effect=AssertionError('legacy access')):
self.assertIsNone(legacy_galaxy_root())
self.assertIsNone(default_remote_pairing().read())
if __name__ == '__main__':
unittest.main()