Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/Java/Threema/domain/libthreema/lib/src/csp_e2e/message/     Datei vom 25.3.2026 mit Größe 12 kB image not shown  

Quelle  payload.rs   Sprache: unbekannt

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

//! Fully decoded/encoded messages, bridging the two CSP layers.
use core::str::{self, Utf8Error};

use educe::Educe;

use crate::{
    common::{MessageFlags, MessageId, Nonce, ThreemaId, ThreemaIdError},
    crypto::salsa20,
    csp::payload::MessageWithMetadataBox,
    utils::{
        bytes::{
            ByteReader as _, ByteReaderError, ByteWriter, ByteWriterError, EncryptedDataRangeReader as _,
            OwnedVecByteReader, OwnedVecByteWriter,
        },
        debug::debug_slice_length,
    },
};

const LEGACY_SENDER_NICKNAME_LENGTH: u8 = 32;

/// An error occurred while decoding an incoming message payload.
#[derive(Debug, thiserror::Error)]
pub(crate) enum IncomingMessagePayloadError {
    /// Decoding the message failed.
    #[error("Decoding message failed: {0}")]
    DecodingFailed(#[from] ByteReaderError),

    /// Invalid receiver identity encountered.
    #[error("Invalid receiver identity: {0}")]
    InvalidReceiverIdentity(#[source] ThreemaIdError),

    /// Invalid nickname encoding.
    #[error("Invalid nickname: {0}")]
    InvalidNickname(Utf8Error),

    /// Invalid message.
    #[error("Invalid message: Does not contain sufficient bytes")]
    InvalidMessage,
}

/// An incoming end-to-end encrypted Threema message with additional end-to-end encrypted metadata.
///
/// Note: Only the outer shell has been decoded here. The incoming message task is responsible for decrypting
/// and decoding the inner layers.
#[derive(Educe)]
#[educe(Debug)]
pub(crate) struct IncomingMessageWithMetadataBox {
    /// Raw bytes of the payload
    #[educe(Debug(method(debug_slice_length)))]
    pub(super) bytes: Vec<u8>,

    /// The sender's Threema ID.
    pub(super) sender_identity: ThreemaId,

    /// The receiver's Threema ID.
    pub(super) receiver_identity: ThreemaId,

    /// The ID of the message
    pub(super) id: MessageId,

    /// (Legacy) UNIX timestamp in seconds for when the message has been created.
    ///
    /// Note: This timestamp is also present in the `metadata` field and should be preferred from
    /// there.
    pub(super) legacy_created_at: u32,

    /// Message flags.
    pub(super) flags: MessageFlags,

    /// Legacy sender nickname (nickname from metadata should be preferred).
    pub(super) legacy_sender_nickname: Option<String>,

    /// Encrypted metadata.
    pub(super) metadata: Option<salsa20::EncryptedDataRange>,

    /// Nonce for both the contained message and the metadata box.
    pub(super) nonce: Nonce,

    /// Encrypted contained message.
    pub(super) message_container: salsa20::EncryptedDataRange,
}
impl TryFrom<MessageWithMetadataBox> for IncomingMessageWithMetadataBox {
    type Error = IncomingMessagePayloadError;

    fn try_from(payload: MessageWithMetadataBox) -> Result<Self, Self::Error> {
        let mut reader = OwnedVecByteReader::new(payload.bytes);

        // Skip sender identity
        reader.skip(ThreemaId::LENGTH)?;

        // Decode receiver identity
        let receiver_identity = ThreemaId::try_from(reader.read_fixed::<{ ThreemaId::LENGTH }>()?.as_slice())
            .map_err(IncomingMessagePayloadError::InvalidReceiverIdentity)?;

        // Skip the message ID (we already have it)
        reader.skip(MessageId::LENGTH)?;

        // Decode legacy _created at_ timestamp
        let legacy_created_at = reader.read_u32_le()?;

        // Skip flags (we already have it) and reserved bytes
        reader.skip(2)?;

        // Decode metadata length
        let metadata_length = reader.read_u16_le()?;

        // Decode and trim zero-padded legacy sender nickname (if any)
        let legacy_sender_nickname = {
            let nickname = reader.read_fixed::<{ LEGACY_SENDER_NICKNAME_LENGTH as usize }>()?;
            let length = nickname
                .iter()
                .position(|&character| character == b'\0')
                .unwrap_or(nickname.len());
            if length > 0 {
                let nickname = nickname
                    .get(..length)
                    .expect("calculated legacy sender nickname length must be in bounds");
                let nickname =
                    str::from_utf8(nickname).map_err(IncomingMessagePayloadError::InvalidNickname)?;
                Some(nickname.trim().to_owned())
            } else {
                None
            }
        };

        // Decode metadata (if any)
        let metadata = if metadata_length > 0 {
            Some(
                reader.read_encrypted_data_range_tag_ahead(
                    (metadata_length as usize)
                        .checked_sub(salsa20::TAG_LENGTH)
                        .ok_or(IncomingMessagePayloadError::InvalidMessage)?,
                )?,
            )
        } else {
            None
        };

        // Decode nonce
        let nonce = Nonce::from(reader.read_fixed::<{ Nonce::LENGTH }>()?);

        // Decode message container
        let message_container = reader.read_encrypted_data_range_tag_ahead(
            reader
                .remaining()
                .checked_sub(salsa20::TAG_LENGTH)
                .ok_or(IncomingMessagePayloadError::InvalidMessage)?,
        )?;

        // Done
        let bytes = reader.expect_consumed()?;
        Ok(IncomingMessageWithMetadataBox {
            bytes,
            sender_identity: payload.sender_identity,
            receiver_identity,
            id: payload.id,
            legacy_created_at,
            flags: payload.flags,
            legacy_sender_nickname,
            metadata,
            nonce,
            message_container,
        })
    }
}

/// An error occurred while encoding an outgoing message payload.
#[derive(Debug, thiserror::Error)]
pub(crate) enum OutgoingMessagePayloadError {
    /// Decoding the message failed.
    #[error("Encoding message failed: {0}")]
    EncodingFailed(#[from] ByteWriterError),

    /// Metadata too large.
    #[error("Metadata too large")]
    MetadataTooLarge,
}

/// An outgoing end-to-end encrypted Threema message with additional end-to-end encrypted metadata.
#[derive(Educe)]
#[educe(Debug)]
pub(crate) struct OutgoingMessageWithMetadataBox {
    /// The sender's Threema ID.
    pub(super) sender_identity: ThreemaId,

    /// The receiver's Threema ID.
    pub(super) receiver_identity: ThreemaId,

    /// The ID of the message.
    pub(super) id: MessageId,

    /// (Legacy) UNIX timestamp in seconds for when the message has been created.
    ///
    /// Note: This timestamp is also present in the `metadata` field and should be preferred from
    /// there.
    pub(super) legacy_created_at: u32,

    /// Message flags.
    pub(super) flags: MessageFlags,

    /// Legacy sender nickname (nickname from metadata should be preferred).
    ///
    /// Note 1: The creator of the struct is responsible for omitting this field when sending a message to
    /// non-Gateway IDs.
    ///
    /// Note 2: Only 32 bytes of the UTF-8 string will be encoded, so prior truncation at UTF-8 codepoints is
    /// advised.
    pub(super) legacy_sender_nickname: Option<String>,

    /// Encrypted metadata.
    #[educe(Debug(method(debug_slice_length)))]
    pub(super) metadata: Vec<u8>,

    /// Nonce for both the contained message and the metadata box.
    pub(super) nonce: Nonce,

    /// Encrypted contained message.
    #[educe(Debug(method(debug_slice_length)))]
    pub(super) message_container: Vec<u8>,
}
impl OutgoingMessageWithMetadataBox {
    const STATIC_LENGTH: usize = 2 * ThreemaId::LENGTH
        + MessageId::LENGTH
        + 4 /* legacy_created_at */
        + MessageFlags::LENGTH
        + 1 /* reserved */
        + 2 /* metadata-length */
        + LEGACY_SENDER_NICKNAME_LENGTH as usize
        + Nonce::LENGTH;

    fn length(&self) -> usize {
        Self::STATIC_LENGTH
            .saturating_add(self.metadata.len())
            .saturating_add(self.message_container.len())
    }

    fn encode_into(&self, writer: &mut impl ByteWriter) -> Result<(), OutgoingMessagePayloadError> {
        // Encode sender identity, receiver identity, message ID, legacy _created at_ timestamp, flags and
        // reserved space
        writer.write(&self.sender_identity.to_bytes())?;
        writer.write(&self.receiver_identity.to_bytes())?;
        writer.write_u64_le(self.id.0)?;
        writer.write_u32_le(self.legacy_created_at)?;
        writer.write_u8(self.flags.0)?;
        writer.write_u8(0)?; // reserved

        // Encode metadata length
        writer.write_u16_le(
            self.metadata
                .len()
                .try_into()
                .map_err(|_| OutgoingMessagePayloadError::MetadataTooLarge)?,
        )?;

        // Encode zero-padded legacy sender nickname if available (truncate if needed)
        writer.write(&[0_u8; LEGACY_SENDER_NICKNAME_LENGTH as usize])?;
        if let Some(legacy_sender_nickname) = &self.legacy_sender_nickname
            && let Some(truncated_legacy_sender_nickname) = legacy_sender_nickname.as_bytes().get(
                ..legacy_sender_nickname
                    .len()
                    .min(LEGACY_SENDER_NICKNAME_LENGTH as usize),
            )
        {
            writer.run_at(
                (LEGACY_SENDER_NICKNAME_LENGTH as isize)
                    .checked_neg()
                    .expect("-LEGACY_SENDER_NICKNAME_LENGTH fits isize"),
                |mut writer| writer.write(truncated_legacy_sender_nickname),
            )?;
        }

        // Encode metadata, nonce and message container
        writer.write(&self.metadata)?;
        writer.write(&self.nonce.0)?;
        writer.write(&self.message_container)?;

        // Done
        Ok(())
    }
}
impl TryFrom<OutgoingMessageWithMetadataBox> for MessageWithMetadataBox {
    type Error = OutgoingMessagePayloadError;

    fn try_from(message: OutgoingMessageWithMetadataBox) -> Result<Self, Self::Error> {
        let mut writer = OwnedVecByteWriter::new_with_capacity(message.length());
        message.encode_into(&mut writer)?;
        Ok(Self {
            sender_identity: message.sender_identity,
            id: message.id,
            flags: message.flags,
            bytes: writer.into_inner(),
        })
    }
}

#[cfg(test)]
mod tests {
    use assert_matches::assert_matches;
    use data_encoding::HEXLOWER;

    use crate::{
        common::{MessageFlags, MessageId, Nonce, ThreemaId},
        csp::payload::MessageWithMetadataBox,
        csp_e2e::message::payload::IncomingMessageWithMetadataBox,
        utils::bytes::OwnedVecByteReader,
    };

    #[test]
    fn valid_message() -> anyhow::Result<()> {
        let message = HEXLOWER.decode(
            b"\
                304441354d453736304850543945574489aa9a7eaff77d96cb7327680100340000000000\
                00000000000000000000000000000000000000000000000000000000439039d79074fa4a0d0961d6\
                51af57b94b7245c4c686733aab55b7f2b049fa3a8a5fec982eaa27d37557beaadd802cfc2703d849\
                b2d718b0db179e3e3bcdbf3c2be997490a0349f2e4fbaa43712e263aab0c2c4a920182f01f810df0\
                63363191b26c2c6404c0e84e73a3e005e327589702878e259642e1cf3b29e36db1c6a258f55ea73c\
                842fddafffd76a3057c0c13b6881bccc6522a0edee793f586fcb9ec5b398eb3be0af1a8c6111fe46\
                3ed25d916e66bea54955ca3398e27cbae25bfb6c16e26f326ecf8a4ba81aef9312b59f612b9e3355\
                de6c14c0434dc195e0a03462fc95d836a7bca74bda61d59be8489a9fdd9e626e7cb7324ac0724b0a\
                42168a5bea525eaef17d3bf13bbd8551ab8c85f5892fa6ba9c32e01343c3bc8ed2ad59f54411de08\
                9b193dca452b9699dafe34d124dfe521a956cce4adf58902a4c7b8bcf3d4548848dd2f1bee",
        )?;
        let message = MessageWithMetadataBox::decode(OwnedVecByteReader::new(message))?;
        let message = IncomingMessageWithMetadataBox::try_from(message)?;

        assert_eq!(message.bytes.len(), 393);
        assert_eq!(message.sender_identity, ThreemaId::try_from("0DA5ME76")?);
        assert_eq!(message.receiver_identity, ThreemaId::try_from("0HPT9EWD")?);
        assert_eq!(message.id, MessageId::from_hex("89aa9a7eaff77d96")?);
        assert_eq!(message.legacy_created_at, 1747416011);
        assert_eq!(message.flags, MessageFlags(MessageFlags::SEND_PUSH_NOTIFICATION));
        assert_eq!(message.legacy_sender_nickname, None);
        let metadata = assert_matches!(message.metadata, Some(metadata) => metadata);
        assert_eq!(
            metadata.tag,
            TryInto::<[u8; 16]>::try_into(HEXLOWER.decode(b"439039d79074fa4a0d0961d651af57b9")?).unwrap()
        );
        assert_eq!(metadata.data, 80..116);
        assert_eq!(
            message.nonce,
            Nonce::from_hex("b2d718b0db179e3e3bcdbf3c2be997490a0349f2e4fbaa43")?,
        );
        assert_eq!(
            message.message_container.tag,
            TryInto::<[u8; 16]>::try_into(HEXLOWER.decode(b"712e263aab0c2c4a920182f01f810df0")?).unwrap()
        );
        assert_eq!(message.message_container.data, 156..393);

        Ok(())
    }

    // TODO(LIB-16): Test negative cases
}

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