Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  provider.rs   Sprache: unbekannt

 
Spracherkennung für: .rs vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

//! A set of providers that must be implemented by the app using the CSP E2EE protocol.
use super::{
    contact::{Contact, ContactUpdate},
    message::{IncomingMessage, WebSessionResumeMessage},
};
use crate::common::{MessageId, Nonce, ThreemaId};

/// Provider errors.
#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
#[cfg_attr(
    feature = "wasm",
    derive(tsify::Tsify, serde::Deserialize),
    serde(
        tag = "type",
        content = "details",
        rename_all = "kebab-case",
        rename_all_fields = "camelCase"
    ),
    tsify(from_wasm_abi)
)]
pub enum ProviderError {
    /// The provided parameter was invalid. This may only be used for cases where the API contract has been
    /// violated and it indicates a bug in libthreema.
    #[error("Invalid parameter: {0}")]
    InvalidParameter(String),

    /// The app's internal state conflicts with the requested action. This can either indicate a bug in the
    /// app or a bug in libthreema.
    #[error("Invalid state: {0}")]
    InvalidState(String),

    /// A foreign function considered infallible returned an error.
    #[cfg(any(test, feature = "uniffi", feature = "cli"))]
    #[error("Infallible function failed in foreign code: {0}")]
    Foreign(String),
}
#[cfg(feature = "uniffi")]
impl From<uniffi::UnexpectedUniFFICallbackError> for ProviderError {
    fn from(error: uniffi::UnexpectedUniFFICallbackError) -> Self {
        Self::Foreign(error.reason)
    }
}

/// A set of supported media types for profile pictures.
#[derive(Clone, Copy)]
pub enum ProfilePictureMediaType {
    /// JPEG (not JPEG XL)
    Jpeg,
}

/// A profile picture.
#[derive(Clone)]
pub struct ProfilePicture {
    /// Media type of the profile picture
    pub media_type: ProfilePictureMediaType,
    /// Bytes of the profile picture
    pub data: Vec<u8>,
}

/// Nonce storage interface.
pub trait NonceStorage {
    /// 1. Lookup `nonce` in the associated context (do any necessary conversion to convert the bytes of
    ///    `nonce` for comparison).
    /// 2. Return whether `nonce` already exists in the storage.
    ///
    /// # Errors
    ///
    /// This function is considered infallible and should not return an error.
    fn has(&self, nonce: &Nonce) -> Result<bool, ProviderError>;

    /// 1. Transform each nonce bytes of `nonces` to the necessary format.
    /// 2. Store the resulting nonces in the storage, disregarding whether a nonce was already present before.
    ///
    /// # Errors
    ///
    /// This function is considered infallible and should not return an error.
    fn add_many(&mut self, nonces: Vec<Nonce>) -> Result<(), ProviderError>;
}

/// Makes dirty shortcuts to get things done faster initially. Contains the pure essence of tech debt. Expect
/// things to be changed or removed quickly.
pub trait ShortcutProvider {
    /// Handle a `web-session-resume` sent from `*3MAPUSH`.
    ///
    /// # Errors
    ///
    /// This function is considered infallible and should not return an error.
    fn handle_web_session_resume(&mut self, message: WebSessionResumeMessage) -> Result<(), ProviderError>;
}

/// Settings storage interface.
pub trait SettingsProvider {
    /// Return whether unknown identities should be blocked.
    ///
    /// # Errors
    ///
    /// This function is considered infallible and should not return an error.
    fn block_unknown_identities(&self) -> Result<bool, ProviderError>;
}

/// Contact storage interface.
pub trait ContactProvider {
    /// 1. If `identity` is called with the user's Threema ID, return [`ProviderError::InvalidParameter`].
    /// 2. Return whether `identity` is present in the identity block list.
    ///
    /// # Errors
    ///
    /// In case libthreema provides invalid parameters. Otherwise, this function is considered infallible and
    /// should not return an error.
    fn is_explicitly_blocked(&self, identity: ThreemaId) -> Result<bool, ProviderError>;

    /// 1. If `identity` is the user's Threema ID, return [`ProviderError::InvalidParameter`].
    /// 2. Return whether `identity` is part of an active group (i.e. a group that is not marked as _left_).
    ///
    /// # Errors
    ///
    /// In case libthreema provides invalid parameters. Otherwise, this function is considered infallible and
    /// should not return an error.
    fn is_member_of_active_group(&self, identity: ThreemaId) -> Result<bool, ProviderError>;

