Skip to main content

iris_chat_core/
fips_ble_ffi.rs

1use std::sync::atomic::{AtomicBool, Ordering};
2use std::sync::Arc;
3use std::time::{Duration, SystemTime, UNIX_EPOCH};
4
5use flume::Sender;
6
7use crate::{CoreMsg, FfiApp, FipsBleCommand, FipsBleEvent};
8
9#[derive(uniffi::Object)]
10pub struct FfiFipsBle {
11    runtime: tokio::runtime::Runtime,
12    adapter: Arc<fips_core::transport::ble::host::HostBleAdapter>,
13    core_tx: Sender<CoreMsg>,
14    closed: AtomicBool,
15}
16
17#[derive(Debug, Clone, uniffi::Error)]
18pub enum FipsBleBridgeError {
19    Initialization(String),
20}
21
22impl std::fmt::Display for FipsBleBridgeError {
23    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        match self {
25            Self::Initialization(message) => formatter.write_str(message),
26        }
27    }
28}
29
30impl std::error::Error for FipsBleBridgeError {}
31
32#[uniffi::export]
33impl FfiFipsBle {
34    #[uniffi::constructor]
35    pub fn new(app: Arc<FfiApp>) -> Result<Arc<Self>, FipsBleBridgeError> {
36        let runtime = tokio::runtime::Builder::new_multi_thread()
37            .worker_threads(1)
38            .enable_time()
39            .build()
40            .map_err(|error| {
41                FipsBleBridgeError::Initialization(format!(
42                    "Could not create the FIPS BLE runtime: {error}"
43                ))
44            })?;
45        let local_token = format!(
46            "iris-{}",
47            SystemTime::now()
48                .duration_since(UNIX_EPOCH)
49                .unwrap_or_default()
50                .as_nanos()
51        );
52        let (io, adapter) = runtime
53            .block_on(async {
54                fips_core::transport::ble::host::HostBleIo::channel("mobile", local_token, 256)
55            })
56            .map_err(|error| {
57                FipsBleBridgeError::Initialization(format!(
58                    "Could not create the FIPS BLE bridge: {error}"
59                ))
60            })?;
61        let (reply_tx, reply_rx) = flume::bounded(1);
62        app.background_tx
63            .send(CoreMsg::AttachHostBle {
64                attachment: crate::core::HostBleAttachment::new(io),
65                reply_tx,
66            })
67            .map_err(|error| {
68                FipsBleBridgeError::Initialization(format!(
69                    "Could not request the FIPS BLE bridge attachment: {error}"
70                ))
71            })?;
72        let attach_result = reply_rx
73            .recv_timeout(Duration::from_secs(2))
74            .map_err(|error| {
75                FipsBleBridgeError::Initialization(format!(
76                    "The Iris core did not attach the FIPS BLE bridge: {error}"
77                ))
78            })?;
79        attach_result.map_err(FipsBleBridgeError::Initialization)?;
80        Ok(Arc::new(Self {
81            runtime,
82            adapter: Arc::new(adapter),
83            core_tx: app.background_tx.clone(),
84            closed: AtomicBool::new(false),
85        }))
86    }
87
88    pub fn next_command(&self, timeout_ms: u64) -> Option<FipsBleCommand> {
89        let timeout = Duration::from_millis(timeout_ms.clamp(1, 60_000));
90        self.runtime.block_on(async {
91            tokio::time::timeout(timeout, self.adapter.next_command())
92                .await
93                .ok()
94                .flatten()
95                .map(Into::into)
96        })
97    }
98
99    pub fn emit(&self, event: FipsBleEvent) -> bool {
100        self.runtime
101            .block_on(self.adapter.emit(event.into()))
102            .is_ok()
103    }
104
105    pub fn detach(&self) {
106        if self.closed.swap(true, Ordering::SeqCst) {
107            return;
108        }
109        let (reply_tx, reply_rx) = flume::bounded(1);
110        if self.core_tx.send(CoreMsg::DetachHostBle(reply_tx)).is_ok() {
111            let _ = reply_rx.recv_timeout(Duration::from_secs(2));
112        }
113    }
114}
115
116impl From<fips_core::transport::ble::host::HostBleCommand> for FipsBleCommand {
117    fn from(command: fips_core::transport::ble::host::HostBleCommand) -> Self {
118        use fips_core::transport::ble::host::HostBleCommand as Host;
119        match command {
120            Host::Listen {
121                request_id,
122                preferred_psm,
123            } => Self::Listen {
124                request_id,
125                preferred_psm,
126            },
127            Host::StopListening => Self::StopListening,
128            Host::StartAdvertising {
129                request_id,
130                bootstrap,
131            } => Self::StartAdvertising {
132                request_id,
133                bootstrap,
134            },
135            Host::StopAdvertising { request_id } => Self::StopAdvertising { request_id },
136            Host::StartScanning { request_id } => Self::StartScanning { request_id },
137            Host::StopScanning => Self::StopScanning,
138            Host::Connect {
139                request_id,
140                peer_token,
141                psm,
142            } => Self::Connect {
143                request_id,
144                peer_token,
145                psm,
146            },
147            Host::Write {
148                request_id,
149                connection_id,
150                bytes,
151            } => Self::Write {
152                request_id,
153                connection_id,
154                bytes,
155            },
156            Host::Close { connection_id } => Self::Close { connection_id },
157        }
158    }
159}
160
161impl From<FipsBleEvent> for fips_core::transport::ble::host::HostBleEvent {
162    fn from(event: FipsBleEvent) -> Self {
163        use fips_core::transport::ble::host::HostBleEvent as Host;
164        match event {
165            FipsBleEvent::Listening { request_id, psm } => Host::Listening { request_id, psm },
166            FipsBleEvent::AdvertisingStarted { request_id } => {
167                Host::AdvertisingStarted { request_id }
168            }
169            FipsBleEvent::AdvertisingStopped { request_id } => {
170                Host::AdvertisingStopped { request_id }
171            }
172            FipsBleEvent::ScanningStarted { request_id } => Host::ScanningStarted { request_id },
173            FipsBleEvent::PeerDiscovered {
174                peer_token,
175                bootstrap,
176            } => Host::PeerDiscovered {
177                peer_token,
178                bootstrap,
179            },
180            FipsBleEvent::Connected {
181                request_id,
182                connection_id,
183                peer_token,
184                send_segment_mtu,
185                receive_segment_mtu,
186            } => Host::Connected {
187                request_id,
188                connection_id,
189                peer_token,
190                send_segment_mtu,
191                receive_segment_mtu,
192            },
193            FipsBleEvent::IncomingConnection {
194                connection_id,
195                peer_token,
196                send_segment_mtu,
197                receive_segment_mtu,
198            } => Host::IncomingConnection {
199                connection_id,
200                peer_token,
201                send_segment_mtu,
202                receive_segment_mtu,
203            },
204            FipsBleEvent::BytesReceived {
205                connection_id,
206                bytes,
207            } => Host::BytesReceived {
208                connection_id,
209                bytes,
210            },
211            FipsBleEvent::WriteCompleted { request_id } => Host::WriteCompleted { request_id },
212            FipsBleEvent::Disconnected {
213                connection_id,
214                reason,
215            } => Host::Disconnected {
216                connection_id,
217                reason,
218            },
219            FipsBleEvent::Failed {
220                request_id,
221                message,
222            } => Host::Failed {
223                request_id,
224                message,
225            },
226        }
227    }
228}