/// Lifetime of the message on the server. #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) enum MessageLifetime { /// The message is kept indefinitely until received.
Indefinite,
/// The message is kept for a short amount of time until it is silently dropped (usually 30s). #[expect(dead_code, reason = "Will use later")]
Brief,
/// The message is only transmitted if the receiver is currently online. /// /// This is a combination of the _no server queuing_ and _no server acknowledgement_ flags /// which are only used in conjunction.
Ephemeral,
}
/// Message properties associated to a message type. #[expect(
clippy::struct_excessive_bools,
reason = "The one place where it's reasonable"
)] #[derive(Debug, Clone, Name)] #[cfg_attr(test, derive(PartialEq))] pub(crate) struct MessageProperties { /// Whether the message should be/has been pushed. pub(crate) push: bool,
/// Lifetime of the message on the server. pub(crate) lifetime: MessageLifetime,
/// Whether the message requires user profile distribution. pub(crate) user_profile_distribution: bool,
/// Whether the message requires to create a _direct_ contact upon reception. pub(crate) requires_direct_contact: bool,
/// Whether the message requires replay protection (by storing the nonce). pub(crate) replay_protection: bool,
/// Whether the message should be reflected in case it is incoming. pub(crate) reflect_incoming: bool,
/// Whether the message should be reflected in case it is outgoing. #[expect(dead_code, reason = "Will use later")] pub(crate) reflect_outgoing: bool,
/// Whether an update that the message has been _sent_ should be reflected (in case it is /// outgoing). #[expect(dead_code, reason = "Will use later")] pub(crate) reflect_sent_update: bool,
/// Whether delivery receipts may be sent in response to this message. pub(crate) delivery_receipts: bool,
} impl Apply<MessageProperties> for MessageFlags { fn apply(&mutself, value: MessageProperties) { if value.push { self.0 |= MessageFlags::SEND_PUSH_NOTIFICATION;
} match value.lifetime {
MessageLifetime::Indefinite => {},
MessageLifetime::Brief => { self.0 |= MessageFlags::SHORT_LIVED_SERVER_QUEUING;
},
MessageLifetime::Ephemeral => { self.0 |= MessageFlags::NO_SERVER_QUEUING | MessageFlags::NO_SERVER_ACKNOWLEDGEMENT;
},
}
}
}
/// Message properties associated to a group message type. /// /// This is a reduced version of [`MessageProperties`]. #[expect(
clippy::struct_excessive_bools,
reason = "The one place where it's reasonable"
)] struct GroupMessageProperties {
push: bool,
lifetime: MessageLifetime,
user_profile_distribution: bool,
replay_protection: bool,
reflect_incoming: bool,
reflect_outgoing: bool,
reflect_sent_update: bool,
} impl GroupMessageProperties { constfn into_message_properties(self) -> MessageProperties {
MessageProperties {
push: self.push,
lifetime: self.lifetime,
user_profile_distribution: self.user_profile_distribution,
requires_direct_contact: false,
replay_protection: self.replay_protection,
reflect_incoming: self.reflect_incoming,
reflect_outgoing: self.reflect_outgoing,
reflect_sent_update: self.reflect_sent_update,
delivery_receipts: false,
}
}
}
/// Message behaviour overrides that need to be stored. #[derive(Clone, Copy, Default, Name)] #[cfg_attr(test, derive(PartialEq))] pubstruct MessageOverrides { /// Whether delivery receipts should be omitted. If `true`, this overrides the default behaviour. pub disable_delivery_receipts: bool,
} impl Apply<MessageOverrides> for MessageProperties { fn apply(&mutself, value: MessageOverrides) { if value.disable_delivery_receipts { self.delivery_receipts = false;
}
}
} impl Apply<MessageOverrides> for MessageFlags { fn apply(&mutself, value: MessageOverrides) { if value.disable_delivery_receipts { self.0 |= MessageFlags::NO_DELIVERY_RECEIPTS;
}
}
} impl fmt::Debug for MessageOverrides { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { // Keep this format in sync with [`MessageFlags`]!
write!(
formatter, "{}({})", Self::NAME,
itertools::join(
[self.disable_delivery_receipts.then_some("no-receipts")]
.into_iter()
.flatten(), ", ",
),
)
}
} impl From<MessageFlags> for MessageOverrides { fn from(flags: MessageFlags) -> Self { Self {
disable_delivery_receipts: flags.0 & MessageFlags::NO_DELIVERY_RECEIPTS != 0,
}
}
}
fn decode(reader: &mutimpl ByteReader) -> Result<Self, IncomingMessageError> { let raw = str::from_utf8(reader.read_remaining())?; letmut lines = raw.split('\n');
// Extract latitude, longitude and optional accuracy from this beautifully formatted soup of strings let (latitude, longitude, accuracy_m) = { let decode_next_f64 = |name: &'static str, values: &mut Split<'_, char>| {
f64::from_str(values.next().ok_or_else(|| {
IncomingMessageError::InvalidMessage(format!("Missing {name} in location message"))
})?)
.map_err(|error| {
IncomingMessageError::InvalidMessage(format!( "Invalid {name} in location message: {error}"
))
})
};
letmut values = lines
.next()
.expect("lines must containt at least one line")
.split(','); let latitude = decode_next_f64("latitude", &mut values)?; let longitude = decode_next_f64("longitude", &mut values)?; let accuracy_m = decode_next_f64("accuracy", &mut values).ok();
(latitude, longitude, accuracy_m)
};
// Extract name and address, which could have been stirred either way /chef-kiss let (name, address) = { let line1 = lines.next(); let line2 = lines.next(); match (line1, line2) {
(None, _) => (None, None),
(Some(address), None) => (None, Some(address)),
(Some(name), Some(address)) => (Some(name.to_owned()), Some(address)),
}
};
// Unescape all the escaped new-lines from the address to remove some of the spicyness let address = address.map(|address| address.replace("\\n", "\n"));
// Done serving this exquisite menu
Ok(LocationMessage {
latitude,
longitude,
accuracy_m,
name,
address,
})
}
// Write name and address (if any) match (
&self.name, self.address.as_ref().map(|address| address.replace('\n', "\\n")),
) {
(None, None) => {},
(None, Some(address)) => {
writer.write("\n".as_bytes())?;
writer.write(address.as_bytes())?;
},
(Some(name), None) => {
warn!(
name, "Discarding name in location message without an associated address"
);
},
(Some(name), Some(address)) => {
writer.write("\n".as_bytes())?;
writer.write(name.as_bytes())?;
writer.write("\n".as_bytes())?;
writer.write(address.as_bytes())?;
},
}
Ok(())
}
} impl fmt::Debug for LocationMessage { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(Self::NAME)
}
}
/// The type of delivery receipt. #[derive(Debug, Clone, Copy, strum::FromRepr)] #[repr(u8)] #[cfg_attr(test, derive(PartialEq))] pubenum DeliveryReceiptType { /// The referred messages have been received (i.e. it was delivered to the recipient).
Received = 0x01, /// The referred messages have been read.
Read = 0x02,
}
/// The type of legacy reaction. #[derive(Debug, Clone, Copy, strum::FromRepr)] #[repr(u8)] #[cfg_attr(test, derive(PartialEq))] pubenum LegacyReactionType { /// The user reacted with _acknowledge_ (i.e. _thumbs up_) to the messages.
Acknowledge = 0x03, /// The user reacted with _decline_ (i.e. _thumbs down_) to the messages.
Decline = 0x04,
}
/// Mapper for deciding between decoding into/encoding from [`DeliveryReceiptMessage`] or /// [`LegacyReactionMessage`]. enum DeliveryReceiptMapper {
DeliveryReceipt(DeliveryReceiptType),
LegacyReaction(LegacyReactionType),
} impl DeliveryReceiptMapper { fn decode_type(reader: &mutimpl ByteReader) -> Result<DeliveryReceiptMapper, IncomingMessageError> { let r#type = reader.read_u8()?; let r#type = match DeliveryReceiptType::from_repr(r#type) {
Some(r#type) => DeliveryReceiptMapper::DeliveryReceipt(r#type),
None => DeliveryReceiptMapper::LegacyReaction(LegacyReactionType::from_repr(r#type).ok_or(
IncomingMessageError::InvalidMessage(format!("Unknown delivery receipt type: 0x{type:02x}")),
)?),
};
Ok(r#type)
}
/// A delivery receipt. /// /// Note: This only covers real delivery receipts, not legacy reactions. These are always mapped to and from /// [`LegacyReactionMessage`]. #[derive(Clone, Educe)] #[educe(Debug)] #[cfg_attr(test, derive(PartialEq))] pubstruct DeliveryReceiptMessage { /// Type of delivery receipt. pub receipt_type: DeliveryReceiptType,
/// Message ids of referred messages the receipt type should be applied to. pub message_ids: Vec<MessageId>,
} impl DeliveryReceiptMessage { const CONTACT_EXEMPT_FROM_BLOCKING: bool = false; const CONTACT_PROPERTIES: MessageProperties = MessageProperties {
push: false,
lifetime: MessageLifetime::Indefinite,
user_profile_distribution: false,
requires_direct_contact: false,
replay_protection: false,
reflect_incoming: true,
reflect_outgoing: true,
reflect_sent_update: false,
delivery_receipts: false,
}; const CONTACT_TYPE: CspE2eMessageType = CspE2eMessageType::DeliveryReceipt;
/// A legacy reaction message. /// /// Note: Delivery receipts containing legacy reactions are always mapped to and from this message internally. #[derive(Clone, Educe)] #[educe(Debug)] #[cfg_attr(test, derive(PartialEq))] pubstruct LegacyReactionMessage { /// Type of legacy reaction. pub reaction_type: LegacyReactionType,
/// Message body of a 1:1 message. #[derive(Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] pubenum ContactMessageBody { /// See [`TextMessage`].
Text(TextMessage),
/// See [`LocationMessage`].
Location(LocationMessage),
/// See [`DeliveryReceiptMessage`].
DeliveryReceipt(DeliveryReceiptMessage),
/// See [`LegacyReactionMessage`].
LegacyReaction(LegacyReactionMessage),
/// Determine whether the 1:1 message is exempt from blocking. constfn is_exempt_from_blocking(&self) -> bool { match &self {
ContactMessageBody::Text(_) => TextMessage::CONTACT_EXEMPT_FROM_BLOCKING,
ContactMessageBody::Location(_) => LocationMessage::CONTACT_EXEMPT_FROM_BLOCKING,
ContactMessageBody::DeliveryReceipt(_) => DeliveryReceiptMessage::CONTACT_EXEMPT_FROM_BLOCKING,
ContactMessageBody::LegacyReaction(_) => LegacyReactionMessage::CONTACT_EXEMPT_FROM_BLOCKING,
ContactMessageBody::WebSessionResume(_) => WebSessionResumeMessage::CONTACT_EXEMPT_FROM_BLOCKING,
}
}
}
/// Message body of a distribution list message. #[derive(Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] pubenum DistributionListMessageBody { /// See [`TextMessage`].
Text(TextMessage),
/// See [`LocationMessage`].
Location(LocationMessage), // TODO(LIB-57): Complete
} impl DistributionListMessageBody { /// Get the distribution list message's type. /// /// Note: These are always just 1:1 message types. constfn message_type(&self) -> CspE2eMessageType { match &self {
DistributionListMessageBody::Text(_) => TextMessage::CONTACT_TYPE,
DistributionListMessageBody::Location(_) => LocationMessage::CONTACT_TYPE,
}
}
/// Get the distribution list message's properties. /// /// Note: The properties are identical to the 1:1 message's properties. constfn properties(&self) -> MessageProperties { match &self {
DistributionListMessageBody::Text(_) => TextMessage::CONTACT_PROPERTIES,
DistributionListMessageBody::Location(_) => LocationMessage::CONTACT_PROPERTIES,
}
}
/// Determine whether the distribution list message is exempt from blocking. /// /// Note: This behaves identical to the respective 1:1 message. constfn is_exempt_from_blocking(&self) -> bool { match &self {
DistributionListMessageBody::Text(_) => TextMessage::CONTACT_EXEMPT_FROM_BLOCKING,
DistributionListMessageBody::Location(_) => LocationMessage::CONTACT_EXEMPT_FROM_BLOCKING,
}
}
}
/// Message body of a group message. #[derive(Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] pubenum GroupMessageBody { /// See [`TextMessage`].
Text(TextMessage),
/// See [`LocationMessage`].
Location(LocationMessage),
/// See [`LegacyReactionMessage`].
LegacyReaction(LegacyReactionMessage), // TODO(LIB-53): Complete
} impl GroupMessageBody { /// Get the group message's type. constfn message_type(&self) -> CspE2eMessageType { match &self {
GroupMessageBody::Text(_) => TextMessage::GROUP_TYPE,
GroupMessageBody::Location(_) => LocationMessage::GROUP_TYPE,
GroupMessageBody::LegacyReaction(_) => LegacyReactionMessage::GROUP_TYPE,
}
}
/// Get the group message's properties. constfn properties(&self) -> GroupMessageProperties { match &self {
GroupMessageBody::Text(_) => TextMessage::GROUP_PROPERTIES,
GroupMessageBody::Location(_) => LocationMessage::GROUP_PROPERTIES,
GroupMessageBody::LegacyReaction(_) => LegacyReactionMessage::GROUP_PROPERTIES,
}
}
/// Determine whether the group message is exempt from blocking. #[inline] fn is_exempt_from_blocking(&self) -> bool { match &self {
GroupMessageBody::Text(_) => TextMessage::GROUP_EXEMPT_FROM_BLOCKING,
GroupMessageBody::Location(_) => LocationMessage::GROUP_EXEMPT_FROM_BLOCKING,
GroupMessageBody::LegacyReaction(_) => LegacyReactionMessage::GROUP_EXEMPT_FROM_BLOCKING,
}
}
}
/// An incoming group message. #[derive(Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] pubstruct IncomingGroupMessageBody { /// The specific group identity. pub group_identity: GroupIdentity,
#[inline] fn decode_group_member_header(
reader: &mutimpl ByteReader,
) -> Result<GroupIdentity, IncomingMessageError> { let creator_identity = ThreemaId::try_from(reader.read_fixed::<{ ThreemaId::LENGTH }>()?.as_slice())
.map_err(|error| {
IncomingMessageError::InvalidMessage(format!( "Invalid group creator identity in group message header: {error}"
))
})?; let group_id = reader.read_u64_le()?;
Ok(GroupIdentity {
group_id,
creator_identity,
})
}
}
/// Incoming message bodies for their respective conversations. #[derive(Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] pubenum IncomingMessageBody { /// An incoming 1:1 message from a contact.
Contact(ContactMessageBody),
/// An incoming group message.
Group(IncomingGroupMessageBody),
} impl IncomingMessageBody { /// Try to decode the incoming message's body from the protocol type and the raw data. pub(crate) fn decode(
message_type: CspE2eMessageType,
message_data: &[u8],
) -> Result<Self, IncomingMessageError> { letmut reader = SliceByteReader::new(message_data);
// Decode message body let message = match message_type {
CspE2eMessageType::Text => { Self::Contact(ContactMessageBody::Text(TextMessage::decode(&mut reader)?))
},
CspE2eMessageType::GroupText => { let group_identity = IncomingGroupMessageBody::decode_group_member_header(&e='color:red'>mut reader)?; let body = GroupMessageBody::Text(TextMessage::decode(&mut reader)?); Self::Group(IncomingGroupMessageBody { group_identity, body })
},
CspE2eMessageType::GroupLocation => { let group_identity = IncomingGroupMessageBody::decode_group_member_header(&e='color:red'>mut reader)?; let body = GroupMessageBody::Location(LocationMessage::decode(&mut reader)?); Self::Group(IncomingGroupMessageBody { group_identity, body })
},
CspE2eMessageType::GroupDeliveryReceipt => { let group_identity = IncomingGroupMessageBody::decode_group_member_header(&e='color:red'>mut reader)?; let body = match DeliveryReceiptMapper::decode_type(&mut reader)? {
DeliveryReceiptMapper::DeliveryReceipt(_) => { return Err(IncomingMessageError::InvalidMessage( "Unexpected group delivery receipt".to_owned(),
));
},
DeliveryReceiptMapper::LegacyReaction(reaction_type) => GroupMessageBody::LegacyReaction(
LegacyReactionMessage::decode(reaction_type, &mut reader)?,
),
}; Self::Group(IncomingGroupMessageBody { group_identity, body })
},
// TODO(LIB-16): Decode the rest
_ => { return Err(IncomingMessageError::InvalidMessage( "Ain't nobody got time to implement those incoming messages".to_owned(),
));
},
};
// Done let _ = reader.expect_consumed()?;
Ok(message)
}
/// Get the incoming message's type. constfn message_type(&self) -> CspE2eMessageType { match &self {
IncomingMessageBody::Contact(body) => body.message_type(),
IncomingMessageBody::Group(body) => body.body.message_type(),
}
}
/// Get the incoming message's properties respective to the associated conversation. constfn properties(&self) -> MessageProperties { match &self {
IncomingMessageBody::Contact(body) => body.properties(),
IncomingMessageBody::Group(body) => body.body.properties().into_message_properties(),
}
}
/// Determine whether the incoming message is exempt from blocking. fn is_exempt_from_blocking(&self) -> bool { match &self {
IncomingMessageBody::Contact(body) => body.is_exempt_from_blocking(),
IncomingMessageBody::Group(body) => body.body.is_exempt_from_blocking(),
}
}
}
/// An outgoing 1:1 message to a contact. #[derive(Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] pubstruct OutgoingContactMessageBody { /// Identity of the receiver. pub receiver_identity: ThreemaId,
/// An outgoing distribution list message. #[derive(Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] pubstruct OutgoingDistributionListMessageBody { /// The specific distribution list identity. pub distribution_list_identity: u64,
/// The message's body. pub body: DistributionListMessageBody,
} impl OutgoingDistributionListMessageBody { /// Encode the distribution list message. /// /// Note: This behaves identical to the respective 1:1 message. fn encode_into(&self, writer: &mutimpl ByteWriter) -> Result<(), ByteWriterError> { match &self.body {
DistributionListMessageBody::Text(message) => message.encode_into(writer),
DistributionListMessageBody::Location(message) => message.encode_into(writer),
}
}
}
/// An outgoing group message. #[derive(Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] pubstruct OutgoingGroupMessageBody { /// The specific group identity. pub group_identity: GroupIdentity,
/// The message's body. pub body: GroupMessageBody,
} impl OutgoingGroupMessageBody { /// Encode the group message with its respective header (either `group-creator-container` or /// `group-member-container`). fn encode_into(&self, writer: &mutimpl ByteWriter) -> Result<(), ByteWriterError> { match &self.body {
GroupMessageBody::Text(message) => { Self::encode_group_member_header_into(&self.group_identity, writer)?;
message.encode_into(writer)?;
},
GroupMessageBody::Location(message) => { Self::encode_group_member_header_into(&self.group_identity, writer)?;
message.encode_into(writer)?;
},
GroupMessageBody::LegacyReaction(message) => { Self::encode_group_member_header_into(&self.group_identity, writer)?;
message.encode_into(writer)?;
},
}
Ok(())
}
/// Outgoing message bodies for their respective conversations. #[derive(Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] pubenum OutgoingMessageBody { /// An outgoing 1:1 message to a contact.
Contact(OutgoingContactMessageBody),
/// An outgoing distribution list message.
DistributionList(OutgoingDistributionListMessageBody),
/// An outgoing group message.
Group(OutgoingGroupMessageBody),
} impl OutgoingMessageBody { pub(crate) fn encode_into(&self, writer: &mutimpl ByteWriter) -> Result<(), ByteWriterError> { // Encode type
writer.write_u8(self.message_type() as u8)?;
// Encode data let data_length = { let offset = writer.offset(); matchself {
OutgoingMessageBody::Contact(body) => body.encode_into(writer),
OutgoingMessageBody::DistributionList(body) => body.encode_into(writer),
OutgoingMessageBody::Group(body) => body.encode_into(writer),
}?;
writer
.offset()
.checked_sub(offset)
.expect("Encoded message data must be >= 0 bytes")
};
// Add PKCS#7 padding let padding_length: u8 = rand::thread_rng().gen_range(1..=255); let padding_length = if data_length.saturating_add(padding_length as usize) < MESSAGE_DATA_PADDING_LENGTH_MIN as usize
{
MESSAGE_DATA_PADDING_LENGTH_MIN.saturating_sub(data_length.try_into().expect( "data_length + 1..=255 must be < MESSAGE_DATA_PADDING_LENGTH_MIN and therefore u8",
))
} else {
padding_length
};
writer
.write_in_place(padding_length as usize)?
.fill(padding_length);
// Done
Ok(())
}
/// Get the outgoing message's type. constfn message_type(&self) -> CspE2eMessageType { match &self {
OutgoingMessageBody::Contact(body) => body.body.message_type(),
OutgoingMessageBody::DistributionList(body) => body.body.message_type(),
OutgoingMessageBody::Group(body) => body.body.message_type(),
}
}
/// Get the outgoing message's properties respective to the associated conversation. constfn properties(&self) -> MessageProperties { match &self {
OutgoingMessageBody::Contact(body) => body.body.properties(),
OutgoingMessageBody::DistributionList(body) => body.body.properties(),
OutgoingMessageBody::Group(body) => body.body.properties().into_message_properties(),
}
}
/// Determine whether the outgoing message is exempt from blocking. fn is_exempt_from_blocking(&self) -> bool { match &self {
OutgoingMessageBody::Contact(body) => body.body.is_exempt_from_blocking(),
OutgoingMessageBody::DistributionList(body) => body.body.is_exempt_from_blocking(),
OutgoingMessageBody::Group(body) => body.body.is_exempt_from_blocking(),
}
}
}
/// An incoming message. #[derive(Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] pubstruct IncomingMessage { /// Identity of the sender. pub sender_identity: ThreemaId,
/// Unix-ish timestamp in milliseconds for when the message has been created. pub created_at: u64,
/// The message's body for the respective conversation. pub body: IncomingMessageBody,
}
/// An outgoing message. #[derive(Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] pubstruct OutgoingMessage { /// The ID of the message. pub id: MessageId,
/// Unix-ish timestamp in milliseconds for when the message has been created. pub created_at: u64,
/// The message's body for the respective conversation. pub body: OutgoingMessageBody,
} impl OutgoingMessage { /// Computes [`MessageFlags`] from the [`MessageProperties`] and then the [`MessageOverrides`]. pub(crate) fn flags(&self) -> MessageFlags {
MessageFlags::default()
.chain_apply(self.properties())
.chain_apply(self.overrides)
}
}
/// Get the message's properties. /// /// IMPORTANT: This does **NOT** include the overrides from [`MessageOverrides`]! #[inline] constfn properties(&self) -> MessageProperties { self.body.properties()
}
/// Get the effective message's properties, i.e. those with the [`MessageOverrides`] applied. #[inline] pub(crate) fn effective_properties(&self) -> MessageProperties { self.properties().chain_apply(self.overrides)
}
/// Determine whether the message is exempt from blocking. #[inline] #[allow(unused, reason = "TODO(LIB-51)")] pub(crate) fn is_exempt_from_blocking(&self) -> bool { self.body.is_exempt_from_blocking()
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.19 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.