matrix_sdk_crypto_ffi/
dehydrated_devices.rs

1use std::{mem::ManuallyDrop, sync::Arc};
2
3use matrix_sdk_crypto::{
4    dehydrated_devices::{
5        DehydratedDevice as InnerDehydratedDevice, DehydratedDevices as InnerDehydratedDevices,
6        RehydratedDevice as InnerRehydratedDevice,
7    },
8    store::DehydratedDeviceKey as InnerDehydratedDeviceKey,
9};
10use ruma::{api::client::dehydrated_device, events::AnyToDeviceEvent, serde::Raw, OwnedDeviceId};
11use serde_json::json;
12use tokio::runtime::Handle;
13
14use crate::{CryptoStoreError, DehydratedDeviceKey};
15
16#[derive(Debug, thiserror::Error, uniffi::Error)]
17#[uniffi(flat_error)]
18pub enum DehydrationError {
19    #[error(transparent)]
20    Pickle(#[from] matrix_sdk_crypto::vodozemac::DehydratedDeviceError),
21    #[error(transparent)]
22    LegacyPickle(#[from] matrix_sdk_crypto::vodozemac::LibolmPickleError),
23    #[error(transparent)]
24    MissingSigningKey(#[from] matrix_sdk_crypto::SignatureError),
25    #[error(transparent)]
26    Json(#[from] serde_json::Error),
27    #[error(transparent)]
28    Store(#[from] matrix_sdk_crypto::CryptoStoreError),
29    #[error("The pickle key has an invalid length, expected 32 bytes, got {0}")]
30    PickleKeyLength(usize),
31    #[error(transparent)]
32    Rand(#[from] rand::Error),
33}
34
35impl From<matrix_sdk_crypto::dehydrated_devices::DehydrationError> for DehydrationError {
36    fn from(value: matrix_sdk_crypto::dehydrated_devices::DehydrationError) -> Self {
37        match value {
38            matrix_sdk_crypto::dehydrated_devices::DehydrationError::Json(e) => Self::Json(e),
39            matrix_sdk_crypto::dehydrated_devices::DehydrationError::Pickle(e) => Self::Pickle(e),
40            matrix_sdk_crypto::dehydrated_devices::DehydrationError::LegacyPickle(e) => {
41                Self::LegacyPickle(e)
42            }
43            matrix_sdk_crypto::dehydrated_devices::DehydrationError::MissingSigningKey(e) => {
44                Self::MissingSigningKey(e)
45            }
46            matrix_sdk_crypto::dehydrated_devices::DehydrationError::Store(e) => Self::Store(e),
47            matrix_sdk_crypto::dehydrated_devices::DehydrationError::PickleKeyLength(l) => {
48                Self::PickleKeyLength(l)
49            }
50        }
51    }
52}
53
54#[derive(uniffi::Object)]
55pub struct DehydratedDevices {
56    pub(crate) runtime: Handle,
57    pub(crate) inner: ManuallyDrop<InnerDehydratedDevices>,
58}
59
60impl Drop for DehydratedDevices {
61    fn drop(&mut self) {
62        // See the drop implementation for the `crate::OlmMachine` for an explanation.
63        let _guard = self.runtime.enter();
64        unsafe {
65            ManuallyDrop::drop(&mut self.inner);
66        }
67    }
68}
69
70#[matrix_sdk_ffi_macros::export]
71impl DehydratedDevices {
72    pub fn create(&self) -> Result<Arc<DehydratedDevice>, DehydrationError> {
73        let inner = self.runtime.block_on(self.inner.create())?;
74
75        Ok(Arc::new(DehydratedDevice {
76            inner: ManuallyDrop::new(inner),
77            runtime: self.runtime.to_owned(),
78        }))
79    }
80
81    pub fn rehydrate(
82        &self,
83        pickle_key: &DehydratedDeviceKey,
84        device_id: String,
85        device_data: String,
86    ) -> Result<Arc<RehydratedDevice>, DehydrationError> {
87        let device_data: Raw<_> = serde_json::from_str(&device_data)?;
88        let device_id: OwnedDeviceId = device_id.into();
89
90        let key = InnerDehydratedDeviceKey::from_slice(&pickle_key.inner)?;
91
92        let ret = RehydratedDevice {
93            runtime: self.runtime.to_owned(),
94            inner: ManuallyDrop::new(self.runtime.block_on(self.inner.rehydrate(
95                &key,
96                &device_id,
97                device_data,
98            ))?),
99        }
100        .into();
101
102        Ok(ret)
103    }
104
105    /// Get the cached dehydrated device pickle key if any.
106    ///
107    /// None if the key was not previously cached (via
108    /// [`Self::save_dehydrated_device_pickle_key`]).
109    ///
110    /// Should be used to periodically rotate the dehydrated device to avoid
111    /// OTK exhaustion and accumulation of to_device messages.
112    pub fn get_dehydrated_device_key(
113        &self,
114    ) -> Result<Option<crate::DehydratedDeviceKey>, CryptoStoreError> {
115        Ok(self
116            .runtime
117            .block_on(self.inner.get_dehydrated_device_pickle_key())?
118            .map(crate::DehydratedDeviceKey::from))
119    }
120
121    /// Store the dehydrated device pickle key in the crypto store.
122    ///
123    /// This is useful if the client wants to periodically rotate dehydrated
124    /// devices to avoid OTK exhaustion and accumulated to_device problems.
125    pub fn save_dehydrated_device_key(
126        &self,
127        pickle_key: &crate::DehydratedDeviceKey,
128    ) -> Result<(), CryptoStoreError> {
129        let pickle_key = InnerDehydratedDeviceKey::from_slice(&pickle_key.inner)?;
130        Ok(self.runtime.block_on(self.inner.save_dehydrated_device_pickle_key(&pickle_key))?)
131    }
132
133    /// Deletes the previously stored dehydrated device pickle key.
134    pub fn delete_dehydrated_device_key(&self) -> Result<(), CryptoStoreError> {
135        Ok(self.runtime.block_on(self.inner.delete_dehydrated_device_pickle_key())?)
136    }
137}
138
139#[derive(uniffi::Object)]
140pub struct RehydratedDevice {
141    inner: ManuallyDrop<InnerRehydratedDevice>,
142    runtime: Handle,
143}
144
145impl Drop for RehydratedDevice {
146    fn drop(&mut self) {
147        // See the drop implementation for the `crate::OlmMachine` for an explanation.
148        let _guard = self.runtime.enter();
149        unsafe {
150            ManuallyDrop::drop(&mut self.inner);
151        }
152    }
153}
154
155#[matrix_sdk_ffi_macros::export]
156impl RehydratedDevice {
157    pub fn receive_events(&self, events: String) -> Result<(), crate::CryptoStoreError> {
158        let events: Vec<Raw<AnyToDeviceEvent>> = serde_json::from_str(&events)?;
159        self.runtime.block_on(self.inner.receive_events(events))?;
160
161        Ok(())
162    }
163}
164
165#[derive(uniffi::Object)]
166pub struct DehydratedDevice {
167    pub(crate) runtime: Handle,
168    pub(crate) inner: ManuallyDrop<InnerDehydratedDevice>,
169}
170
171impl Drop for DehydratedDevice {
172    fn drop(&mut self) {
173        // See the drop implementation for the `crate::OlmMachine` for an explanation.
174        let _guard = self.runtime.enter();
175        unsafe {
176            ManuallyDrop::drop(&mut self.inner);
177        }
178    }
179}
180
181#[matrix_sdk_ffi_macros::export]
182impl DehydratedDevice {
183    pub fn keys_for_upload(
184        &self,
185        device_display_name: String,
186        pickle_key: &DehydratedDeviceKey,
187    ) -> Result<UploadDehydratedDeviceRequest, DehydrationError> {
188        let key = InnerDehydratedDeviceKey::from_slice(&pickle_key.inner)?;
189
190        let request =
191            self.runtime.block_on(self.inner.keys_for_upload(device_display_name, &key))?;
192
193        Ok(request.into())
194    }
195}
196
197#[derive(Debug, uniffi::Record)]
198pub struct UploadDehydratedDeviceRequest {
199    /// The serialized JSON body of the request.
200    body: String,
201}
202
203impl From<dehydrated_device::put_dehydrated_device::unstable::Request>
204    for UploadDehydratedDeviceRequest
205{
206    fn from(value: dehydrated_device::put_dehydrated_device::unstable::Request) -> Self {
207        let body = json!({
208            "device_id": value.device_id,
209            "device_data": value.device_data,
210            "initial_device_display_name": value.initial_device_display_name,
211            "device_keys": value.device_keys,
212            "one_time_keys": value.one_time_keys,
213            "fallback_keys": value.fallback_keys,
214        });
215
216        let body = serde_json::to_string(&body)
217            .expect("We should be able to serialize the PUT dehydrated devices request body");
218
219        Self { body }
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use crate::{dehydrated_devices::DehydrationError, DehydratedDeviceKey};
226
227    #[test]
228    fn test_creating_dehydrated_key() {
229        let result = DehydratedDeviceKey::new();
230        assert!(result.is_ok());
231        let dehydrated_device_key = result.unwrap();
232        let base_64 = dehydrated_device_key.to_base64();
233        let inner_bytes = dehydrated_device_key.inner;
234
235        let copy = DehydratedDeviceKey::from_slice(&inner_bytes).unwrap();
236
237        assert_eq!(base_64, copy.to_base64());
238    }
239
240    #[test]
241    fn test_creating_dehydrated_key_failure() {
242        let bytes = [0u8; 24];
243
244        let pickle_key = DehydratedDeviceKey::from_slice(&bytes);
245
246        assert!(pickle_key.is_err());
247
248        match pickle_key {
249            Err(DehydrationError::PickleKeyLength(pickle_key_length)) => {
250                assert_eq!(bytes.len(), pickle_key_length);
251            }
252            _ => panic!("Should have failed!"),
253        }
254    }
255}