//! Common items needed in many places. use core::{
array::TryFromSliceError,
fmt,
num::ParseIntError,
ops::Deref,
str::{self, FromStr},
}; use std::env;
#[cfg(test)] use anyhow; use data_encoding::HEXLOWER; use libthreema_macros::Name; use rand::{self, Rng as _}; use serde::{Deserialize, Serialize}; use tracing::warn;
usecrate::{
crypto::{consts::U24, generic_array::GenericArray},
protobuf,
utils::{apply::Apply, debug::Name as _, protobuf::PaddedMessage as _, time::utc_now_ms},
};
/// Locale, i.e. `<language>/<country-code>` where: /// /// - `<language>` is an ISO 639-1:2002-ish language code /// - `<country-code>` is an ISO 3166-1-ish country code
locale: String,
/// Device model.
device_model: String,
/// OS version.
os_version: String,
},
/// iOS
Ios { /// Version string.
version: String,
/// Locale, i.e. `<language>/<country-code>` where: /// /// - `<language>` is an ISO 639-1:2002-ish language code /// - `<country-code>` is an ISO 3166-1-ish country code
locale: String,
/// Device model.
device_model: String,
/// OS version.
os_version: String,
},
/// Desktop 2.x
Desktop { /// Version string.
version: String,
/// Locale, i.e. `<language>/<country-code>` where: /// /// - `<language>` is an ISO 639-1:2002-ish language code /// - `<country-code>` is an ISO 3166-1-ish country code
locale: String,
/// Renderer name (e.g. `electron`).
renderer_name: String,
/// Renderer version.
renderer_version: String,
/// OS name (e.g. `linux`).
os_name: String,
/// OS architecture (e.g. `x64`).
os_architecture: String,
},
/// Construct a [`protobuf::d2d::DeviceInfo`] from a device label and the [`ClientInfo`]. /// /// The device label (e.g. "PC at Work") is recommended to not exceed 64 grapheme clusters. pub(crate) fn to_device_info(&self, label: Option<String>) -> protobuf::d2d::DeviceInfo { let (platform, platform_details, app_version) = matchself {
ClientInfo::Android {
version,
device_model,
..
} => (
protobuf::d2d::device_info::Platform::Android,
device_model.clone(),
version.clone(),
),
/// A valid Threema ID. #[expect(
clippy::unsafe_derive_deserialize,
reason = "False positive triggered by the unsafe block in as_str, \
see https://github.com/rust-lang/rust-clippy/issues/10349"
)] #[derive(Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize, Name)] #[serde(try_from = "&str", into = "String")] pubstruct ThreemaId([u8; Self::LENGTH]); impl ThreemaId { /// Byte length of a Threema ID. pubconst LENGTH: usize = 8;
/// Construct a predefined Threema ID. /// /// IMPORTANT: This skips the validation, so `identity` must be known to be valid! #[must_use] pubconstfn predefined(identity: [u8; Self::LENGTH]) -> Self {
ThreemaId(identity)
}
/// Byte representation of the Threema ID. #[inline] #[must_use] pubfn to_bytes(self) -> [u8; Self::LENGTH] { self.0
}
/// String representation of the Threema ID. #[inline] #[must_use] pubfn as_str(&self) -> &str { // SAFETY: This is safe because the creation of a `ThreemaId` requires that it is a valid // UTF-8 sequence. unsafe { str::from_utf8_unchecked(&self.0) }
}
/// Return whether this is a Gateway ID #[inline] #[must_use] pubfn is_gateway_id(self) -> bool { self.0[0] == b'*'
}
} impl fmt::Display for ThreemaId { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
} impl fmt::Debug for ThreemaId { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_tuple(Self::NAME)
.field(&self.to_string())
.finish()
}
} impl From<ThreemaId> for String { fn from(id: ThreemaId) -> Self {
id.as_str().to_owned()
}
} impl TryFrom<&[u8]> for ThreemaId { type Error = ThreemaIdError;
fn try_from(id: &[u8]) -> Result<Self, Self::Error> { let id = <[u8; Self::LENGTH]>::try_from(id).map_err(|_| ThreemaIdError::InvalidLength)?; if ![b'*'..=b'*', b'0'..=b'9', b'A'..=b'Z']
.iter()
.any(|range| range.contains(id.first().expect("id must be >= 8 bytes")))
{ return Err(ThreemaIdError::InvalidSymbols);
} if !id.get(1..8).expect("id must be >= 8 bytes").iter().all(|byte| {
[b'0'..=b'9', b'A'..=b'Z']
.iter()
.any(|range| range.contains(byte))
}) { return Err(ThreemaIdError::InvalidSymbols);
}
Ok(ThreemaId(id))
}
} impl TryFrom<&str> for ThreemaId { type Error = ThreemaIdError;
fn try_from(id: &str) -> Result<Self, Self::Error> { Self::try_from(id.as_bytes())
}
} impl FromStr for ThreemaId { type Err = ThreemaIdError;
/// A unique group identity. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pubstruct GroupIdentity { /// Group ID as chosen by the group's creator pub group_id: u64, /// Threema ID of the group's creator pub creator_identity: ThreemaId,
} impl TryFrom<&protobuf::common::GroupIdentity> for GroupIdentity { type Error = ThreemaIdError;
/// A specific conversation (aka _receiver_). #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pubenum ConversationId { /// A specific 1:1 contact.
Contact(ThreemaId), /// A specific distribution list.
DistributionList(u64), /// A specific group.
Group(GroupIdentity),
} impl TryFrom<&protobuf::d2d::conversation_id::Id> for ConversationId { type Error = ThreemaIdError;
/// CSP features supported by a device or available for a contact. /// /// IMPORTANT: The flags determine what a device/contact is capable of, not /// whether the settings allow for it. For example, group calls may be supported /// but ignored if disabled in the settings. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pubstruct FeatureMask(pub u64); #[rustfmt::skip] impl FeatureMask { /// No features available pubconst NONE: u64 = 0b_0000_0000_0000_0000; /// Supports voice messages. pubconst VOICE_MESSAGE_SUPPORT: u64 = 0b_0000_0000_0000_0001; /// Supports groups. pubconst GROUP_SUPPORT: u64 = 0b_0000_0000_0000_0010; /// Supports polls. pubconst POLL_SUPPORT: u64 = 0b_0000_0000_0000_0100; /// Supports file messages. pubconst FILE_MESSAGE_SUPPORT: u64 = 0b_0000_0000_0000_1000; /// Supports 1:1 audio calls. pubconst O2O_AUDIO_CALL_SUPPORT: u64 = 0b_0000_0000_0001_0000; /// Supports 1:1 video calls. pubconst O2O_VIDEO_CALL_SUPPORT: u64 = 0b_0000_0000_0010_0000; /// Supports forward security. pubconst FORWARD_SECURITY_SUPPORT: u64 = 0b_0000_0000_0100_0000; /// Supports group calls. pubconst GROUP_CALL_SUPPORT: u64 = 0b_0000_0000_1000_0000; /// Supports editing messages. pubconst EDIT_MESSAGE_SUPPORT: u64 = 0b_0000_0001_0000_0000; /// Supports deleting messages. pubconst DELETE_MESSAGE_SUPPORT: u64 = 0b_0000_0010_0000_0000;
}
/// A 24-byte nonce for use with XSalsa20Poly1305 or XChaCha20Poly1305. #[derive(Clone, Eq, PartialEq, Hash, Name)] pubstruct Nonce(pub [u8; Self::LENGTH]); impl Nonce { /// Byte length of a nonce. pubconst LENGTH: usize = 24;
/// Generate a random nonce #[must_use] pubfn random() -> Self { letmut nonce = Self([0_u8; Self::LENGTH]);
rand::thread_rng().fill(&mut nonce.0);
nonce
}
/// A message ID. /// /// May or may not be unique, depending on the context it is used for. #[derive(Clone, Copy, Eq, Hash, PartialEq, Name)] pubstruct MessageId(pub u64); impl MessageId { /// Byte length of a message ID. pubconst LENGTH: usize = 8;
/// Generate a random message ID. #[must_use] pubfn random() -> Self { Self(rand::thread_rng().r#gen())
}
/// Message flags which were/are transmitted to the server. #[derive(Clone, Copy, Default, Name, PartialEq, Eq)] pubstruct MessageFlags(pub u8); #[rustfmt::skip] impl MessageFlags { pub(crate) const LENGTH: usize = 1;
/// Whether a push should be sent by the chat server. Only meaningful for an outgoing message. pubconst SEND_PUSH_NOTIFICATION:u8 = 0b_0000_0001;
/// Whether the chat server should discard the message in case the receiver is not currently connected to /// the chat server. Only meaningful for an outgoing message. pubconst NO_SERVER_QUEUING: u8 = 0b_0000_0010;
/// Whether the message should not be acknowledged by the chat server (outgoing) or by the client /// (incoming). pubconst NO_SERVER_ACKNOWLEDGEMENT: u8 = 0b_0000_0100;
/// Instructs the chat server to only queue the message for a short period of time (currently 60 seconds). /// Only meaningful for an outgoing message. pubconst SHORT_LIVED_SERVER_QUEUING: u8 = 0b_0010_0000;
// Reserved: 0b_0100_0000
/// If present, overrides behaviour of messages that would normally trigger a delivery receipt of type /// _received_ or _read_. pubconst NO_DELIVERY_RECEIPTS: u8 = 0b_1000_0000;
} impl fmt::Debug for MessageFlags { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { let check_flag = |flag: u8, name: &'static str| -> Option<&'static str> { ifself.0 & flag != 0 { Some(name) } else { None }
};
// Keep this format in sync with [`MessageOverrides`]!
write!(
formatter, "{}({})", Self::NAME,
itertools::join(
[
check_flag(Self::SEND_PUSH_NOTIFICATION, "push"),
check_flag(Self::NO_SERVER_QUEUING, "no-queue"),
check_flag(Self::NO_SERVER_ACKNOWLEDGEMENT, "no-ack"),
check_flag(Self::SHORT_LIVED_SERVER_QUEUING, "short-lived"),
check_flag(Self::NO_DELIVERY_RECEIPTS, "no-receipts"),
]
.into_iter()
.flatten(), ", ",
),
)
}
}
/// Metadata associated to a message. #[derive(Debug, Clone)] pubstruct MessageMetadata { /// Unique message ID. Must match the message ID of the outer struct. pub message_id: MessageId, /// Unix-ish timestamp in milliseconds for when the message has been created. pub created_at: u64, /// Nickname of the sender at the time the message had been created. pub nickname: Delta<String>,
} impl MessageMetadata { /// Create for a new outgoing message. #[must_use] pubfn new_outgoing(nickname: Delta<String>, message_id: MessageId) -> Self { Self {
message_id,
created_at: utc_now_ms(),
nickname,
}
}
} impl From<protobuf::csp_e2e::MessageMetadata> for MessageMetadata { fn from(metadata: protobuf::csp_e2e::MessageMetadata) -> Self {
MessageMetadata {
message_id: MessageId(metadata.message_id),
created_at: metadata.created_at,
nickname: Delta::from_non_empty(metadata.nickname.map(|nickname| nickname.trim().to_owned())),
}
}
} impl From<MessageMetadata> for Vec<u8> { fn from(metadata: MessageMetadata) -> Self { let metadata = protobuf::csp_e2e::MessageMetadata { #[expect(deprecated, reason = "Will be filled by encode_to_vec_padded")]
padding: vec![],
message_id: metadata.message_id.0,
created_at: metadata.created_at,
nickname: metadata.nickname.into_non_empty(),
};
metadata.encode_to_vec_padded()
}
}
/// A blob ID. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pubstruct BlobId(pub [u8; Self::LENGTH]); impl BlobId { /// Byte length of a blob ID. pubconst LENGTH: usize = 16;
}
/// A delta update, where some value can be unchanged, updated or removed. #[derive(Debug, Clone, PartialEq)] pubenum Delta<T> { /// Value remains unchanged.
Unchanged, /// Value updated to inner `T`.
Update(T), /// Value removed.
Remove,
} impl<T> Delta<T> { /// Maps a `Delta<T>` to `Delta<U>` by applying a function to a contained value (if `Update`) or returns /// `Unchanged` or `Remove` respectively. pub(crate) fn map<U, F: FnOnce(T) -> U>(self, transform_fn: F) -> Delta<U> { matchself {
Delta::Unchanged => Delta::Unchanged,
Delta::Update(value) => Delta::Update(transform_fn(value)),
Delta::Remove => Delta::Remove,
}
}
/// Converts from [`&Delta<T>`](`Delta<T>`) to [`Delta<&T>`]. #[inline] pub(crate) constfn as_ref(&self) -> Delta<&T> { match &self { Self::Unchanged => Delta::Unchanged, Self::Update(value) => Delta::Update(value), Self::Remove => Delta::Remove,
}
}
/// Converts from [`Delta<T>`] (or [`&Delta<T>`](`Delta<T>`)) to `Delta<&T::Target>`. #[expect(dead_code, reason = "May use later")] #[inline] pub(crate) fn as_deref(&self) -> Delta<&T::Target> where
T: Deref,
{ self.as_ref().map(Deref::deref)
}
} impl<T> Apply<Delta<T>> for Option<T> { /// Apply the delta update to self. /// /// - [`Delta::Unchanged`] does nothing, /// - [`Delta::Update`] replaces any value in self with `Some(T)`, /// - [`Delta::Remove`] replaces any value in self with `None`. #[inline] fn apply(&mutself, value: Delta<T>) { match value {
Delta::Unchanged => {},
Delta::Update(value) => { let _ = self.insert(value);
},
Delta::Remove => { let _ = self.take();
},
}
}
} impl<T: Eq> Delta<T> { /// Re-evaluate the delta update against a `current` value, ensuring that it reflects actual /// changes and otherwise reports [`Delta::Unchanged`] if there is no change. pub(crate) fn changes(self, current: Option<&T>) -> Delta<T> { match (self, current) {
(Self::Update(update), None) => Delta::Update(update),
(Self::Update(update), Some(current)) => { if current == &update {
Delta::Unchanged
} else {
Delta::Update(update)
}
},
(Self::Remove, None) | (Self::Unchanged, _) => Delta::Unchanged,
(Self::Remove, Some(_)) => Delta::Remove,
}
}
} impl<T: Default + Eq> Delta<T> { /// Creates a [`Delta<T>`] from a source where the `T::Default` is semantically equivalent to /// [`Delta::Remove`]. pub(crate) fn from_non_empty(update: Option<T>) -> Self { match update {
Some(update) => { if update == T::default() { Self::Remove
} else { Self::Update(update)
}
},
None => Self::Unchanged,
}
}
/// Creates a [`Option<T>`] from the delta update where [`Delta::Remove`] is converted into `T::Default`. /// /// WARNING: A [`Delta::Update`] should never contain a `T::Default`, since the resulting [`Option<T>`] is /// recognized as a [`Delta::Remove`] when converted back via [`Delta<T>::from_non_empty`]. pub(crate) fn into_non_empty(self) -> Option<T> { matchself { Self::Unchanged => None, Self::Update(value) => { if value == T::default() {
warn!("Delta::Update contained T::Default");
}
Some(value)
}, Self::Remove => Some(T::default()),
}
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-06-22)
¤
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.