libsignal_service/
content.rs

1use libsignal_core::DeviceId;
2use libsignal_protocol::{ProtocolAddress, ServiceId};
3use std::fmt;
4use uuid::Uuid;
5
6pub use crate::{
7    proto::{
8        attachment_pointer::Flags as AttachmentPointerFlags,
9        data_message::Flags as DataMessageFlags, data_message::Reaction,
10        sync_message, AttachmentPointer, CallMessage, DataMessage, EditMessage,
11        GroupContextV2, NullMessage, PniSignatureMessage, ReceiptMessage,
12        StoryMessage, SyncMessage, TypingMessage,
13    },
14    push_service::ServiceError,
15    ServiceIdExt,
16};
17
18mod data_message;
19mod story_message;
20
21#[derive(Clone, Debug)]
22pub struct Metadata {
23    pub sender: ServiceId,
24    pub destination: ServiceId,
25    pub sender_device: DeviceId,
26    pub timestamp: u64,
27    pub needs_receipt: bool,
28    pub unidentified_sender: bool,
29    pub was_plaintext: bool,
30
31    /// A unique UUID for this specific message, produced by the Signal servers.
32    ///
33    /// The server GUID is used to report spam messages.
34    pub server_guid: Option<Uuid>,
35}
36
37impl fmt::Display for Metadata {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        write!(
40            f,
41            "Metadata {{ sender: {}, guid: {} }}",
42            self.sender.service_id_string(),
43            // XXX: should this still be optional?
44            self.server_guid
45                .map(|u| u.to_string())
46                .as_deref()
47                .unwrap_or("None"),
48        )
49    }
50}
51
52impl Metadata {
53    pub(crate) fn protocol_address(
54        &self,
55    ) -> Result<ProtocolAddress, libsignal_core::InvalidDeviceId> {
56        self.sender.to_protocol_address(self.sender_device)
57    }
58}
59
60#[derive(Clone, Debug)]
61pub struct Content {
62    pub metadata: Metadata,
63    pub body: ContentBody,
64}
65
66impl Content {
67    pub fn from_body(body: impl Into<ContentBody>, metadata: Metadata) -> Self {
68        Self {
69            metadata,
70            body: body.into(),
71        }
72    }
73
74    /// Converts a proto::Content into a public Content, including metadata.
75    #[expect(clippy::result_large_err)]
76    pub fn from_proto(
77        p: crate::proto::Content,
78        metadata: Metadata,
79    ) -> Result<Self, ServiceError> {
80        // The Java version also assumes only one content type at a time.
81        // It's a bit sad that we cannot really match here, we've got no
82        // r#type() method.
83        // Allow the manual map (if let Some -> option.map(||)), because it
84        // reduces the git diff when more types would be added.
85        #[allow(clippy::manual_map)]
86        if let Some(msg) = p.data_message {
87            Ok(Self::from_body(msg, metadata))
88        } else if let Some(msg) = p.sync_message {
89            Ok(Self::from_body(msg, metadata))
90        } else if let Some(msg) = p.call_message {
91            Ok(Self::from_body(msg, metadata))
92        } else if let Some(msg) = p.receipt_message {
93            Ok(Self::from_body(msg, metadata))
94        } else if let Some(msg) = p.typing_message {
95            Ok(Self::from_body(msg, metadata))
96        // } else if let Some(msg) = p.sender_key_distribution_message {
97        //     Ok(Self::from_body(msg, metadata))
98        // } else if let Some(msg) = p.decryption_error_message {
99        //     Ok(Self::from_body(msg, metadata))
100        } else if let Some(msg) = p.story_message {
101            Ok(Self::from_body(msg, metadata))
102        } else if let Some(msg) = p.pni_signature_message {
103            Ok(Self::from_body(msg, metadata))
104        } else if let Some(msg) = p.edit_message {
105            Ok(Self::from_body(msg, metadata))
106        } else {
107            Err(ServiceError::UnsupportedContent)
108        }
109    }
110}
111
112impl fmt::Display for ContentBody {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        match self {
115            Self::NullMessage(_) => write!(f, "NullMessage"),
116            Self::DataMessage(m) => {
117                match (&m.body, &m.reaction, m.attachments.len()) {
118                    (Some(body), _, 0) => {
119                        write!(f, "DataMessage({})", body)
120                    },
121                    (Some(body), _, n) => {
122                        write!(f, "DataMessage({}, attachments: {n})", body)
123                    },
124                    (None, Some(emoji), _) => {
125                        write!(
126                            f,
127                            "DataMessage(reaction: {})",
128                            emoji.emoji.as_deref().unwrap_or("None")
129                        )
130                    },
131                    (None, _, n) if n > 0 => {
132                        write!(f, "DataMessage(attachments: {n})")
133                    },
134                    _ => {
135                        write!(f, "{self:?}")
136                    },
137                }
138            },
139            Self::SynchronizeMessage(_) => write!(f, "SynchronizeMessage"),
140            Self::CallMessage(_) => write!(f, "CallMessage"),
141            Self::ReceiptMessage(_) => write!(f, "ReceiptMessage"),
142            Self::TypingMessage(_) => write!(f, "TypingMessage"),
143            // Self::SenderKeyDistributionMessage(_) => write!(f, "SenderKeyDistributionMessage"),
144            // Self::DecryptionErrorMessage(_) => write!(f, "DecryptionErrorMessage"),
145            Self::StoryMessage(_) => write!(f, "StoryMessage"),
146            Self::PniSignatureMessage(_) => write!(f, "PniSignatureMessage"),
147            Self::EditMessage(_) => write!(f, "EditMessage"),
148        }
149    }
150}
151
152#[derive(Clone, Debug)]
153#[allow(clippy::large_enum_variant)]
154pub enum ContentBody {
155    NullMessage(NullMessage),
156    DataMessage(DataMessage),
157    SynchronizeMessage(SyncMessage),
158    CallMessage(CallMessage),
159    ReceiptMessage(ReceiptMessage),
160    TypingMessage(TypingMessage),
161    // SenderKeyDistributionMessage(SenderKeyDistributionMessage),
162    // DecryptionErrorMessage(DecryptionErrorMessage),
163    StoryMessage(StoryMessage),
164    PniSignatureMessage(PniSignatureMessage),
165    EditMessage(EditMessage),
166}
167
168impl ContentBody {
169    pub fn into_proto(self) -> crate::proto::Content {
170        match self {
171            Self::NullMessage(msg) => crate::proto::Content {
172                null_message: Some(msg),
173                ..Default::default()
174            },
175            Self::DataMessage(msg) => crate::proto::Content {
176                data_message: Some(msg),
177                ..Default::default()
178            },
179            Self::SynchronizeMessage(msg) => crate::proto::Content {
180                sync_message: Some(msg),
181                ..Default::default()
182            },
183            Self::CallMessage(msg) => crate::proto::Content {
184                call_message: Some(msg),
185                ..Default::default()
186            },
187            Self::ReceiptMessage(msg) => crate::proto::Content {
188                receipt_message: Some(msg),
189                ..Default::default()
190            },
191            Self::TypingMessage(msg) => crate::proto::Content {
192                typing_message: Some(msg),
193                ..Default::default()
194            },
195            // XXX Those two are serialized as Vec<u8> and I'm not currently sure how to handle
196            // them.
197            // Self::SenderKeyDistributionMessage(msg) => crate::proto::Content {
198            //     sender_key_distribution_message: Some(msg),
199            //     ..Default::default()
200            // },
201            // Self::DecryptionErrorMessage(msg) => crate::proto::Content {
202            //     decryption_error_message: Some(msg.serialized()),
203            //     ..Default::default()
204            // },
205            Self::StoryMessage(msg) => crate::proto::Content {
206                story_message: Some(msg),
207                ..Default::default()
208            },
209            Self::PniSignatureMessage(msg) => crate::proto::Content {
210                pni_signature_message: Some(msg),
211                ..Default::default()
212            },
213            Self::EditMessage(msg) => crate::proto::Content {
214                edit_message: Some(msg),
215                ..Default::default()
216            },
217        }
218    }
219}
220
221macro_rules! impl_from_for_content_body {
222    ($enum:ident ($t:ty)) => {
223        impl From<$t> for ContentBody {
224            fn from(inner: $t) -> ContentBody {
225                ContentBody::$enum(inner)
226            }
227        }
228    };
229}
230
231impl_from_for_content_body!(NullMessage(NullMessage));
232impl_from_for_content_body!(DataMessage(DataMessage));
233impl_from_for_content_body!(SynchronizeMessage(SyncMessage));
234impl_from_for_content_body!(CallMessage(CallMessage));
235impl_from_for_content_body!(ReceiptMessage(ReceiptMessage));
236impl_from_for_content_body!(TypingMessage(TypingMessage));
237// impl_from_for_content_body!(SenderKeyDistributionMessage(
238//     SenderKeyDistributionMessage
239// ));
240// impl_from_for_content_body!(DecryptionErrorMessage(DecryptionErrorMessage));
241impl_from_for_content_body!(StoryMessage(StoryMessage));
242impl_from_for_content_body!(PniSignatureMessage(PniSignatureMessage));
243impl_from_for_content_body!(EditMessage(EditMessage));