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 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 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 #[expect(clippy::result_large_err)]
76 pub fn from_proto(
77 p: crate::proto::Content,
78 metadata: Metadata,
79 ) -> Result<Self, ServiceError> {
80 #[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.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::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 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 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));
237impl_from_for_content_body!(StoryMessage(StoryMessage));
242impl_from_for_content_body!(PniSignatureMessage(PniSignatureMessage));
243impl_from_for_content_body!(EditMessage(EditMessage));