libsignal_service/
content.rs

1use libsignal_protocol::{ProtocolAddress, ServiceId};
2use std::fmt;
3use uuid::Uuid;
4
5pub use crate::{
6    proto::{
7        attachment_pointer::Flags as AttachmentPointerFlags,
8        data_message::Flags as DataMessageFlags, data_message::Reaction,
9        group_context::Type as GroupType, sync_message, AttachmentPointer,
10        CallMessage, DataMessage, EditMessage, GroupContext, GroupContextV2,
11        NullMessage, PniSignatureMessage, ReceiptMessage, StoryMessage,
12        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: u32,
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(&self) -> ProtocolAddress {
54        self.sender.to_protocol_address(self.sender_device)
55    }
56}
57
58#[derive(Clone, Debug)]
59pub struct Content {
60    pub metadata: Metadata,
61    pub body: ContentBody,
62}
63
64impl Content {
65    pub fn from_body(body: impl Into<ContentBody>, metadata: Metadata) -> Self {
66        Self {
67            metadata,
68            body: body.into(),
69        }
70    }
71
72    /// Converts a proto::Content into a public Content, including metadata.
73    #[expect(clippy::result_large_err)]
74    pub fn from_proto(
75        p: crate::proto::Content,
76        metadata: Metadata,
77    ) -> Result<Self, ServiceError> {
78        // The Java version also assumes only one content type at a time.
79        // It's a bit sad that we cannot really match here, we've got no
80        // r#type() method.
81        // Allow the manual map (if let Some -> option.map(||)), because it
82        // reduces the git diff when more types would be added.
83        #[allow(clippy::manual_map)]
84        if let Some(msg) = p.data_message {
85            Ok(Self::from_body(msg, metadata))
86        } else if let Some(msg) = p.sync_message {
87            Ok(Self::from_body(msg, metadata))
88        } else if let Some(msg) = p.call_message {
89            Ok(Self::from_body(msg, metadata))
90        } else if let Some(msg) = p.receipt_message {
91            Ok(Self::from_body(msg, metadata))
92        } else if let Some(msg) = p.typing_message {
93            Ok(Self::from_body(msg, metadata))
94        // } else if let Some(msg) = p.sender_key_distribution_message {
95        //     Ok(Self::from_body(msg, metadata))
96        // } else if let Some(msg) = p.decryption_error_message {
97        //     Ok(Self::from_body(msg, metadata))
98        } else if let Some(msg) = p.story_message {
99            Ok(Self::from_body(msg, metadata))
100        } else if let Some(msg) = p.pni_signature_message {
101            Ok(Self::from_body(msg, metadata))
102        } else if let Some(msg) = p.edit_message {
103            Ok(Self::from_body(msg, metadata))
104        } else {
105            Err(ServiceError::UnsupportedContent)
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(_) => write!(f, "DecryptionErrorMessage"),
143            Self::StoryMessage(_) => write!(f, "StoryMessage"),
144            Self::PniSignatureMessage(_) => write!(f, "PniSignatureMessage"),
145            Self::EditMessage(_) => write!(f, "EditMessage"),
146        }
147    }
148}
149
150#[derive(Clone, Debug)]
151#[allow(clippy::large_enum_variant)]
152pub enum ContentBody {
153    NullMessage(NullMessage),
154    DataMessage(DataMessage),
155    SynchronizeMessage(SyncMessage),
156    CallMessage(CallMessage),
157    ReceiptMessage(ReceiptMessage),
158    TypingMessage(TypingMessage),
159    // SenderKeyDistributionMessage(SenderKeyDistributionMessage),
160    // DecryptionErrorMessage(DecryptionErrorMessage),
161    StoryMessage(StoryMessage),
162    PniSignatureMessage(PniSignatureMessage),
163    EditMessage(EditMessage),
164}
165
166impl ContentBody {
167    pub fn into_proto(self) -> crate::proto::Content {
168        match self {
169            Self::NullMessage(msg) => crate::proto::Content {
170                null_message: Some(msg),
171                ..Default::default()
172            },
173            Self::DataMessage(msg) => crate::proto::Content {
174                data_message: Some(msg),
175                ..Default::default()
176            },
177            Self::SynchronizeMessage(msg) => crate::proto::Content {
178                sync_message: Some(msg),
179                ..Default::default()
180            },
181            Self::CallMessage(msg) => crate::proto::Content {
182                call_message: Some(msg),
183                ..Default::default()
184            },
185            Self::ReceiptMessage(msg) => crate::proto::Content {
186                receipt_message: Some(msg),
187                ..Default::default()
188            },
189            Self::TypingMessage(msg) => crate::proto::Content {
190                typing_message: Some(msg),
191                ..Default::default()
192            },
193            // XXX Those two are serialized as Vec<u8> and I'm not currently sure how to handle
194            // them.
195            // Self::SenderKeyDistributionMessage(msg) => crate::proto::Content {
196            //     sender_key_distribution_message: Some(msg),
197            //     ..Default::default()
198            // },
199            // Self::DecryptionErrorMessage(msg) => crate::proto::Content {
200            //     decryption_error_message: Some(msg.serialized()),
201            //     ..Default::default()
202            // },
203            Self::StoryMessage(msg) => crate::proto::Content {
204                story_message: Some(msg),
205                ..Default::default()
206            },
207            Self::PniSignatureMessage(msg) => crate::proto::Content {
208                pni_signature_message: Some(msg),
209                ..Default::default()
210            },
211            Self::EditMessage(msg) => crate::proto::Content {
212                edit_message: Some(msg),
213                ..Default::default()
214            },
215        }
216    }
217}
218
219macro_rules! impl_from_for_content_body {
220    ($enum:ident ($t:ty)) => {
221        impl From<$t> for ContentBody {
222            fn from(inner: $t) -> ContentBody {
223                ContentBody::$enum(inner)
224            }
225        }
226    };
227}
228
229impl_from_for_content_body!(NullMessage(NullMessage));
230impl_from_for_content_body!(DataMessage(DataMessage));
231impl_from_for_content_body!(SynchronizeMessage(SyncMessage));
232impl_from_for_content_body!(CallMessage(CallMessage));
233impl_from_for_content_body!(ReceiptMessage(ReceiptMessage));
234impl_from_for_content_body!(TypingMessage(TypingMessage));
235// impl_from_for_content_body!(SenderKeyDistributionMessage(
236//     SenderKeyDistributionMessage
237// ));
238// impl_from_for_content_body!(DecryptionErrorMessage(DecryptionErrorMessage));
239impl_from_for_content_body!(StoryMessage(StoryMessage));
240impl_from_for_content_body!(PniSignatureMessage(PniSignatureMessage));
241impl_from_for_content_body!(EditMessage(EditMessage));