core_crypto_ffi/generic/
epoch_observer.rs

1use std::sync::Arc;
2
3use async_trait::async_trait;
4use core_crypto::prelude::{ConversationId, Obfuscated};
5
6use crate::{CoreCryptoError, generic::CoreCryptoResult};
7
8use super::CoreCrypto;
9
10#[derive(Debug, thiserror::Error, uniffi::Error)]
11#[uniffi(flat_error)]
12pub enum EpochChangedReportingError {
13    #[error("panic or otherwise unexpected error from foreign code")]
14    Ffi(#[from] uniffi::UnexpectedUniFFICallbackError),
15}
16
17/// An `EpochObserver` is notified whenever a conversation's epoch changes.
18#[uniffi::export(with_foreign)]
19#[async_trait]
20pub trait EpochObserver: Send + Sync {
21    /// This function will be called every time a conversation's epoch changes.
22    ///
23    /// The `epoch` parameter is the new epoch.
24    ///
25    /// <div class="warning">
26    /// This function must not block! Foreign implementors of this inteface can
27    /// spawn a task indirecting the notification, or (unblocking) send the notification
28    /// on some kind of channel, or anything else, as long as the operation completes
29    /// quickly.
30    /// </div>
31    ///
32    /// Though the signature includes an error type, that error is only present because
33    /// it is required by `uniffi` in order to handle panics. This function should suppress
34    /// and ignore internal errors instead of propagating them, to the maximum extent possible.
35    async fn epoch_changed(
36        &self,
37        conversation_id: ConversationId,
38        epoch: u64,
39    ) -> Result<(), EpochChangedReportingError>;
40}
41
42/// This shim bridges the public `EpochObserver` interface with the internal one defined by `core-crypto`.
43///
44/// This is slightly unfortunate, as it introduces an extra layer of indirection before a change notice can
45/// actually reach its foreign target. However, the orphan rule prevents us from just tying the two traits
46/// together directly, so this is the straightforward way to accomplish that.
47struct ObserverShim(Arc<dyn EpochObserver>);
48
49#[async_trait]
50impl core_crypto::mls::EpochObserver for ObserverShim {
51    async fn epoch_changed(&self, conversation_id: ConversationId, epoch: u64) {
52        if let Err(err) = self.0.epoch_changed(conversation_id.clone(), epoch).await {
53            // we don't _care_ if an error is thrown by the the notification function, per se,
54            // but this would probably be useful information for downstream debugging efforts
55            log::warn!(
56                conversation_id = Obfuscated::new(&conversation_id),
57                epoch,
58                err = log::kv::Value::from_dyn_error(&err);
59                "caught an error when attempting to notify the epoch observer of an epoch change"
60            );
61        }
62    }
63}
64
65#[uniffi::export]
66impl CoreCrypto {
67    /// Add an epoch observer to this client.
68    ///
69    /// This function should be called 0 or 1 times in a client's lifetime.
70    /// If called when an epoch observer already exists, this will return an error.
71    pub async fn register_epoch_observer(&self, epoch_observer: Arc<dyn EpochObserver>) -> CoreCryptoResult<()> {
72        let shim = Arc::new(ObserverShim(epoch_observer));
73        self.central
74            .register_epoch_observer(shim)
75            .await
76            .map_err(CoreCryptoError::generic())
77    }
78}