    /// 1. If `identity` is the user's Threema ID, return [`ProviderError::InvalidParameter`].
    /// 2. Return whether a contact for `identity` exists.¹
    ///
    /// ¹: This must consider contacts existing in storage with any acquaintance level, including _deleted_.
    ///
    /// # Errors
    ///
    /// In case libthreema provides invalid parameters. Otherwise, this function is considered infallible and
    /// should not return an error.
    fn has(&self, identity: ThreemaId) -> Result<bool, ProviderError>;

    /// 1. If any identity of `identities` is the user's Threema ID, return
    ///    [`ProviderError::InvalidParameter`].
    /// 2. Return the amount of `identities` a contact exists for.¹
    ///
    /// ¹: This must count contacts existing in storage with any acquaintance level, including _deleted_.
    ///
    /// # Errors
    ///
    /// In case libthreema provides invalid parameters. Otherwise, this function is considered infallible and
    /// should not return an error.
    fn has_many(&self, identities: &[ThreemaId]) -> Result<usize, ProviderError>;

    /// 1. If `identity` is the user's Threema ID, return [`ProviderError::InvalidParameter`].
    /// 2. Return the contact stored for `identity`, if any.¹
    ///
    /// ¹: This must return contacts existing in storage with any acquaintance level, including _deleted_.
    ///
    /// # Errors
    ///
    /// In case libthreema provides invalid parameters. Otherwise, this function is considered infallible and
    /// should not return an error.
    fn get(&self, identity: ThreemaId) -> Result<Option<Contact>, ProviderError>;

    /// 1. If any identity of `contacts` is the user's Threema ID, return [`ProviderError::InvalidParameter`].
    /// 2. If any contact for `contacts` exists, return [`ProviderError::InvalidState`].¹
    /// 3. Add each contact of `contacts`.
    ///
    /// ¹: This must consider contacts existing in storage with any acquaintance level, including _deleted_.
    ///
    /// # Errors
    ///
    /// In case libthreema provides invalid parameters or an invalid state. Otherwise, this function is
    /// considered infallible and should not return an error.
    fn add(&mut self, contacts: Vec<Contact>) -> Result<(), ProviderError>;

    /// 1. If `contact.identity` is the user's Threema ID, return [`ProviderError::InvalidParameter`].
    /// 2. If no contact for `identity` exists, return [`ProviderError::InvalidState`].¹
    /// 3. Update the `contact`.
    ///
    /// ¹: This must consider contacts existing in storage with any acquaintance level, including _deleted_.
    ///
    /// # Errors
    ///
    /// In case libthreema provides invalid parameters or an invalid state. Otherwise, this function is
    /// considered infallible and should not return an error.
    fn update(&mut self, contacts: Vec<ContactUpdate>) -> Result<(), ProviderError>;

    /// 1. If `identity` is the user's Threema ID, return [`ProviderError::InvalidParameter`].
    /// 2. Look up the contact associated to `identity` and let `contact` be the result.¹
    /// 3. If `contact` is not defined, return [`ProviderError::InvalidState`].
    /// 4. Return the contact-defined profile picture associated to `contact` (if any).
    ///
    /// ¹: This must consider contacts existing in storage with any acquaintance level, including
    /// _deleted_.
    ///
    /// # Errors
    ///
    /// In case libthreema provides invalid parameters. Otherwise, this function is considered infallible and
    /// should not return an error.
    fn get_contact_defined_profile_picture(
        &self,
        identity: ThreemaId,
    ) -> Result<Option<ProfilePicture>, ProviderError>;

    /// 1. If `identity` is the user's Threema ID, return [`ProviderError::InvalidParameter`].
    /// 2. Look up the contact associated to `identity` and let `contact` be the result.¹
    /// 3. If `contact` is not defined, return [`ProviderError::InvalidState`].
    /// 4. Return the user-defined profile picture associated to `contact` (if any).
    ///
    /// ¹: This must consider contacts existing in storage with any acquaintance level, including _deleted_.
    ///
    /// # Errors
    ///
    /// In case libthreema provides invalid parameters. Otherwise, this function is considered infallible and
    /// should not return an error.
    fn get_user_defined_profile_picture(
        &self,
        identity: ThreemaId,
    ) -> Result<Option<ProfilePicture>, ProviderError>;
}

/// Conversation storage interface.
pub trait ConversationProvider {
    /// TODO(LIB-16): Known problematic because checking the message ID early and bailing may prevent
    /// reflection.
    ///
    /// Return whether a message with message `id` from `sender_identity` has been marked as used (i.e. points
    /// to an existing message or was explicitly marked used at some point).
    ///
    /// # Errors
    ///
    /// This function is considered infallible and should not return an error.
    fn message_is_marked_used(
        &self,
        sender_identity: ThreemaId,
        id: MessageId,
    ) -> Result<bool, ProviderError>;

    /// Set the typing indicator for a specific 1:1 conversation.
    ///
    /// 1. If the referred contact does not exist, return [`ProviderError::InvalidState`].¹
    /// 2. If `is_typing` is `true`, start a timer to display that the sender is typing in the associated
    ///    conversation for the next 15s.
    /// 3. If `is_typing` is `false`, cancel any running timer displaying that the sender is typing in the
    ///    associated conversation.
    ///
    /// ¹: This must consider contacts existing in storage with any acquaintance level, including _deleted_.
    ///
    /// # Errors
    ///
    /// In case libthreema provides invalid parameters or an invalid state. Otherwise, this function is
    /// considered infallible and should not return an error.
    fn set_typing_indicator(
        &mut self,
        sender_identity: ThreemaId,
        is_typing: bool,
    ) -> Result<(), ProviderError>;

    /// Add a new visible incoming message or update an existing message's state from an incoming message for
    /// a specific conversation.
    ///
    /// 1. If `message.body` refers to a 1:1 contact conversation:
    ///    1. If the referred contact does not exist, return [`ProviderError::InvalidState`].¹
    ///    2. If the referred contact does not have acquaintance level _direct_, log a notice, discard
    ///       `message` and return.²
    /// 2. If `message.body` refers to a group conversation and the referred group does not exist, return
    ///    [`ProviderError::InvalidState`].
    /// 3. If the `message.sender_identity`, `message.id` tuple is already present in the referred
    ///    conversation (i.e. the message has already been added at some point), log a notice, discard
    ///    `message` and return.³
    /// 4. Match over the `message.body`:
    ///    1. If `message` is a 1:1 text message, add it to the conversation.
    ///    2. (Unreachable)
    ///
    /// ¹: This must consider contacts existing in storage with any acquaintance level, including _deleted_.
    ///
    /// ²: Not all messages update the acquaintance level, e.g. typing indicators. But considering that
    /// contacts with an acquaintance level other than _direct_ are invisible and by definition should not
    /// have an associated conversation with visible messages, there is no reason to process such messages
    /// further.
    ///
    /// ³: Messages are not acknowledged from the server until they are fully processed, so this can happen
    /// even under normal conditions.
    ///
    /// # Errors
    ///
    /// In case libthreema provides invalid parameters or an invalid state. Otherwise, this function is
    /// considered infallible and should not return an error.
    fn add_or_update_incoming_message(&mut self, message: IncomingMessage) -> Result<(), ProviderError>;
}

/// In memory provider implementations for testing / the CLI
#[cfg(any(test, feature = "cli"))]
pub mod in_memory {
    use core::cell::RefCell;
    use std::{
        collections::{HashMap, HashSet, hash_map},
        rc::Rc,
    };

    use tracing::{info, warn};

    use super::{
        ContactProvider, ConversationProvider, NonceStorage, ProfilePicture, ProviderError, SettingsProvider,
        ShortcutProvider,
    };
    use crate::{
        common::{ConversationId, GroupIdentity, MessageId, Nonce, ThreemaId},
        model::{
            contact::{Contact, ContactUpdate},
            message::{IncomingMessage, IncomingMessageBody, WebSessionResumeMessage},
        },
        utils::apply::TryApply as _,
    };

    /// Default shortcut provider, applying reasonable shortcut defaults.
    pub struct DefaultShortcutProvider;
    impl ShortcutProvider for DefaultShortcutProvider {
        fn handle_web_session_resume(
            &mut self,
            _message: WebSessionResumeMessage,
        ) -> Result<(), ProviderError> {
            warn!("Discarding web-session-resume");
            Ok(())
        }
    }

