Skip to main content

libsignal_service/
content.rs

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