Skip to main content

libsignal_service/
content.rs

1use chrono::Utc;
2use libsignal_core::DeviceId;
3use libsignal_protocol::{Pni, ProtocolAddress, ServiceId};
4use prost::Message;
5use std::fmt;
6use uuid::Uuid;
7
8pub use crate::{
9    proto::{
10        attachment_pointer::Flags as AttachmentPointerFlags,
11        data_message::Flags as DataMessageFlags, data_message::Reaction,
12        sync_message, AttachmentPointer, CallMessage, DataMessage,
13        DecryptionErrorMessage, EditMessage, GroupContextV2, NullMessage,
14        ReceiptMessage, StoryMessage, SyncMessage, TypingMessage,
15    },
16    push_service::ServiceError,
17    ServiceIdExt,
18};
19
20mod data_message;
21mod story_message;
22
23#[derive(Clone, Debug)]
24pub struct Metadata {
25    pub sender: ServiceId,
26    pub destination: ServiceId,
27    pub sender_device: DeviceId,
28    /// Contains the PNI of the sender if verification was succesfull
29    pub pni_verified: Option<Pni>,
30
31    pub client_timestamp: chrono::DateTime<Utc>,
32    pub server_timestamp: chrono::DateTime<Utc>,
33    pub needs_receipt: bool,
34    pub unidentified_sender: bool,
35    pub was_plaintext: bool,
36
37    /// A unique UUID for this specific message, produced by the Signal servers.
38    ///
39    /// The server GUID is used to report spam messages.
40    pub server_guid: Option<Uuid>,
41}
42
43impl fmt::Display for Metadata {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        write!(
46            f,
47            "Metadata {{ sender: {}, guid: {}, server timestamp: {} }}",
48            self.sender.service_id_string(),
49            // XXX: should this still be optional?
50            self.server_guid
51                .map(|u| u.to_string())
52                .as_deref()
53                .unwrap_or("None"),
54            self.server_timestamp,
55        )
56    }
57}
58
59impl Metadata {
60    pub(crate) fn protocol_address(
61        &self,
62    ) -> Result<ProtocolAddress, libsignal_core::InvalidDeviceId> {
63        self.sender.to_protocol_address(self.sender_device)
64    }
65}
66
67#[derive(Clone, Debug)]
68pub struct Content {
69    pub metadata: Metadata,
70    pub body: ContentBody,
71}
72
73impl Content {
74    pub fn from_body(body: impl Into<ContentBody>, metadata: Metadata) -> Self {
75        Self {
76            metadata,
77            body: body.into(),
78        }
79    }
80
81    /// Converts a proto::Content into a public Content, including metadata.
82    pub fn from_proto(
83        p: crate::proto::Content,
84        metadata: Metadata,
85    ) -> Result<Self, ServiceError> {
86        let Some(content) = p.content else {
87            return Err(ServiceError::UnsupportedContent);
88        };
89
90        use crate::proto::content::Content;
91        match content {
92            Content::DataMessage(msg) => Ok(Self::from_body(msg, metadata)),
93            Content::SyncMessage(msg) => Ok(Self::from_body(msg, metadata)),
94            Content::CallMessage(msg) => Ok(Self::from_body(msg, metadata)),
95            Content::NullMessage(msg) => Ok(Self::from_body(msg, metadata)),
96            Content::ReceiptMessage(msg) => Ok(Self::from_body(msg, metadata)),
97            Content::TypingMessage(msg) => Ok(Self::from_body(msg, metadata)),
98            Content::DecryptionErrorMessage(msg) => Ok(Self {
99                metadata,
100                body: ContentBody::DecryptionErrorMessage(
101                    DecryptionErrorMessage::decode(msg.as_ref())?,
102                ),
103            }),
104            Content::StoryMessage(msg) => Ok(Self::from_body(msg, metadata)),
105            Content::EditMessage(msg) => Ok(Self::from_body(msg, metadata)),
106        }
107    }
108}
109
110impl fmt::Display for ContentBody {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        match self {
113            Self::NullMessage(_) => write!(f, "NullMessage"),
114            Self::DataMessage(m) => {
115                match (&m.body, &m.reaction, m.attachments.len()) {
116                    (Some(body), _, 0) => {
117                        write!(f, "DataMessage({})", body)
118                    },
119                    (Some(body), _, n) => {
120                        write!(f, "DataMessage({}, attachments: {n})", body)
121                    },
122                    (None, Some(emoji), _) => {
123                        write!(
124                            f,
125                            "DataMessage(reaction: {})",
126                            emoji.emoji.as_deref().unwrap_or("None")
127                        )
128                    },
129                    (None, _, n) if n > 0 => {
130                        write!(f, "DataMessage(attachments: {n})")
131                    },
132                    _ => {
133                        write!(f, "{self:?}")
134                    },
135                }
136            },
137            Self::SynchronizeMessage(_) => write!(f, "SynchronizeMessage"),
138            Self::CallMessage(_) => write!(f, "CallMessage"),
139            Self::ReceiptMessage(_) => write!(f, "ReceiptMessage"),
140            Self::TypingMessage(_) => write!(f, "TypingMessage"),
141            // Self::SenderKeyDistributionMessage(_) => write!(f, "SenderKeyDistributionMessage"),
142            Self::DecryptionErrorMessage(_) => {
143                write!(f, "DecryptionErrorMessage")
144            },
145            Self::StoryMessage(_) => write!(f, "StoryMessage"),
146            Self::EditMessage(_) => write!(f, "EditMessage"),
147        }
148    }
149}
150
151#[derive(Clone, Debug)]
152#[allow(clippy::large_enum_variant)]
153pub enum ContentBody {
154    NullMessage(NullMessage),
155    DataMessage(DataMessage),
156    SynchronizeMessage(SyncMessage),
157    CallMessage(CallMessage),
158    ReceiptMessage(ReceiptMessage),
159    TypingMessage(TypingMessage),
160    // SenderKeyDistributionMessage(SenderKeyDistributionMessage),
161    DecryptionErrorMessage(DecryptionErrorMessage),
162    StoryMessage(StoryMessage),
163    EditMessage(EditMessage),
164}
165
166impl NullMessage {
167    pub fn generate<R: rand::Rng + rand::CryptoRng>(rng: &mut R) -> Self {
168        // Random length between 1 and 140 bytes
169        let padding_length = (rng.next_u64() % 140) as usize + 1;
170        let mut padding = vec![0; padding_length];
171        rng.fill(padding.as_mut_slice());
172        NullMessage {
173            padding: Some(padding),
174        }
175    }
176}
177
178impl ContentBody {
179    pub fn into_proto(self) -> crate::proto::Content {
180        use crate::proto::content::Content;
181
182        let inner = match self {
183            Self::NullMessage(msg) => Content::NullMessage(msg),
184            Self::DataMessage(msg) => Content::DataMessage(msg),
185            Self::SynchronizeMessage(msg) => Content::SyncMessage(msg),
186            Self::CallMessage(msg) => Content::CallMessage(msg),
187            Self::ReceiptMessage(msg) => Content::ReceiptMessage(msg),
188            Self::TypingMessage(msg) => Content::TypingMessage(msg),
189            Self::DecryptionErrorMessage(msg) => {
190                Content::DecryptionErrorMessage(msg.encode_to_vec())
191            },
192            Self::StoryMessage(msg) => Content::StoryMessage(msg),
193            Self::EditMessage(msg) => Content::EditMessage(msg),
194        };
195        crate::proto::Content {
196            content: Some(inner),
197            // TODO: handle SKDM; ideally this is also "tacked on" when needed,
198            // and not handled as a separate message.
199            sender_key_distribution_message: None,
200            // PNI signature gets added down the message sender stream
201            pni_signature_message: None,
202        }
203    }
204}
205
206macro_rules! impl_from_for_content_body {
207    ($enum:ident ($t:ty)) => {
208        impl From<$t> for ContentBody {
209            fn from(inner: $t) -> ContentBody {
210                ContentBody::$enum(inner)
211            }
212        }
213    };
214}
215
216impl_from_for_content_body!(NullMessage(NullMessage));
217impl_from_for_content_body!(DataMessage(DataMessage));
218impl_from_for_content_body!(SynchronizeMessage(SyncMessage));
219impl_from_for_content_body!(CallMessage(CallMessage));
220impl_from_for_content_body!(ReceiptMessage(ReceiptMessage));
221impl_from_for_content_body!(TypingMessage(TypingMessage));
222// impl_from_for_content_body!(SenderKeyDistributionMessage(
223//     SenderKeyDistributionMessage
224// ));
225// impl_from_for_content_body!(DecryptionErrorMessage(DecryptionErrorMessage));
226impl_from_for_content_body!(StoryMessage(StoryMessage));
227impl_from_for_content_body!(EditMessage(EditMessage));
228
229macro_rules! impl_from_for_sync_message {
230    ($t:ident) => {
231        impl From<$crate::proto::sync_message::$t> for SyncMessage {
232            fn from(inner: sync_message::$t) -> SyncMessage {
233                SyncMessage {
234                    content: Some($crate::proto::sync_message::Content::$t(
235                        inner,
236                    )),
237                    ..SyncMessage::with_padding(&mut rand::rng())
238                }
239            }
240        }
241    };
242}
243
244impl_from_for_sync_message!(Sent);
245impl_from_for_sync_message!(Contacts);
246impl_from_for_sync_message!(Request);
247impl_from_for_sync_message!(Blocked);
248// impl_from_for_sync_message!(Verified); // maps on crate::proto::Verified instead?
249impl_from_for_sync_message!(Configuration);
250impl_from_for_sync_message!(ViewOnceOpen);
251impl_from_for_sync_message!(FetchLatest);
252impl_from_for_sync_message!(Keys);
253impl_from_for_sync_message!(MessageRequestResponse);
254impl_from_for_sync_message!(OutgoingPayment);
255impl_from_for_sync_message!(PniChangeNumber);
256impl_from_for_sync_message!(CallEvent);
257impl_from_for_sync_message!(CallLinkUpdate);
258impl_from_for_sync_message!(CallLogEvent);
259impl_from_for_sync_message!(DeleteForMe);
260impl_from_for_sync_message!(DeviceNameChange);
261impl_from_for_sync_message!(AttachmentBackfillRequest);
262impl_from_for_sync_message!(AttachmentBackfillResponse);
263impl_from_for_sync_message!(UsernameChange);