    /// In-memory settings.
    #[derive(Default)]
    pub struct InMemoryDbSettings {
        /// Whether unknown identities should be blocked.
        pub block_unknown_identities: bool,
    }

    /// In-memory nonce storage provider.
    pub struct InMemoryDbNonceProvider(Rc<RefCell<HashSet<Nonce>>>);
    impl NonceStorage for InMemoryDbNonceProvider {
        fn has(&self, nonce: &Nonce) -> Result<bool, ProviderError> {
            Ok(self.0.borrow().contains(nonce))
        }

        fn add_many(&mut self, nonces: Vec<Nonce>) -> Result<(), ProviderError> {
            self.0.borrow_mut().extend(nonces);
            Ok(())
        }
    }

    /// In-memory settings provider.
    pub struct InMemoryDbSettingsProvider(Rc<RefCell<InMemoryDbSettings>>);
    impl SettingsProvider for InMemoryDbSettingsProvider {
        fn block_unknown_identities(&self) -> Result<bool, ProviderError> {
            Ok(self.0.borrow().block_unknown_identities)
        }
    }

    type InMemoryDbContacts = HashMap<ThreemaId, (Contact, Option<ProfilePicture>)>;

    /// In-memory contacts provider.
    pub struct InMemoryDbContactProvider {
        pub(crate) user_identity: ThreemaId,
        pub(crate) blocked_identities: Rc<RefCell<HashSet<ThreemaId>>>,
        pub(crate) contacts: Rc<RefCell<InMemoryDbContacts>>,
        pub(crate) _groups: Rc<RefCell<HashMap<GroupIdentity, bool>>>, // TODO(LIB-16): Implement groups
    }
    impl ContactProvider for InMemoryDbContactProvider {
        fn is_explicitly_blocked(&self, identity: ThreemaId) -> Result<bool, ProviderError> {
            // Bail if identity is the user's identity
            if identity == self.user_identity {
                return Err(ProviderError::InvalidParameter(
                    "Unexpected encounter of user's identity in contact provider".to_owned(),
                ));
            }

            // Check if the identity is explicitly blocked
            Ok(self.blocked_identities.borrow().contains(&identity))
        }

        fn is_member_of_active_group(&self, identity: ThreemaId) -> Result<bool, ProviderError> {
            // Bail if identity is the user's identity
            if identity == self.user_identity {
                return Err(ProviderError::InvalidParameter(
                    "Unexpected encounter of user's identity in contact provider".to_owned(),
                ));
            }

            // Check if the identity is a member of an active group
            // TODO(LIB-16): Implement groups
            Ok(false)
        }

        fn has(&self, identity: ThreemaId) -> Result<bool, ProviderError> {
            // Bail if identity is the user's identity
            if identity == self.user_identity {
                return Err(ProviderError::InvalidParameter(
                    "Unexpected encounter of user's identity in contact provider".to_owned(),
                ));
            }

            // Check if a contact for identity exists
            Ok(self.contacts.borrow().contains_key(&identity))
        }

        fn has_many(&self, identities: &[ThreemaId]) -> Result<usize, ProviderError> {
            identities.iter().try_fold(0_usize, |n_identities, identity| {
                // Bail if identity is the user's identity
                if *identity == self.user_identity {
                    return Err(ProviderError::InvalidParameter(
                        "Unexpected encounter of user's identity in contact provider".to_owned(),
                    ));
                }

                // Increase count if a contact for identity exists
                if self.contacts.borrow().contains_key(identity) {
                    Ok(n_identities.saturating_add(1))
                } else {
                    Ok(n_identities)
                }
            })
        }

        fn get(&self, identity: ThreemaId) -> Result<Option<Contact>, ProviderError> {
            // Bail if identity is the user's identity
            if identity == self.user_identity {
                return Err(ProviderError::InvalidParameter(
                    "Unexpected encounter of user's identity in contact provider".to_owned(),
                ));
            }

            // Get a snapshot of the contact (if it exists)
            let contacts = self.contacts.borrow();
            let Some((contact, _)) = contacts.get(&identity) else {
                return Ok(None);
            };
            Ok(Some(contact.clone()))
        }

