matrix_sdk_crypto_ffi/
dehydrated_devices.rs

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