IQ.Pilot Release Commit @ 0798119
This commit is contained in:
500
iqpilot/_proprietary_loader.py
Normal file
500
iqpilot/_proprietary_loader.py
Normal file
@@ -0,0 +1,500 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
|
||||
_IQPILOT_PUBLIC_KEY = bytes.fromhex("40ae3f81b77506ecc4982a1ca37ba1d6f8765d2ae510eae9039577206c3e5732")
|
||||
|
||||
_KONN3KT_API_HOST = os.environ.get("KONN3KT_API_HOST", "https://api-iqlabs.konn3kt.com").rstrip("/")
|
||||
_KONN3KT_API_HOST_FALLBACK = "https://api-iqlabs.konn3kt.com"
|
||||
|
||||
|
||||
class ProprietaryModuleMissing(ImportError):
|
||||
pass
|
||||
|
||||
|
||||
class ProprietaryModuleIntegrityError(ImportError):
|
||||
pass
|
||||
|
||||
|
||||
_verified_roots: set[Path] = set()
|
||||
|
||||
|
||||
def _dev_fallbacks_enabled() -> bool:
|
||||
return os.environ.get("IQPILOT_ALLOW_DEV_FALLBACKS", "").strip() == "1"
|
||||
|
||||
|
||||
def _read_dongle_id() -> str | None:
|
||||
for path in ("/persist/comma/dongle_id", "/data/params/d/DongleId"):
|
||||
try:
|
||||
val = Path(path).read_text(encoding="utf-8").strip()
|
||||
if val and len(val) >= 12:
|
||||
return val
|
||||
except Exception:
|
||||
continue
|
||||
try:
|
||||
from openpilot.common.params import Params
|
||||
val = Params().get("DongleId", encoding="utf-8")
|
||||
if val:
|
||||
return val.strip()
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _make_device_jwt(dongle_id: str) -> str | None:
|
||||
try:
|
||||
from iqpilot.konn3kt.cloud_client import Konn3ktApi
|
||||
return Konn3ktApi(dongle_id).get_token(expiry_hours=1)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
from openpilot.common.api.base import BaseApi
|
||||
api = BaseApi(dongle_id, _KONN3KT_API_HOST)
|
||||
return api.get_token(expiry_hours=1)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
import base64
|
||||
import json as _json
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
|
||||
key_path = Path("/persist/comma/id_rsa")
|
||||
if not key_path.exists():
|
||||
return None
|
||||
private_key = serialization.load_pem_private_key(key_path.read_bytes(), password=None)
|
||||
now = int(time.time())
|
||||
_sep = (",", ":")
|
||||
header = base64.urlsafe_b64encode(_json.dumps({"alg": "RS256", "typ": "JWT"}, separators=_sep).encode()).rstrip(b"=")
|
||||
claims = base64.urlsafe_b64encode(_json.dumps({"identity": dongle_id, "iat": now, "nbf": now, "exp": now + 3600}, separators=_sep).encode()).rstrip(b"=")
|
||||
signing_input = header + b"." + claims
|
||||
sig = private_key.sign(signing_input, padding.PKCS1v15(), hashes.SHA256())
|
||||
sig_b64 = base64.urlsafe_b64encode(sig).rstrip(b"=")
|
||||
return (signing_input + b"." + sig_b64).decode("ascii")
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _read_git_commit() -> str | None:
|
||||
try:
|
||||
from openpilot.system.version import get_build_metadata
|
||||
return get_build_metadata().openpilot.git_commit
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _snapshot_module_attrs(root: Path) -> dict[str, str]:
|
||||
attrs: dict[str, str] = {}
|
||||
base = root.parent
|
||||
try:
|
||||
for f in sorted(base.rglob("*.so")):
|
||||
attrs[str(f.relative_to(base))] = hashlib.sha256(f.read_bytes()).hexdigest()
|
||||
except Exception:
|
||||
pass
|
||||
return attrs
|
||||
|
||||
|
||||
def _sync_runtime_state(python_root: Path, flags: list[str]) -> None:
|
||||
import json
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
dongle_id = _read_dongle_id()
|
||||
if not dongle_id:
|
||||
os._exit(174)
|
||||
|
||||
payload = json.dumps({
|
||||
"t": "rt_health",
|
||||
"d": {
|
||||
"r": str(python_root),
|
||||
"f": flags,
|
||||
"m": _snapshot_module_attrs(python_root),
|
||||
"ts": time.time(),
|
||||
"v": _read_git_commit(),
|
||||
},
|
||||
}).encode("utf-8")
|
||||
|
||||
headers = {"Content-Type": "application/json", "User-Agent": "iqpilot/1.0"}
|
||||
|
||||
token = _make_device_jwt(dongle_id)
|
||||
if token:
|
||||
headers["Authorization"] = f"JWT {token}"
|
||||
|
||||
for api_host in (_KONN3KT_API_HOST, _KONN3KT_API_HOST_FALLBACK):
|
||||
try:
|
||||
url = f"{api_host}/v1/devices/{dongle_id}/rt_health"
|
||||
req = urllib.request.Request(url, data=payload, headers=headers, method="POST")
|
||||
urllib.request.urlopen(req, timeout=10)
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
os._exit(174)
|
||||
|
||||
|
||||
class ProprietaryModuleIntegrityError(ImportError):
|
||||
pass
|
||||
|
||||
|
||||
_verified_roots: set[Path] = set()
|
||||
|
||||
|
||||
def _read_dongle_id() -> str | None:
|
||||
for path in ("/persist/comma/dongle_id", "/data/params/d/DongleId"):
|
||||
try:
|
||||
val = Path(path).read_text(encoding="utf-8").strip()
|
||||
if val and len(val) >= 12:
|
||||
return val
|
||||
except Exception:
|
||||
continue
|
||||
try:
|
||||
from openpilot.common.params import Params
|
||||
val = Params().get("DongleId", encoding="utf-8")
|
||||
if val:
|
||||
return val.strip()
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _make_device_jwt(dongle_id: str) -> str | None:
|
||||
try:
|
||||
from iqpilot.konn3kt.cloud_client import Konn3ktApi
|
||||
return Konn3ktApi(dongle_id).get_token(expiry_hours=1)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
from openpilot.common.api.base import BaseApi
|
||||
api = BaseApi(dongle_id, _KONN3KT_API_HOST)
|
||||
return api.get_token(expiry_hours=1)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
import base64
|
||||
import json as _json
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
|
||||
key_path = Path("/persist/comma/id_rsa")
|
||||
if not key_path.exists():
|
||||
return None
|
||||
private_key = serialization.load_pem_private_key(key_path.read_bytes(), password=None)
|
||||
now = int(time.time())
|
||||
_sep = (",", ":")
|
||||
header = base64.urlsafe_b64encode(_json.dumps({"alg": "RS256", "typ": "JWT"}, separators=_sep).encode()).rstrip(b"=")
|
||||
claims = base64.urlsafe_b64encode(_json.dumps({"identity": dongle_id, "iat": now, "nbf": now, "exp": now + 3600}, separators=_sep).encode()).rstrip(b"=")
|
||||
signing_input = header + b"." + claims
|
||||
sig = private_key.sign(signing_input, padding.PKCS1v15(), hashes.SHA256())
|
||||
sig_b64 = base64.urlsafe_b64encode(sig).rstrip(b"=")
|
||||
return (signing_input + b"." + sig_b64).decode("ascii")
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _read_git_commit() -> str | None:
|
||||
try:
|
||||
from openpilot.system.version import get_build_metadata
|
||||
return get_build_metadata().openpilot.git_commit
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _snapshot_module_attrs(root: Path) -> dict[str, str]:
|
||||
attrs: dict[str, str] = {}
|
||||
base = root.parent
|
||||
try:
|
||||
for f in sorted(base.rglob("*.so")):
|
||||
attrs[str(f.relative_to(base))] = hashlib.sha256(f.read_bytes()).hexdigest()
|
||||
except Exception:
|
||||
pass
|
||||
return attrs
|
||||
|
||||
|
||||
def _sync_runtime_state(python_root: Path, flags: list[str]) -> None:
|
||||
import json
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
dongle_id = _read_dongle_id()
|
||||
if not dongle_id:
|
||||
os._exit(174)
|
||||
|
||||
payload = json.dumps({
|
||||
"t": "rt_health",
|
||||
"d": {
|
||||
"r": str(python_root),
|
||||
"f": flags,
|
||||
"m": _snapshot_module_attrs(python_root),
|
||||
"ts": time.time(),
|
||||
"v": _read_git_commit(),
|
||||
},
|
||||
}).encode("utf-8")
|
||||
|
||||
headers = {"Content-Type": "application/json", "User-Agent": "iqpilot/1.0"}
|
||||
|
||||
token = _make_device_jwt(dongle_id)
|
||||
if token:
|
||||
headers["Authorization"] = f"JWT {token}"
|
||||
|
||||
for api_host in (_KONN3KT_API_HOST, _KONN3KT_API_HOST_FALLBACK):
|
||||
try:
|
||||
url = f"{api_host}/v1/devices/{dongle_id}/rt_health"
|
||||
req = urllib.request.Request(url, data=payload, headers=headers, method="POST")
|
||||
urllib.request.urlopen(req, timeout=10)
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
os._exit(174)
|
||||
|
||||
|
||||
def _iter_proprietary_python_roots() -> list[Path]:
|
||||
roots: list[Path] = []
|
||||
|
||||
env_root_raw = os.environ.get("IQPILOT_PROPRIETARY_ROOT", "").strip()
|
||||
if env_root_raw:
|
||||
env_root = Path(env_root_raw)
|
||||
roots.append(env_root)
|
||||
bundles_root = env_root / "bundles"
|
||||
if bundles_root.exists():
|
||||
roots.extend(sorted(bundle / "python" for bundle in bundles_root.iterdir() if bundle.is_dir()))
|
||||
|
||||
repo_root = Path(__file__).resolve().parents[1]
|
||||
_artifact_names = ["iqpilot_model_selector_private", "iqpilot_maps_private", "iqpilot_navd_private", "iqpilot_hephaestusd_private", "iqpilot_alc_private", "iqpilot_commander_private", "iqpilot_updater_private"]
|
||||
# Check repo_root and its parent — handles the case where the repo is cloned
|
||||
# inside a parent dir that holds the artifacts (e.g. /data/openpilot/openpilot/
|
||||
# with artifacts at /data/openpilot/artifacts/).
|
||||
for artifact_base in (repo_root, repo_root.parent):
|
||||
for name in _artifact_names:
|
||||
roots.append(artifact_base / "artifacts" / name)
|
||||
|
||||
return [root / "python" for root in roots]
|
||||
|
||||
|
||||
def _iter_repo_roots() -> list[Path]:
|
||||
roots: list[Path] = []
|
||||
seen: set[Path] = set()
|
||||
this_file = Path(__file__).resolve()
|
||||
|
||||
for parent in this_file.parents:
|
||||
if parent in seen:
|
||||
continue
|
||||
|
||||
if (parent / "konn3kt_private").exists() or (parent / "iqpilot" / "models_private_src").exists() or (parent / "iqdbc_repo").exists():
|
||||
roots.append(parent)
|
||||
seen.add(parent)
|
||||
|
||||
return roots
|
||||
|
||||
|
||||
def _repo_private_source_module_name(private_module_name: str) -> str | None:
|
||||
if private_module_name.startswith("iqpilot_private.models."):
|
||||
return private_module_name.replace("iqpilot_private.models.", "iqpilot.models_private_src.", 1)
|
||||
if private_module_name.startswith("iqpilot_private.maps."):
|
||||
return private_module_name.replace("iqpilot_private.maps.", "iqpilot.maps_private_src.", 1)
|
||||
if private_module_name.startswith("iqpilot_private.navd."):
|
||||
return private_module_name.replace("iqpilot_private.navd.", "konn3kt_private.navd.", 1)
|
||||
if private_module_name.startswith("iqpilot_private.konn3kt.hephaestus."):
|
||||
return private_module_name.replace("iqpilot_private.konn3kt.hephaestus.", "konn3kt_private.hephaestus.", 1)
|
||||
if private_module_name.startswith("iqpilot_private.konn3kt.uploaderd.") or private_module_name == "iqpilot_private.konn3kt.uploaderd":
|
||||
return private_module_name.replace("iqpilot_private.konn3kt.uploaderd", "konn3kt_private.uploaderd", 1)
|
||||
if private_module_name.startswith("iqpilot_private.konn3kt.iqlvbs."):
|
||||
return private_module_name.replace("iqpilot_private.konn3kt.iqlvbs.", "konn3kt_private.iqlvbs.", 1)
|
||||
return None
|
||||
|
||||
|
||||
def _load_repo_private_source(private_module_name: str) -> ModuleType | None:
|
||||
if not _dev_fallbacks_enabled():
|
||||
return None
|
||||
|
||||
fallback_module_name = _repo_private_source_module_name(private_module_name)
|
||||
if fallback_module_name is None:
|
||||
return None
|
||||
|
||||
for repo_root in _iter_repo_roots():
|
||||
repo_root_str = str(repo_root)
|
||||
if repo_root_str not in sys.path:
|
||||
sys.path.insert(0, repo_root_str)
|
||||
|
||||
try:
|
||||
return importlib.import_module(fallback_module_name)
|
||||
except ModuleNotFoundError as error:
|
||||
missing = error.name or ""
|
||||
if missing == fallback_module_name or missing.startswith(f"{fallback_module_name}.") or fallback_module_name.startswith(f"{missing}."):
|
||||
continue
|
||||
raise
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _candidate_module_paths(python_root: Path, private_module_name: str) -> list[Path]:
|
||||
rel_parts = private_module_name.split(".")
|
||||
module_base = python_root.joinpath(*rel_parts)
|
||||
paths = [
|
||||
module_base.with_suffix(".py"),
|
||||
module_base.with_suffix(".pyc"),
|
||||
]
|
||||
paths.extend(module_base.parent.glob(f"{module_base.name}.*.so"))
|
||||
paths.append(module_base / "__init__.py")
|
||||
paths.append(module_base / "__init__.pyc")
|
||||
paths.extend(module_base.glob("__init__.*.so"))
|
||||
return paths
|
||||
|
||||
|
||||
def _module_root_for_name(private_module_name: str) -> Path | None:
|
||||
for python_root in _iter_proprietary_python_roots():
|
||||
if not (python_root / "iqpilot_private").exists():
|
||||
continue
|
||||
if any(path.exists() for path in _candidate_module_paths(python_root, private_module_name)):
|
||||
return python_root
|
||||
return None
|
||||
|
||||
|
||||
def _load_manifest(manifest_path: Path) -> dict:
|
||||
import json
|
||||
|
||||
try:
|
||||
return json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _verify_bundle_signatures(python_root: Path) -> None:
|
||||
global _verified_roots
|
||||
if python_root in _verified_roots:
|
||||
return
|
||||
|
||||
|
||||
manifest_path = python_root.parent / "manifest.json"
|
||||
|
||||
manifest = _load_manifest(manifest_path)
|
||||
signatures = manifest.get("signatures")
|
||||
if not signatures:
|
||||
raise ProprietaryModuleIntegrityError(f"unsigned bundle: {python_root}")
|
||||
|
||||
try:
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
||||
except Exception:
|
||||
raise ProprietaryModuleIntegrityError(f"required dependency missing for {python_root}")
|
||||
|
||||
try:
|
||||
public_key = Ed25519PublicKey.from_public_bytes(_IQPILOT_PUBLIC_KEY)
|
||||
except Exception as exc:
|
||||
raise ProprietaryModuleIntegrityError(f"module init failed: {exc}")
|
||||
|
||||
import base64
|
||||
|
||||
flags: list[str] = []
|
||||
for rel_path, sig_b64 in signatures.items():
|
||||
so_path = python_root.parent / rel_path
|
||||
if not so_path.exists():
|
||||
flags.append(f"missing: {rel_path}")
|
||||
continue
|
||||
try:
|
||||
sig = base64.b64decode(sig_b64)
|
||||
digest = hashlib.sha256(so_path.read_bytes()).digest()
|
||||
public_key.verify(sig, digest)
|
||||
except InvalidSignature:
|
||||
flags.append(f"mismatch: {rel_path}")
|
||||
continue
|
||||
except Exception as exc:
|
||||
flags.append(f"{rel_path}: {exc}")
|
||||
continue
|
||||
|
||||
if flags:
|
||||
_sync_runtime_state(python_root, flags)
|
||||
raise ProprietaryModuleIntegrityError(f"module integrity check failed for {python_root}")
|
||||
|
||||
_verified_roots.add(python_root)
|
||||
|
||||
|
||||
def _extend_package_path(package_name: str, new_pkg_dir: Path) -> None:
|
||||
pkg = sys.modules.get(package_name)
|
||||
if pkg is not None and hasattr(pkg, "__path__"):
|
||||
new_dir_str = str(new_pkg_dir)
|
||||
if new_dir_str not in list(pkg.__path__):
|
||||
pkg.__path__.append(new_dir_str)
|
||||
|
||||
|
||||
def _ensure_private_path(private_module_name: str) -> None:
|
||||
resolved_root = _module_root_for_name(private_module_name)
|
||||
if resolved_root is not None:
|
||||
resolved_root_str = str(resolved_root)
|
||||
if resolved_root_str not in sys.path:
|
||||
sys.path.insert(0, resolved_root_str)
|
||||
_verify_bundle_signatures(resolved_root)
|
||||
|
||||
parts = private_module_name.split(".")
|
||||
for i in range(1, len(parts)):
|
||||
pkg_name = ".".join(parts[:i])
|
||||
_extend_package_path(pkg_name, resolved_root.joinpath(*parts[:i]))
|
||||
return
|
||||
|
||||
for python_root in _iter_proprietary_python_roots():
|
||||
if (python_root / "iqpilot_private").exists():
|
||||
python_root_str = str(python_root)
|
||||
if python_root_str not in sys.path:
|
||||
sys.path.insert(0, python_root_str)
|
||||
_verify_bundle_signatures(python_root)
|
||||
_extend_package_path("iqpilot_private", python_root / "iqpilot_private")
|
||||
return
|
||||
|
||||
|
||||
def _is_private_module_missing(error: ModuleNotFoundError, private_module_name: str) -> bool:
|
||||
missing = error.name or ""
|
||||
parts = private_module_name.split(".")
|
||||
valid_missing = {".".join(parts[:i]) for i in range(1, len(parts) + 1)}
|
||||
return missing in valid_missing or private_module_name.startswith(f"{missing}.")
|
||||
|
||||
|
||||
def _publish_module_symbols(public_module: ModuleType, private_module: ModuleType) -> None:
|
||||
skip = {
|
||||
"__name__",
|
||||
"__package__",
|
||||
"__loader__",
|
||||
"__spec__",
|
||||
"__file__",
|
||||
"__cached__",
|
||||
"__builtins__",
|
||||
}
|
||||
|
||||
for key, value in private_module.__dict__.items():
|
||||
if key in skip:
|
||||
continue
|
||||
public_module.__dict__[key] = value
|
||||
|
||||
public_module.__dict__["__private_module__"] = private_module.__name__
|
||||
if "__all__" not in public_module.__dict__:
|
||||
public_module.__dict__["__all__"] = [k for k in private_module.__dict__ if not k.startswith("_")]
|
||||
|
||||
|
||||
def load_private_module(public_module_name: str, private_module_name: str) -> ModuleType:
|
||||
public_module = sys.modules[public_module_name]
|
||||
_ensure_private_path(private_module_name)
|
||||
|
||||
try:
|
||||
private_module = importlib.import_module(private_module_name)
|
||||
except ModuleNotFoundError as error:
|
||||
if _is_private_module_missing(error, private_module_name):
|
||||
private_module = _load_repo_private_source(private_module_name)
|
||||
if private_module is None:
|
||||
raise ProprietaryModuleMissing(
|
||||
f"missing proprietary module '{private_module_name}'. install the IQ Pilot private proprietary bundle into IQPILOT_PROPRIETARY_ROOT"
|
||||
) from error
|
||||
else:
|
||||
raise
|
||||
|
||||
_publish_module_symbols(public_module, private_module)
|
||||
return private_module
|
||||
Reference in New Issue
Block a user