//! A set of providers that must be implemented by the app using the CSP E2EE protocol. usesuper::{
contact::{Contact, ContactUpdate},
message::{IncomingMessage, WebSessionResumeMessage},
}; usecrate::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)
)] pubenum 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)] pubenum ProfilePictureMediaType { /// JPEG (not JPEG XL)
Jpeg,
}
/// A profile picture. #[derive(Clone)] pubstruct ProfilePicture { /// Media type of the profile picture pub media_type: ProfilePictureMediaType, /// Bytes of the profile picture pub data: Vec<u8>,
}
/// Nonce storage interface. pubtrait 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(&mutself, 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. pubtrait 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(&mutself, message: WebSessionResumeMessage) -> Result<(), ProviderError>;
}
/// Settings storage interface. pubtrait 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. pubtrait 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(&mutself, 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(&mutself, 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. pubtrait 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(
&mutself,
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(&mutself, message: IncomingMessage) -> Result<(), ProviderError>;
}
/// In memory provider implementations for testing / the CLI #[cfg(any(test, feature = "cli"))] pubmod in_memory { use core::cell::RefCell; use std::{
collections::{HashMap, HashSet, hash_map},
rc::Rc,
};
type InMemoryDbContacts = HashMap<ThreemaId, (Contact, Option<ProfilePicture>)>;
/// In-memory contacts provider. pubstruct 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 ifself.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(&mutself, 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 ifself.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(&mutself, 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 letmut 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. pubstruct 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 inself.conversations.borrow().values() { if conversation.contains_key(&(sender_identity, id)) { return Ok(true);
}
}
Ok(false)
}
// 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(),
));
}
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(())
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.