quantum_link/
lib.rs

1// SPDX-FileCopyrightText: 2024 Foundation Devices, Inc. <hello@foundation.xyz>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4pub use foundation_api;
5pub use worker::*;
6
7pub mod messages;
8mod worker;
9
10use messages::*;
11use server::{CheckedConn, CheckedPermissions, MessageAllowed};
12
13#[macro_export]
14macro_rules! use_api {
15    () => {
16        mod quantum_link_permissions {
17            use quantum_link::messages::*;
18            #[derive(Clone, Default, server::Permissions)]
19            #[server_name = "os/quantum-link"]
20            pub struct QuantumLinkPermissions;
21        }
22        type QuantumLinkApi = quantum_link::QuantumLinkApi<quantum_link_permissions::QuantumLinkPermissions>;
23        type QlStatus = quantum_link::QlStatus<quantum_link_permissions::QuantumLinkPermissions>;
24    };
25}
26
27#[macro_export]
28macro_rules! use_prestart_api {
29    () => {
30        mod quantum_link_prestart_permissions {
31            use quantum_link::messages::StartWithoutFilesystem;
32            #[derive(Clone, Default, server::Permissions)]
33            #[server_name = "os/ql-prestart"]
34            pub struct QuantumLinkPrestartPermissions;
35        }
36        use quantum_link_prestart_permissions::QuantumLinkPrestartPermissions;
37    };
38}
39
40#[derive(Default)]
41pub struct QuantumLinkApi<P: CheckedPermissions> {
42    conn: CheckedConn<P>,
43}
44
45impl<P: CheckedPermissions> QuantumLinkApi<P> {
46    /// Returns the devices XID document
47    pub fn xid_document(&self) -> Vec<u8>
48    where
49        P: MessageAllowed<GetXidDocument>,
50    {
51        self.conn.send_blocking_archive(GetXidDocument)
52    }
53
54    pub fn subscribe_restore_magic_backup<S>(&self, context: &mut server::ServerContext<S>)
55    where
56        S: server::Server + server::ArchiveEventHandler<foundation_api::backup::RestoreMagicBackupEvent>,
57        P: MessageAllowed<SubscribeRestoreMagicBackup>,
58    {
59        self.conn.subscribe_archive_infallible(SubscribeRestoreMagicBackup, context)
60    }
61
62    pub fn subscribe_firmware_fetch<S>(&self, context: &mut server::ServerContext<S>)
63    where
64        S: server::Server + server::ArchiveEventHandler<foundation_api::firmware::FirmwareFetchEvent>,
65        P: MessageAllowed<SubscribeFirmwareFetch>,
66    {
67        self.conn.subscribe_archive_infallible(SubscribeFirmwareFetch, context)
68    }
69
70    pub fn start_firmware_update(&self, chunk_offset: Option<u64>) -> Result<(), SendMessageError>
71    where
72        P: MessageAllowed<StartFirmwareUpdate>,
73    {
74        self.conn.send_blocking_archive(StartFirmwareUpdate { chunk_offset })
75    }
76}
77
78pub fn start_quantum_link_without_filesystem<P>()
79where
80    P: CheckedPermissions,
81    P: MessageAllowed<StartWithoutFilesystem>,
82{
83    let Some(conn) =
84        server::CheckedConn::<P>::try_connect_with_timeout(std::time::Duration::from_millis(2000))
85    else {
86        log::warn!("QL prestart server was not running");
87        return;
88    };
89    conn.send_blocking_scalar(StartWithoutFilesystem);
90}
91
92#[derive(Debug, Copy, Clone, thiserror::Error, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
93pub enum SendMessageError {
94    #[error("no device paired")]
95    NoDevicePaired,
96    #[error(transparent)]
97    Bluetooth(#[from] bt::BluetoothError),
98    #[error("send message cancelled")]
99    Cancelled,
100    #[error("timed out")]
101    Timeout,
102}
103
104#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
105pub enum SecurityCheckState {
106    /// Challenge received from Envoy, processing with Security server
107    ReceivedChallenge,
108
109    /// Security check completed successfully
110    Success,
111
112    /// Security check failed - device validation failed
113    Failed,
114
115    /// Error communicating with Foundation servers
116    Error,
117}
118
119#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
120pub enum PairingEvent {
121    RequestReceived,
122    PairingComplete { device_name: String, new: bool },
123    PairingFailed,
124    Disconnected,
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128#[repr(C)]
129pub struct ConnectionStatus {
130    pub bt_connected: bool,
131    pub ql_paired: bool,
132    pub live: bool,
133}
134
135impl server::FromScalar<1> for ConnectionStatus {
136    fn from_scalar([value]: [u32; 1]) -> Self {
137        Self { bt_connected: (value & 0x1) != 0, ql_paired: (value & 0x2) != 0, live: (value & 0x4) != 0 }
138    }
139}
140
141impl server::AsScalar<1> for ConnectionStatus {
142    fn as_scalar(&self) -> [u32; 1] {
143        let mut value = 0u32;
144        if self.bt_connected {
145            value |= 0x1;
146        }
147        if self.ql_paired {
148            value |= 0x2;
149        }
150        if self.live {
151            value |= 0x4;
152        }
153        [value]
154    }
155}