Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


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.27 Sekunden, vorverarbeitet 2026-04-27]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge