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: Rust
 

//! 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, EncryptedDataRangeReadeas _,
            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(crateenum 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(cratestruct 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<SelfSelf::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(crateenum 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(cratestruct 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<SelfSelf::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
}

Messung V0.5 in Prozent
C=82 H=100 G=91

¤ Dauer der Verarbeitung: 0.1 Sekunden  (vorverarbeitet am  2026-06-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

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.