mirror of
https://github.com/infiniteCable2/openpilot.git
synced 2026-07-27 04:12:10 +08:00
43e43849ad
* Add AES encryption and utility methods This commit introduces a new AESCipher class that can be used for AES encryption with support for both AES-128 and AES-256. It also adds a set of utility functions, including methods for RSA to AES key derivation, file decryption and compression, and encryption and decompression. These changes provide fundamental cryptographic functionalities for data security within the system. * Revised backup management system with new structures This update introduces important revisions to the backup management system. A new struct named 'BackupManagerSP' has been integrated into the 'custom.capnp' file, replacing 'CustomReserved6'. This new struct includes several variables that help keep track of backup and restore status, progress and related messages. Additional code modifications were made in 'base.py' and 'api.py' to include a 'json' parameter in the 'api_get' method. Lastly, 'manager.py' has been updated with functions to manage device configuration backups to and from sunnylink. The changes promote better handling and management of data backup and restoration processes. The update is specifically designed to ensure that the backup information is well integrated into the system, with properly tracked status and progress. * Refactor `allKeys` to filter by parameter key type Updated the `allKeys` method to support filtering by `ParamKeyType`, allowing more specific key retrieval. Added a default value for backward compatibility and updated related bindings and keys to reflect this change. * Improve Backup and Restore mechanisms The commit refactors and improves several aspects of the backup and restore mechanisms in the `BackupManagerSP` class. These improvements include removing redundant status tracking variables and replacing them with unified ones, updating the messaging system to handle all changes correctly, and including an enumeration `OperationType` to keep track of the type of operation currently being processed. This commit also applies stricter conditions for restore operations, such that it only restores parameters that are currently marked as backupable, and skips those that are not. This is a preventive measure against potential issues when restoring parameters that are no longer relevant or could conflict with current versions. Also, the encryption and decryption methods were updated to use AES-256 for more security. These changes have increased the robustness and reliability of the backup and restoration processes. * copyright * Add backup_manager process to offroad sunnypilot tasks The backup_manager process is introduced to handle backups during offroad mode when SunnyLink is ready. This ensures proper backup management functionality integrated into the system. * Simplify backup endpoint construction in restore method Replaced conditional expression with a more concise and readable `or` operation for constructing the backup endpoint. This makes the code cleaner and easier to maintain while preserving functionality. * Added support for backing up and restoring sunnypilot settings An update to the sunnypilot functionality now provides two new features that allow users to backup and restore their sunnypilot settings. The changes include the addition of UI controls for initiating backup and restore operations, and the creation of a system-wide state management function for tracking these operations. This enhancement significantly improves the user experience by providing a safety net for user settings in case of software failures, bugs, or unintended changes. * Refactor type hints to use PEP 604 syntax for clarity Replaced `Optional` and `Dict` type hints with `|` and `dict` syntax for improved readability and compliance with Python 3.10+. Updated related imports and adjusted list comprehension for cleaner code. * Update import path for hardware module in utils.py Replaced the import path for the `Paths` module to align with the new directory structure under `openpilot`. This ensures compatibility with recent project reorganization and avoids import errors. * Improve RSA key handling and fix backup status comparison Added explicit RSA key type checks to handle invalid key formats. Enhanced type safety in `manager.py` by ensuring the backup status comparison returns a boolean. These changes improve robustness and error handling in backups. * format * more * Improve backup and restore flow with progress tracking and fixes Added proper progress tracking and cleanup logic during restore operations. Enhanced restore experience by resetting progress after completion and introducing confirmation dialogs for errors and completion. Updated API compatibility with a version query parameter for backups. * Enable backup button only when restore process is complete Previously, the backup button could be enabled during a restore operation, which might cause unintended behavior. This update introduces a check to ensure the backup button remains disabled while a restore process is active. This improves user experience and prevents potential conflicts. * Fix restore button state handling during restore process Ensure the restore button is disabled consistently when a restore is in progress. This prevents user interaction issues and aligns the button state with the restore operation status. * "Refactor restore logic and improve button state handling" Replaced `is_restoring` with `restore_request_pending` for clarity and better state management. Adjusted button behavior to immediately disable upon user action, ensuring improved UX and preventing repeat inputs. Refined restore completion flow for better consistency and reliability. * Refine restore process logic for SunnyLink settings. Introduced `restore_request_started` to improve handling of restore states and ensure accurate UI updates during the process. Adjusted case handling to enhance clarity and maintain proper behavior when restoring settings, especially during ongoing or completed requests. * revert * move around * fix enabled states for different statuses * add prompt to notify backup is complete * same states as restore * disable buttons if sunnylink is off * can use the same texts --------- Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
159 lines
6.2 KiB
Python
159 lines
6.2 KiB
Python
import json
|
|
import os
|
|
import random
|
|
import time
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
|
|
import jwt
|
|
from openpilot.common.api.base import BaseApi
|
|
from openpilot.common.params import Params
|
|
from openpilot.system.hardware import HARDWARE
|
|
from openpilot.system.hardware.hw import Paths
|
|
|
|
API_HOST = os.getenv('SUNNYLINK_API_HOST', 'https://stg.api.sunnypilot.ai')
|
|
UNREGISTERED_SUNNYLINK_DONGLE_ID = "UnregisteredDevice"
|
|
MAX_RETRIES = 6
|
|
CRASH_LOG_DIR = Paths.crash_log_root()
|
|
|
|
|
|
class SunnylinkApi(BaseApi):
|
|
def __init__(self, dongle_id):
|
|
super().__init__(dongle_id, API_HOST)
|
|
self.user_agent = "sunnypilot-"
|
|
self.spinner = None
|
|
self.params = Params()
|
|
|
|
def api_get(self, endpoint, method='GET', timeout=10, access_token=None, json=None, **kwargs):
|
|
if not self.params.get_bool("SunnylinkEnabled"):
|
|
return None
|
|
|
|
return super().api_get(endpoint, method, timeout, access_token, json, **kwargs)
|
|
|
|
def resume_queued(self, timeout=10, **kwargs):
|
|
sunnylinkId, commaId = self._resolve_dongle_ids()
|
|
return self.api_get(f"ws/{sunnylinkId}/resume_queued", "POST", timeout, access_token=self.get_token(), **kwargs)
|
|
|
|
def get_token(self, expiry_hours=1):
|
|
# Add your additional data here
|
|
additional_data = {}
|
|
return super()._get_token(expiry_hours, **additional_data)
|
|
|
|
def _status_update(self, message):
|
|
print(message)
|
|
if self.spinner:
|
|
self.spinner.update(message)
|
|
time.sleep(0.5)
|
|
|
|
def _resolve_dongle_ids(self):
|
|
sunnylink_dongle_id = self.params.get("SunnylinkDongleId", encoding='utf-8')
|
|
comma_dongle_id = self.dongle_id or self.params.get("DongleId", encoding='utf-8')
|
|
return sunnylink_dongle_id, comma_dongle_id
|
|
|
|
def _resolve_imeis(self):
|
|
imei1, imei2 = None, None
|
|
imei_try = 0
|
|
while imei1 is None and imei2 is None and imei_try < MAX_RETRIES:
|
|
try:
|
|
imei1, imei2 = HARDWARE.get_imei(0), HARDWARE.get_imei(1)
|
|
except Exception:
|
|
self._status_update(f"Error getting imei, trying again... [{imei_try + 1}/{MAX_RETRIES}]")
|
|
time.sleep(1)
|
|
imei_try += 1
|
|
return imei1, imei2
|
|
|
|
def _resolve_serial(self):
|
|
return (self.params.get("HardwareSerial", encoding='utf8')
|
|
or HARDWARE.get_serial())
|
|
|
|
def register_device(self, spinner=None, timeout=60, verbose=False):
|
|
self.spinner = spinner
|
|
|
|
sunnylink_dongle_id, comma_dongle_id = self._resolve_dongle_ids()
|
|
|
|
if comma_dongle_id is None:
|
|
self._status_update("Comma dongle ID not found, deferring sunnylink's registration to comma's registration process.")
|
|
return None
|
|
|
|
imei1, imei2 = self._resolve_imeis()
|
|
serial = self._resolve_serial()
|
|
|
|
if sunnylink_dongle_id not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID):
|
|
return sunnylink_dongle_id
|
|
|
|
privkey_path = Path(f"{Paths.persist_root()}/comma/id_rsa")
|
|
pubkey_path = Path(f"{Paths.persist_root()}/comma/id_rsa.pub")
|
|
|
|
start_time = time.monotonic()
|
|
successful_registration = False
|
|
if not pubkey_path.is_file():
|
|
sunnylink_dongle_id = UNREGISTERED_SUNNYLINK_DONGLE_ID
|
|
self._status_update("Public key not found, setting dongle ID to unregistered.")
|
|
else:
|
|
Params().put("LastSunnylinkPingTime", "0") # Reset the last ping time to 0 if we are trying to register
|
|
with pubkey_path.open() as f1, privkey_path.open() as f2:
|
|
public_key = f1.read()
|
|
private_key = f2.read()
|
|
|
|
backoff = 1
|
|
while True:
|
|
register_token = jwt.encode({'register': True, 'exp': datetime.utcnow() + timedelta(hours=1)}, private_key, algorithm='RS256')
|
|
try:
|
|
if verbose or time.monotonic() - start_time < timeout / 2:
|
|
self._status_update("Registering device to sunnylink...")
|
|
elif time.monotonic() - start_time >= timeout / 2:
|
|
self._status_update("Still registering device to sunnylink...")
|
|
|
|
resp = self.api_get("v2/pilotauth/", method='POST', timeout=15, imei=imei1, imei2=imei2, serial=serial,
|
|
comma_dongle_id=comma_dongle_id, public_key=public_key, register_token=register_token)
|
|
|
|
if resp is None:
|
|
raise Exception("Unable to register device, request was None")
|
|
|
|
if resp.status_code in (409, 412):
|
|
timeout = time.monotonic() - start_time # Don't retry if the public key is already in use
|
|
key_in_use = "Public key is already in use, is your key unique? Contact your vendor for a new key."
|
|
unsafe_key = "Public key is known to not be unique and it's unsafe. Contact your vendor for a new key."
|
|
error_message = key_in_use if resp.status_code == 409 else unsafe_key
|
|
raise Exception(error_message)
|
|
|
|
if resp.status_code != 200:
|
|
raise Exception(f"Failed to register with sunnylink. Status code: {resp.status_code}\nData\n:{resp.text}")
|
|
|
|
dongleauth = json.loads(resp.text)
|
|
sunnylink_dongle_id = dongleauth["device_id"]
|
|
if sunnylink_dongle_id:
|
|
self._status_update("Device registered successfully.")
|
|
successful_registration = True
|
|
break
|
|
except Exception as e:
|
|
if verbose:
|
|
self._status_update(f"Waiting {backoff}s before retry, Exception occurred during registration: [{str(e)}]")
|
|
|
|
if not os.path.exists(CRASH_LOG_DIR):
|
|
os.makedirs(CRASH_LOG_DIR)
|
|
|
|
with open(f'{CRASH_LOG_DIR}/error.txt', 'a') as f:
|
|
f.write(f"[{datetime.now()}] sunnylink: {str(e)}\n")
|
|
|
|
backoff = min(backoff * 2 * (0.5 + random.random()), 60)
|
|
time.sleep(backoff)
|
|
|
|
if time.monotonic() - start_time > timeout:
|
|
self._status_update(f"Giving up on sunnylink's registration after {timeout}s. Will retry on next boot.")
|
|
time.sleep(3)
|
|
break
|
|
|
|
self.params.put("SunnylinkDongleId", sunnylink_dongle_id or UNREGISTERED_SUNNYLINK_DONGLE_ID)
|
|
|
|
# Set the last ping time to the current time since we were just talking to the API
|
|
last_ping = int(time.monotonic() * 1e9) if successful_registration else start_time
|
|
Params().put("LastSunnylinkPingTime", str(last_ping))
|
|
|
|
# Disable sunnylink if registration was not successful
|
|
if not successful_registration:
|
|
Params().put_bool("SunnylinkEnabled", False)
|
|
|
|
self.spinner = None
|
|
return sunnylink_dongle_id
|