import importlib.util import json import os import re import zipfile from pathlib import Path import pytest def _load_patch_module(): path = Path(__file__).resolve().parent / "patch_system_reset_image.py" spec = importlib.util.spec_from_file_location("patch_system_reset_image_under_test", path) assert spec is not None and spec.loader is not None module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module patch_image = _load_patch_module() def test_only_version_runtime_packages_and_factory_install_payloads_are_mutable(): assert patch_image.ALLOWED_IMAGE_MUTATIONS == { patch_image.VERSION_PATH_IN_IMAGE, *patch_image.STAR_PILOT_DEPENDENCY_PATHS, *patch_image.LEGACY_RUNTIME_LIBRARY_PATHS, patch_image.SETUP_PATH_IN_IMAGE, patch_image.INSTALLER_PATH_IN_IMAGE, } assert set(patch_image.C3_DEPENDENCY_PATHS) < set(patch_image.STAR_PILOT_DEPENDENCY_PATHS) assert len(patch_image.LEGACY_RUNTIME_LIBRARY_PATHS) == 10 assert len(patch_image.LEGACY_RUNTIME_LIBRARY_PATHS) == len(set(patch_image.LEGACY_RUNTIME_LIBRARY_PATHS)) def test_upstream_profile_covers_factory_reset_and_runtime_paths(): assert patch_image.UPSTREAM_VERSION == "19.6" assert patch_image.UPSTREAM_RAW_SHA256 == "5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3" assert set(patch_image.UPSTREAM_REQUIRED_VENV_PATHS) == {"capnp", "numpy", "Crypto", "tqdm", "raylib"} assert set(patch_image.REQUIRED_VENV_PATHS) == { "crcmod", "serial", "kaitaistruct", "cv2", "mapbox_earcut", "jsonrpc", "xattr", "onnx", "aiohttp", "pyaudio", "capnp", "numpy", "Crypto", "tqdm", "raylib", } assert patch_image.CANDIDATE_SITE_PACKAGES_COUNT == ( patch_image.UPSTREAM_SITE_PACKAGES_COUNT + len(patch_image.STAR_PILOT_DEPENDENCY_PATHS) ) assert len(patch_image.STAR_PILOT_DEPENDENCY_PATHS) == len(set(patch_image.STAR_PILOT_DEPENDENCY_PATHS)) assert set(patch_image.PROTECTED_PAYLOAD_HASHES) >= { "/etc/NetworkManager/NetworkManager.conf", "/lib/systemd/system/NetworkManager.service", "/usr/comma/updater", "/usr/comma/reset", "/usr/comma/comma.sh", "/usr/comma/magic.py", } assert set(patch_image.UPSTREAM_FACTORY_INSTALL_HASHES) == {"/usr/comma/installer", "/usr/comma/setup"} assert set(patch_image.PROTECTED_PAYLOAD_HASHES).isdisjoint(patch_image.UPSTREAM_FACTORY_INSTALL_HASHES) @pytest.mark.parametrize("version", ["19.6.1", "19.6.5", "19.6.99"]) def test_target_version_accepts_starpilot_revision(version): assert patch_image.validate_target_version(version) == version @pytest.mark.parametrize("version", ["19.6", "19.6.0", "19.7.1", "20.6.1", "latest"]) def test_target_version_rejects_non_revision(version): with pytest.raises(RuntimeError): patch_image.validate_target_version(version) def test_write_version_fails_closed_if_allowlist_changes(tmp_path, monkeypatch): monkeypatch.setattr(patch_image, "ALLOWED_IMAGE_MUTATIONS", frozenset({"/VERSION", "/usr/comma/setup"})) with pytest.raises(RuntimeError, match="allowlist"): patch_image.write_version("debugfs", tmp_path / "system.img", tmp_path / "VERSION") def test_add_path_to_image_preserves_runtime_library_symlink(tmp_path, monkeypatch): target = tmp_path / "libavformat.so.58.29.100" target.write_bytes(b"ELF") source = tmp_path / "libavformat.so.58" source.symlink_to(target.name) calls = [] monkeypatch.setattr(patch_image, "image_path_exists", lambda *_args: False) monkeypatch.setattr(patch_image, "ensure_image_directory", lambda *_args: None) def fake_run_debugfs(_debugfs, _image, request, *, write=False): calls.append((request, write)) if request.startswith("stat "): return 'Inode: 1 Type: symlink\nFast link dest: "libavformat.so.58.29.100"' return "" monkeypatch.setattr(patch_image, "run_debugfs", fake_run_debugfs) patch_image.add_path_to_image("debugfs", tmp_path / "system.img", source, "/usr/local/lib/libavformat.so.58") assert ("symlink /usr/local/lib/libavformat.so.58 libavformat.so.58.29.100", True) in calls assert not any(request.startswith("write ") for request, _write in calls) def _setup_source(member: str) -> str: connectivity = ( 'request = urllib.request.Request(OPENPILOT_URL, method="HEAD")' if member.endswith("mici_setup.py") else "urllib.request.urlopen(OPENPILOT_URL, timeout=2.0)" ) labels = ( 'LargerSlider("slide to install\\nopenpilot", use_openpilot_callback)\n' 'BigPillButton("install openpilot", green=True)\n' 'set_text("install openpilot" if not custom_software else "choose software")' if member.endswith("mici_setup.py") else 'ButtonRadio("openpilot", self.checkmark)' ) if member.endswith("mici_setup.py"): not_elf = 'self._download_failed_reason = "No custom software found at this URL: " + self.download_url.replace("https://", "", 1)' http_error = 'self._download_failed_reason = "http"' generic_error = 'self._download_failed_reason = "Invalid URL: " + self.download_url.replace("https://", "", 1)' else: not_elf = 'self.download_failed(self.download_url, "No custom software found at this URL.")' http_error = 'self.download_failed(self.download_url, "http")' generic_error = ( 'error_msg = "Ensure the entered URL is valid, and the device\'s internet connection is good."\n' ' self.download_failed(self.download_url, error_msg)' ) return f'''USER_AGENT = f"AGNOSSetup-{{HARDWARE.get_os_version()}}" OPENPILOT_URL = "https://openpilot.comma.ai" {connectivity} {labels} def download(self, url: str): # autocomplete incomplete URLs if re.match("^([^/.]+)/([^/]+)$", url): url = f"https://installer.comma.ai/{{url}}" parsed = urlparse(url, scheme='https') self.download_url = (urlparse(f"https://{{url}}") if not parsed.netloc else parsed).geturl() try: import tempfile headers = {{"User-Agent": "test"}} req = urllib.request.Request(self.download_url, headers=headers) with open(tmpfile, 'wb') as f, urllib.request.urlopen(req, timeout=30) as response: total_size = int(response.headers.get('content-length', 0)) is_elf = True if not is_elf: {not_elf} with open(INSTALLER_URL_PATH, "w") as f: f.write(self.download_url) except urllib.error.HTTPError as e: {http_error} except Exception: {generic_error} ''' def test_patch_setup_zipapp_preserves_prefix_and_custom_flow(tmp_path): source = tmp_path / "setup" source.write_bytes(b"#!/usr/bin/env python3\n") with zipfile.ZipFile(source, "a") as setup_zip: for member in patch_image.SETUP_SOURCE_MEMBERS: setup_zip.writestr(member, _setup_source(member)) cache_member = str(Path(member).parent / "__pycache__" / f"{Path(member).stem}.cpython-312.pyc") setup_zip.writestr(cache_member, b"stale") setup_zip.writestr("unchanged.txt", b"upstream") os.chmod(source, 0o755) destination = tmp_path / "setup.patched" patch_image.patch_setup_zipapp(source, destination) assert destination.read_bytes().startswith(b"#!/usr/bin/env python3\n") assert destination.stat().st_mode & 0o777 == 0o755 with zipfile.ZipFile(destination) as setup_zip: assert setup_zip.read("unchanged.txt") == b"upstream" assert not any(patch_image.is_setup_cache_member(name) for name in setup_zip.namelist()) mici = setup_zip.read(patch_image.SETUP_SOURCE_MEMBERS[0]).decode() tici = setup_zip.read(patch_image.SETUP_SOURCE_MEMBERS[1]).decode() assert 'OPENPILOT_URL = "file:///usr/comma/installer"' in mici assert 'LargerSlider("slide to install\\nStarPilot"' in mici assert "urllib.request.Request(CONNECTIVITY_URL" in mici assert 'ButtonRadio("StarPilot"' in tici assert "urllib.request.urlopen(CONNECTIVITY_URL" in tici for setup_source in (mici, tici): assert 'USER_AGENT = f"AGNOSSetup-{\'.\'.join(HARDWARE.get_os_version().split(\'.\')[:2])}"' in setup_source assert 're.fullmatch(r"(?:https://installer\\.comma\\.ai/)?([A-Za-z0-9_-]+)/([A-Za-z0-9_.-]+)", url)' in setup_source assert "install.sunnypilot.ai/release-mici" in setup_source assert "patch_bundled_installer(tmpfile, *self.bundled_installer_target)" in setup_source assert "install_bundled_installer(*bundled_target, self.installer_url)" in setup_source assert 'self.bundled_installer_target = (("firestar5683", "StarPilot") if url == OPENPILOT_URL else None)' in setup_source assert "self.installer_url = (" in setup_source assert "url = OPENPILOT_URL" in setup_source assert "f.write(self.installer_url)" in setup_source assert 'open("/usr/comma/installer", "rb")' in setup_source assert "self.download_url == OPENPILOT_URL" in setup_source assert 'url = f"https://installer.comma.ai/{url}"' not in setup_source shorthand_pattern = r"(?:https://installer\.comma\.ai/)?([A-Za-z0-9_-]+)/([A-Za-z0-9_.-]+)" assert re.fullmatch(shorthand_pattern, "sunnypilot/release-mici").groups() == ("sunnypilot", "release-mici") assert re.fullmatch(shorthand_pattern, "https://installer.comma.ai/sunnypilot/release-mici").groups() == ( "sunnypilot", "release-mici", ) for installer_url in ( "install.sunnypilot.ai/release-mici", "https://install.sunnypilot.ai/release-mici", "staging.sunnypilot.ai", "dev.sunnypilot.ai", ): assert re.fullmatch(shorthand_pattern, installer_url) is None def test_patch_installer_binary_keeps_elf_layout_and_targets_starpilot(tmp_path): source = tmp_path / "installer" source.write_bytes( b"\x7fELF" + b"https://github.com/commaai/openpilot.git?" + b" " * 64 + b"\0" + b"release3?" + b" " * 64 + b"\0tail" ) os.chmod(source, 0o755) destination = tmp_path / "installer.patched" patch_image.patch_installer_binary(source, destination) assert destination.stat().st_size == source.stat().st_size assert destination.stat().st_mode & 0o777 == 0o755 data = destination.read_bytes() assert data.count(b"https://github.com/firestar5683/openpilot.git?") == 1 assert data.count(b"StarPilot?") == 1 assert b"commaai/openpilot" not in data def test_update_manifest_changes_only_system_entry(): original = [ {"name": "boot", "url": "custom-boot", "hash": "boot-hash"}, {"name": "system", "url": "old", "hash": "old", "alt": {"url": "old-alt"}}, ] updated = patch_image.update_manifest_system_entry(original, "hosted", "new-hash", 123) assert updated[0] == original[0] assert updated[1] == { "name": "system", "url": "hosted", "hash": "new-hash", "hash_raw": "new-hash", "size": 123, "sparse": False, "full_check": False, "has_ab": True, "ondevice_hash": "new-hash", } assert json.dumps(original) def test_protected_payload_validation_reports_any_drift(): patch_image.validate_protected_payloads(dict(patch_image.PROTECTED_PAYLOAD_HASHES)) changed = dict(patch_image.PROTECTED_PAYLOAD_HASHES) changed["/usr/comma/reset"] = "0" * 64 with pytest.raises(RuntimeError, match="/usr/comma/reset"): patch_image.validate_protected_payloads(changed)