        fn add(&mut self, contacts: Vec<Contact>) -> Result<(), ProviderError> {
            for contact in contacts {
                // Bail if the contact's identity is the user's identity or the contact already exists
                if self.has(contact.identity)? {
                    return Err(ProviderError::InvalidState(
                        "Contact to be added already exists".to_owned(),
                    ));
                }

                // Add the contact
                let _ = self
                    .contacts
                    .borrow_mut()
                    .insert(contact.identity, (contact, None));
            }

            Ok(())
        }

        fn update(&mut self, contacts: Vec<ContactUpdate>) -> Result<(), ProviderError> {
            for update in contacts {
                // Bail if identity is the user's identity
                if update.identity == self.user_identity {
                    return Err(ProviderError::InvalidParameter(
                        "Unexpected encounter of user's identity in contact provider".to_owned(),
                    ));
                }

                // Ensure the contact exists
                let mut contacts = self.contacts.borrow_mut();
                let Some((contact, _)) = contacts.get_mut(&update.identity) else {
                    return Err(ProviderError::InvalidState(
                        "Contact to be updated does not exist".to_owned(),
                    ));
                };

                // Update the contact
                contact
                    .try_apply(update)
                    .map_err(|error| ProviderError::Foreign(error.to_string()))?;
            }

            Ok(())
        }

        fn get_contact_defined_profile_picture(
            &self,
            identity: ThreemaId,
        ) -> Result<Option<ProfilePicture>, ProviderError> {
            // Bail if identity is the user's identity
            if identity == self.user_identity {
                return Err(ProviderError::InvalidParameter(
                    "Unexpected encounter of user's identity in contact provider".to_owned(),
                ));
            }

            // Ensure the contact exists
            let contacts = self.contacts.borrow();
            let Some((_, profile_picture)) = contacts.get(&identity) else {
                return Err(ProviderError::InvalidState(
                    "Contact to retrieve the contact-defined profile picture from does not exist".to_owned(),
                ));
            };

            // Hand out the contact-defined profile picture
            Ok(profile_picture.clone())
        }

        fn get_user_defined_profile_picture(
            &self,
            identity: ThreemaId,
        ) -> Result<Option<ProfilePicture>, ProviderError> {
            // Bail if identity is the user's identity
            if identity == self.user_identity {
                return Err(ProviderError::InvalidParameter(
                    "Unexpected encounter of user's identity in contact provider".to_owned(),
                ));
            }

            // Ensure the contact exists
            if !self.contacts.borrow().contains_key(&identity) {
                return Err(ProviderError::InvalidState(
                    "Contact to retrieve the user-defined profile picture from does not exist".to_owned(),
                ));
            }

            // This implementation does not have a user-defined profile picture
            Ok(None)
        }
    }

    type InMemoryDbConversations = HashMap<ConversationId, HashMap<(ThreemaId, MessageId), IncomingMessage>>;

    /// In-memory message provider.
    pub struct InMemoryDbMessageProvider {
        pub(crate) contacts: Rc<RefCell<InMemoryDbContacts>>,
        pub(crate) conversations: Rc<RefCell<InMemoryDbConversations>>,
    }
    impl ConversationProvider for InMemoryDbMessageProvider {
        fn message_is_marked_used(
            &self,
            sender_identity: ThreemaId,
            id: MessageId,
        ) -> Result<bool, ProviderError> {
            // TODO(LIB-16): This is pretty expensive. We'll have to figure this out.
            for conversation in self.conversations.borrow().values() {
                if conversation.contains_key(&(sender_identity, id)) {
                    return Ok(true);
                }
            }
            Ok(false)
        }

        fn set_typing_indicator(
            &mut self,
            _sender_identity: ThreemaId,
            _is_typing: bool,
        ) -> Result<(), ProviderError> {
            // Ignore
            Ok(())
        }

        fn add_or_update_incoming_message(&mut self, message: IncomingMessage) -> Result<(), ProviderError> {
            let mut conversations = self.conversations.borrow_mut();

            // Get or create the conversation
            let conversation = conversations
                .entry(match &message.body {
                    IncomingMessageBody::Contact { .. } => {
                        // Ensure the referred contact exists
                        if !self.contacts.borrow().contains_key(&message.sender_identity) {
                            return Err(ProviderError::InvalidState(
                                "Contact the incoming message refers to does not exist".to_owned(),
                            ));
                        }

                        ConversationId::Contact(message.sender_identity)
                    },

                    IncomingMessageBody::Group(body) => {
                        // TODO(LIB-16): Ensure the referred group exists
                        ConversationId::Group(body.group_identity)
                    },
                })
                .or_default();

            // Add, if possible
            match conversation.entry((message.sender_identity, message.id)) {
                hash_map::Entry::Occupied(_) => {
                    // A message from the sender with the same message ID already existss
                    warn!(
                        sender_identity = ?message.sender_identity,
                        nessage_id = ?message.id,
                        "Discarding message that already exists"
                    );
                },
                hash_map::Entry::Vacant(vacant_entry) => {
                    info!(?message, "Added message");
                    let _ = vacant_entry.insert(message);
                },
            }
            Ok(())
        }
    }

    /// Initializer for an [`InMemoryDb`].
    pub struct InMemoryDbInit {
        /// The user's identity.
        pub user_identity: ThreemaId,
        /// Initial settings.
        pub settings: InMemoryDbSettings,
        /// Initial blocked identities.
        pub blocked_identities: Vec<ThreemaId>,
        /// Initial contacts.
        pub contacts: Vec<Contact>,
    }

    /// In-memory database.
    pub struct InMemoryDb {
        pub(crate) user_identity: ThreemaId,
        pub(crate) csp_e2e_nonce_storage: Rc<RefCell<HashSet<Nonce>>>,
        pub(crate) d2x_nonce_storage: Rc<RefCell<HashSet<Nonce>>>,
        pub(crate) settings: Rc<RefCell<InMemoryDbSettings>>,
        pub(crate) blocked_identities: Rc<RefCell<HashSet<ThreemaId>>>,
        pub(crate) contacts: Rc<RefCell<InMemoryDbContacts>>,
        pub(crate) groups: Rc<RefCell<HashMap<GroupIdentity, bool>>>, // TODO(LIB-16): Implement groups
        pub(crate) conversations: Rc<RefCell<InMemoryDbConversations>>,
    }

    impl From<InMemoryDbInit> for InMemoryDb {
        fn from(init: InMemoryDbInit) -> Self {
            Self {
                user_identity: init.user_identity,
                csp_e2e_nonce_storage: Rc::new(RefCell::new(HashSet::new())),
                d2x_nonce_storage: Rc::new(RefCell::new(HashSet::new())),
                settings: Rc::new(RefCell::new(init.settings)),
                blocked_identities: Rc::new(RefCell::new(init.blocked_identities.into_iter().collect())),
                contacts: Rc::new(RefCell::new(
                    init.contacts
                        .into_iter()
                        .map(|contact| (contact.identity, (contact, None)))
                        .collect(),
                )),
                groups: Rc::new(RefCell::new(HashMap::new())), // TODO(LIB-16): Implement groups
                conversations: Rc::new(RefCell::new(HashMap::new())),
            }
        }
    }

    impl InMemoryDb {
        /// CSP nonce provider.
        #[must_use]
        pub fn csp_e2e_nonce_provider(&mut self) -> InMemoryDbNonceProvider {
            InMemoryDbNonceProvider(Rc::clone(&self.csp_e2e_nonce_storage))
        }

        /// D2D nonce provider.
        #[must_use]
        pub fn d2d_nonce_provider(&mut self) -> InMemoryDbNonceProvider {
            InMemoryDbNonceProvider(Rc::clone(&self.d2x_nonce_storage))
        }

        /// Settings provider.
        #[must_use]
        pub fn settings_provider(&self) -> InMemoryDbSettingsProvider {
            InMemoryDbSettingsProvider(Rc::clone(&self.settings))
        }

        /// Contact provider.
        #[must_use]
        pub fn contact_provider(&mut self) -> InMemoryDbContactProvider {
            InMemoryDbContactProvider {
                user_identity: self.user_identity,
                blocked_identities: Rc::clone(&self.blocked_identities),
                contacts: Rc::clone(&self.contacts),
                _groups: Rc::clone(&self.groups),
            }
        }

        /// Message provider.
        #[must_use]
        pub fn message_provider(&mut self) -> InMemoryDbMessageProvider {
            InMemoryDbMessageProvider {
                contacts: Rc::clone(&self.contacts),
                conversations: Rc::clone(&self.conversations),
            }
        }
    }
}

[Dauer der Verarbeitung: 0.31 Sekunden, vorverarbeitet 2026-04-27]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge