Skip to main content

libsignal_service/
sender.rs

1use std::{collections::HashSet, time::SystemTime};
2
3use chrono::prelude::*;
4use libsignal_core::{curve::CurveError, InvalidDeviceId};
5use libsignal_protocol::{
6    process_prekey_bundle, Aci, DeviceId, IdentityKey, IdentityKeyPair, Pni,
7    ProtocolStore, SenderCertificate, SenderKeyStore, ServiceId,
8    SessionNotFound, SignalProtocolError,
9};
10use rand::{rng, CryptoRng, Rng};
11use tracing::{debug, error, info, trace, warn};
12use tracing_futures::Instrument;
13use uuid::Uuid;
14use zkgroup::GROUP_IDENTIFIER_LEN;
15
16use crate::{
17    cipher::{get_preferred_protocol_address, ServiceCipher},
18    content::ContentBody,
19    proto::{
20        attachment_pointer::{
21            AttachmentIdentifier, Flags as AttachmentPointerFlags,
22        },
23        sync_message::{
24            self, message_request_response, MessageRequestResponse,
25        },
26        AttachmentPointer, SyncMessage,
27    },
28    push_service::*,
29    service_address::ServiceIdExt,
30    session_store::SessionStoreExt,
31    unidentified_access::UnidentifiedAccess,
32    utils::{serde_device_id, serde_service_id},
33    websocket::{self, SignalWebSocket},
34};
35
36pub use crate::proto::ContactDetails;
37
38#[derive(serde::Serialize, Debug)]
39#[serde(rename_all = "camelCase")]
40pub struct OutgoingPushMessage {
41    pub r#type: u32,
42    #[serde(with = "serde_device_id")]
43    pub destination_device_id: DeviceId,
44    pub destination_registration_id: u32,
45    pub content: String,
46}
47
48#[derive(serde::Serialize, Debug)]
49pub struct OutgoingPushMessages {
50    #[serde(with = "serde_service_id")]
51    pub destination: ServiceId,
52    pub timestamp: u64,
53    pub messages: Vec<OutgoingPushMessage>,
54    pub online: bool,
55}
56
57#[derive(serde::Deserialize, Debug)]
58#[serde(rename_all = "camelCase")]
59pub struct SendMessageResponse {
60    pub needs_sync: bool,
61}
62
63pub type SendMessageResult = Result<SentMessage, MessageSenderError>;
64
65#[derive(Debug, Clone)]
66pub struct SentMessage {
67    pub recipient: ServiceId,
68    pub used_identity_key: IdentityKey,
69    pub unidentified: bool,
70    pub needs_sync: bool,
71}
72
73/// Attachment specification to be used for uploading.
74///
75/// Loose equivalent of Java's `SignalServiceAttachmentStream`.
76#[derive(Debug, Default)]
77pub struct AttachmentSpec {
78    pub content_type: String,
79    pub length: usize,
80    pub file_name: Option<String>,
81    pub preview: Option<Vec<u8>>,
82    pub voice_note: Option<bool>,
83    pub borderless: Option<bool>,
84    pub width: Option<u32>,
85    pub height: Option<u32>,
86    pub caption: Option<String>,
87    pub blur_hash: Option<String>,
88}
89
90#[derive(Clone)]
91pub struct MessageSender<S> {
92    identified_ws: SignalWebSocket<websocket::Identified>,
93    unidentified_ws: SignalWebSocket<websocket::Unidentified>,
94    service: PushService,
95    cipher: ServiceCipher<S>,
96    protocol_store: S,
97    local_aci: Aci,
98    local_pni: Pni,
99    aci_identity: IdentityKeyPair,
100    pni_identity: Option<IdentityKeyPair>,
101    device_id: DeviceId,
102}
103
104#[derive(thiserror::Error, Debug)]
105pub enum AttachmentUploadError {
106    #[error("{0}")]
107    ServiceError(#[from] ServiceError),
108
109    #[error("Could not read attachment contents")]
110    IoError(#[from] std::io::Error),
111}
112
113#[derive(thiserror::Error, Debug)]
114pub enum MessageSenderError {
115    #[error("service error: {0}")]
116    ServiceError(#[from] ServiceError),
117
118    #[error("protocol error: {0}")]
119    ProtocolError(#[from] SignalProtocolError),
120
121    #[error("invalid private key: {0}")]
122    InvalidPrivateKey(#[from] CurveError),
123
124    #[error("invalid device ID: {0}")]
125    InvalidDeviceId(#[from] InvalidDeviceId),
126
127    #[error("Failed to upload attachment {0}")]
128    AttachmentUploadError(#[from] AttachmentUploadError),
129
130    #[error("primary device can't send sync message {0:?}")]
131    SendSyncMessageError(sync_message::request::Type),
132
133    #[error("Untrusted identity key with {address:?}")]
134    UntrustedIdentity { address: ServiceId },
135
136    #[error("Exceeded maximum number of retries")]
137    MaximumRetriesLimitExceeded,
138
139    #[error("Proof of type {options:?} required using token {token}")]
140    ProofRequired { token: String, options: Vec<String> },
141
142    #[error("Recipient not found: {service_id:?}")]
143    NotFound { service_id: ServiceId },
144
145    #[error("no messages were encrypted: this should not really happen and most likely implies a logic error")]
146    NoMessagesToSend,
147}
148
149pub type GroupV2Id = [u8; GROUP_IDENTIFIER_LEN];
150
151#[derive(Debug)]
152pub enum ThreadIdentifier {
153    Aci(Uuid),
154    Group(GroupV2Id),
155}
156
157#[derive(Debug)]
158pub struct EncryptedMessages {
159    messages: Vec<OutgoingPushMessage>,
160    used_identity_key: IdentityKey,
161}
162
163impl<S> MessageSender<S>
164where
165    S: ProtocolStore + SenderKeyStore + SessionStoreExt + Sync + Clone,
166{
167    #[allow(clippy::too_many_arguments)]
168    pub fn new(
169        identified_ws: SignalWebSocket<websocket::Identified>,
170        unidentified_ws: SignalWebSocket<websocket::Unidentified>,
171        service: PushService,
172        cipher: ServiceCipher<S>,
173        protocol_store: S,
174        local_aci: impl Into<Aci>,
175        local_pni: impl Into<Pni>,
176        aci_identity: IdentityKeyPair,
177        pni_identity: Option<IdentityKeyPair>,
178        device_id: DeviceId,
179    ) -> Self {
180        MessageSender {
181            service,
182            identified_ws,
183            unidentified_ws,
184            cipher,
185            protocol_store,
186            local_aci: local_aci.into(),
187            local_pni: local_pni.into(),
188            aci_identity,
189            pni_identity,
190            device_id,
191        }
192    }
193
194    /// Encrypts and uploads an attachment
195    ///
196    /// Contents are accepted as an owned, plain text Vec, because encryption happens in-place.
197    #[tracing::instrument(skip(self, contents, csprng), fields(size = contents.len()))]
198    pub async fn upload_attachment<R: Rng + CryptoRng>(
199        &mut self,
200        spec: AttachmentSpec,
201        mut contents: Vec<u8>,
202        csprng: &mut R,
203    ) -> Result<AttachmentPointer, AttachmentUploadError> {
204        let len = contents.len();
205        // Encrypt
206        let (key, iv) = {
207            let mut key = [0u8; 64];
208            let mut iv = [0u8; 16];
209            csprng.fill_bytes(&mut key);
210            csprng.fill_bytes(&mut iv);
211            (key, iv)
212        };
213
214        // Padded length uses an exponential bracketting thingy.
215        // If you want to see how it looks:
216        // https://www.wolframalpha.com/input/?i=plot+floor%281.05%5Eceil%28log_1.05%28x%29%29%29+for+x+from+0+to+5000000
217        let padded_len: usize = {
218            // Java:
219            // return (int) Math.max(541, Math.floor(Math.pow(1.05, Math.ceil(Math.log(size) / Math.log(1.05)))))
220            std::cmp::max(
221                541,
222                1.05f64.powf((len as f64).log(1.05).ceil()).floor() as usize,
223            )
224        };
225        if padded_len < len {
226            error!(
227                "Padded len {} < len {}. Continuing with a privacy risk.",
228                padded_len, len
229            );
230        } else {
231            contents.resize(padded_len, 0);
232        }
233
234        tracing::trace_span!("encrypting attachment").in_scope(|| {
235            crate::attachment_cipher::encrypt_in_place(iv, key, &mut contents)
236        });
237
238        // Request upload attributes
239        // TODO: we can actually store the upload spec to be able to resume the upload later
240        // if it fails or stalls (= we should at least split the API calls so clients can decide what to do)
241        let attachment_upload_form = self
242            .service
243            .get_attachment_v4_upload_attributes()
244            .instrument(tracing::trace_span!("requesting upload attributes"))
245            .await?;
246
247        let resumable_upload_url = self
248            .service
249            .get_attachment_resumable_upload_url(&attachment_upload_form)
250            .await?;
251
252        let attachment_digest = self
253            .service
254            .upload_attachment_v4(
255                attachment_upload_form.cdn,
256                &resumable_upload_url,
257                contents.len() as u64,
258                attachment_upload_form.headers,
259                &mut std::io::Cursor::new(&contents),
260            )
261            .await?;
262
263        Ok(AttachmentPointer {
264            content_type: Some(spec.content_type),
265            key: Some(key.to_vec()),
266            size: Some(len as u32),
267            // thumbnail: Option<Vec<u8>>,
268            digest: Some(attachment_digest.digest),
269            file_name: spec.file_name,
270            flags: Some(
271                if spec.voice_note == Some(true) {
272                    AttachmentPointerFlags::VoiceMessage as u32
273                } else {
274                    0
275                } | if spec.borderless == Some(true) {
276                    AttachmentPointerFlags::Borderless as u32
277                } else {
278                    0
279                },
280            ),
281            width: spec.width,
282            height: spec.height,
283            caption: spec.caption,
284            blur_hash: spec.blur_hash,
285            upload_timestamp: Some(
286                SystemTime::now()
287                    .duration_since(SystemTime::UNIX_EPOCH)
288                    .expect("unix epoch in the past")
289                    .as_millis() as u64,
290            ),
291            cdn_number: Some(attachment_upload_form.cdn),
292            attachment_identifier: Some(AttachmentIdentifier::CdnKey(
293                attachment_upload_form.key,
294            )),
295            ..Default::default()
296        })
297    }
298
299    /// Upload contact details to the CDN
300    ///
301    /// Returns attachment ID and the attachment digest
302    #[tracing::instrument(skip(self, contacts))]
303    async fn upload_contact_details<Contacts>(
304        &mut self,
305        contacts: Contacts,
306    ) -> Result<AttachmentPointer, AttachmentUploadError>
307    where
308        Contacts: IntoIterator<Item = ContactDetails>,
309    {
310        use prost::Message;
311        let mut out = Vec::new();
312        for contact in contacts {
313            contact
314                .encode_length_delimited(&mut out)
315                .expect("infallible encoding");
316            // XXX add avatar here
317        }
318
319        let spec = AttachmentSpec {
320            content_type: "application/octet-stream".into(),
321            length: out.len(),
322            file_name: None,
323            preview: None,
324            voice_note: None,
325            borderless: None,
326            width: None,
327            height: None,
328            caption: None,
329            blur_hash: None,
330        };
331        self.upload_attachment(spec, out, &mut rng()).await
332    }
333
334    /// Return whether we have to prepare sync messages for other devices
335    ///
336    /// - If we are the main registered device, and there are established sub-device sessions (linked clients), return true
337    /// - If we are a secondary linked device, return true
338    async fn is_multi_device(&self) -> bool {
339        if self.device_id == *DEFAULT_DEVICE_ID {
340            self.protocol_store
341                .get_sub_device_sessions(&self.local_aci.into())
342                .await
343                .is_ok_and(|s| !s.is_empty())
344        } else {
345            true
346        }
347    }
348
349    /// Send a message `content` to a single `recipient`.
350    #[tracing::instrument(
351        skip(self, unidentified_access, message),
352        fields(unidentified_access = unidentified_access.is_some(), recipient = recipient.service_id_string()),
353    )]
354    pub async fn send_message(
355        &mut self,
356        recipient: &ServiceId,
357        mut unidentified_access: Option<&UnidentifiedAccess>,
358        message: impl Into<ContentBody>,
359        timestamp: u64,
360        include_pni_signature: bool,
361        online: bool,
362    ) -> SendMessageResult {
363        let content_body = message.into();
364        let message_to_self = recipient == &self.local_aci;
365        let sync_message =
366            matches!(content_body, ContentBody::SynchronizeMessage(..));
367        let is_multi_device = self.is_multi_device().await;
368
369        // only send a sync message when sending to self and skip the rest of the process
370        if message_to_self && is_multi_device && !sync_message {
371            debug!("sending note to self");
372            if let Some(sync_message) = self
373                .create_multi_device_sent_transcript_content(
374                    Some(recipient),
375                    content_body,
376                    timestamp,
377                    None,
378                )
379            {
380                return self
381                    .try_send_message(
382                        *recipient,
383                        None,
384                        &sync_message,
385                        timestamp,
386                        include_pni_signature,
387                        online,
388                    )
389                    .await;
390            } else {
391                error!("could not create sync message from message to self");
392                return SendMessageResult::Err(
393                    MessageSenderError::NoMessagesToSend,
394                );
395            }
396        }
397
398        // sync messages are never sent as unidentified (reasons unclear), see: https://github.com/signalapp/Signal-Android/blob/main/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java#L779
399        if sync_message {
400            unidentified_access.take();
401        }
402
403        // try to send the original message to all the recipient's devices
404        let result = self
405            .try_send_message(
406                *recipient,
407                unidentified_access,
408                &content_body,
409                timestamp,
410                include_pni_signature,
411                online,
412            )
413            .await;
414
415        let needs_sync = match &result {
416            Ok(SentMessage { needs_sync, .. }) => *needs_sync,
417            _ => false,
418        };
419
420        if needs_sync || is_multi_device {
421            let sync_body = if sync_message {
422                Some(content_body)
423            } else {
424                // Only some ContentBody types are syncable to self,
425                // not getting a content body to sync is not an error.
426                self.create_multi_device_sent_transcript_content(
427                    Some(recipient),
428                    content_body,
429                    timestamp,
430                    Some(&result),
431                )
432            };
433            if let Some(body) = sync_body {
434                debug!("sending multi-device sync message");
435                self.try_send_message(
436                    self.local_aci.into(),
437                    None,
438                    &body,
439                    timestamp,
440                    false,
441                    false,
442                )
443                .await?;
444            }
445        }
446
447        result
448    }
449
450    /// Send a message to the recipients in a group.
451    ///
452    /// Recipients are a list of tuples, each containing:
453    /// - The recipient's address
454    /// - The recipient's unidentified access
455    /// - Whether the recipient requires a PNI signature
456    #[tracing::instrument(
457        skip(self, recipients, message),
458        fields(recipients = recipients.as_ref().len()),
459    )]
460    pub async fn send_message_to_group(
461        &mut self,
462        recipients: impl AsRef<[(ServiceId, Option<UnidentifiedAccess>, bool)]>,
463        message: impl Into<ContentBody>,
464        timestamp: u64,
465        online: bool,
466    ) -> Vec<SendMessageResult> {
467        let content_body: ContentBody = message.into();
468        let mut results = vec![];
469
470        let mut needs_sync_in_results = false;
471
472        for (recipient, unidentified_access, include_pni_signature) in
473            recipients.as_ref()
474        {
475            let result = self
476                .try_send_message(
477                    *recipient,
478                    unidentified_access.as_ref(),
479                    &content_body,
480                    timestamp,
481                    *include_pni_signature,
482                    online,
483                )
484                .await;
485
486            match result {
487                Ok(SentMessage { needs_sync, .. }) if needs_sync => {
488                    needs_sync_in_results = true;
489                },
490                _ => (),
491            };
492
493            results.push(result);
494        }
495
496        // we only need to send a synchronization message once
497        if needs_sync_in_results || self.is_multi_device().await {
498            if let Some(sync_message) = self
499                .create_multi_device_sent_transcript_content(
500                    None,
501                    content_body.clone(),
502                    timestamp,
503                    &results,
504                )
505            {
506                // Note: the result of sending a sync message is not included in results
507                // See Signal Android `SignalServiceMessageSender.java:2817`
508                if let Err(error) = self
509                    .try_send_message(
510                        self.local_aci.into(),
511                        None,
512                        &sync_message,
513                        timestamp,
514                        false, // XXX: maybe the sync device does want a PNI signature?
515                        false,
516                    )
517                    .await
518                {
519                    error!(%error, "failed to send a synchronization message");
520                }
521            } else {
522                error!("could not create sync message from a group message")
523            }
524        }
525
526        results
527    }
528
529    /// Send a sender-key retry receipt
530    ///
531    /// The receipt goes out as a regular encrypted (or sealed-sender encrypted,
532    /// when `unidentified_access` is supplied) `Content`, so it rides the same
533    /// fan-out, session repair, and retry machinery as any other message.
534    ///
535    /// Divergences from the official clients:
536    /// - Official clients deliver retry receipts as unencrypted `PlaintextContent`;
537    ///   receiving clients accept the DEM inside an encrypted `Content` too.
538    /// - Under sealed sender they use `ContentHint::Implicit` and attach the
539    ///   group id; our sealed path hardcodes `ContentHint::Default` without a
540    ///   group id (see `sealed_sender_encrypt`).  Harmless in practice: the
541    ///   receipt's original sender finds its send-log entry by timestamp.
542    #[tracing::instrument(
543        skip(self, unidentified_access),
544        fields(recipient = recipient.service_id_string(), unidentified_send = unidentified_access.is_some(), failed_timestamp),
545    )]
546    pub async fn send_sender_key_decryption_error_message(
547        &mut self,
548        recipient: &ServiceId,
549        unidentified_access: Option<&UnidentifiedAccess>,
550        failed_timestamp: u64,
551        failed_device: DeviceId,
552    ) -> Result<(), MessageSenderError> {
553        info!("sending retry receipt/decryption error");
554
555        // The absent `ratchet_key` marks this as a sender-key failure.
556        // This function assumes the 1:1 session is intact, and hence tries to
557        // transmit the DME through the 1:1 encrypted (sealed, if available) channel.
558        //
559        // A DME for a 1:1 decrypt failure carries `ratchet_key`
560        // and must be deliverable *without* depending on the session under
561        // repair; upstream sends those as unencrypted `PlaintextContent`
562        // (or sealed USMC) envelopes.  Implement that case as a sibling
563        // function producing plaintext envelopes over this same fan-out.
564        let content_body = ContentBody::DecryptionErrorMessage(
565            crate::proto::DecryptionErrorMessage {
566                ratchet_key: None,
567                timestamp: Some(failed_timestamp),
568                device_id: Some(failed_device.into()),
569            },
570        );
571
572        self.try_send_message(
573            *recipient,
574            unidentified_access,
575            &content_body,
576            Utc::now().timestamp_millis() as u64,
577            false,
578            false,
579        )
580        .await?;
581
582        Ok(())
583    }
584
585    /// Send a message (`content`) to an address (`recipient`).
586    #[tracing::instrument(
587        level = "trace",
588        skip(self, unidentified_access, content_body, recipient),
589        fields(unidentified_access = unidentified_access.is_some(), recipient = recipient.service_id_string()),
590    )]
591    async fn try_send_message(
592        &mut self,
593        recipient: ServiceId,
594        mut unidentified_access: Option<&UnidentifiedAccess>,
595        content_body: &ContentBody,
596        timestamp: u64,
597        include_pni_signature: bool,
598        online: bool,
599    ) -> SendMessageResult {
600        trace!("trying to send a message");
601
602        use prost::Message;
603
604        let mut content = content_body.clone().into_proto();
605        if include_pni_signature {
606            content.pni_signature_message = Some(self.create_pni_signature()?);
607        }
608
609        let content_bytes = content.encode_to_vec();
610
611        let mut rng = rng();
612
613        for _ in 0..4u8 {
614            let Some(EncryptedMessages {
615                messages,
616                used_identity_key,
617            }) = self
618                .create_encrypted_messages(
619                    &recipient,
620                    unidentified_access.map(|x| &x.certificate),
621                    &content_bytes,
622                )
623                .await?
624            else {
625                // this can happen for example when a device is primary, without any secondaries
626                // and we send a message to ourselves (which is only a SyncMessage { sent: ... })
627                // addressed to self
628                return Err(MessageSenderError::NoMessagesToSend);
629            };
630
631            let messages = OutgoingPushMessages {
632                destination: recipient,
633                timestamp,
634                messages,
635                online,
636            };
637
638            let send = if let Some(unidentified) = &unidentified_access {
639                tracing::debug!("sending via unidentified");
640                self.unidentified_ws
641                    .send_messages_unidentified(messages, unidentified)
642                    .await
643            } else {
644                tracing::debug!("sending identified");
645                self.identified_ws.send_messages(messages).await
646            };
647
648            match send {
649                Ok(SendMessageResponse { needs_sync }) => {
650                    tracing::debug!("message sent!");
651                    return Ok(SentMessage {
652                        recipient,
653                        used_identity_key,
654                        unidentified: unidentified_access.is_some(),
655                        needs_sync,
656                    });
657                },
658                Err(ServiceError::Unauthorized)
659                    if unidentified_access.is_some() =>
660                {
661                    tracing::trace!("unauthorized error using unidentified; retry over identified");
662                    unidentified_access = None;
663                },
664                Err(ServiceError::MismatchedDevicesException(ref m)) => {
665                    tracing::debug!("{:?}", m);
666                    for extra_device_id in &m.extra_devices {
667                        tracing::debug!(
668                            "dropping session with device {}",
669                            extra_device_id
670                        );
671                        self.protocol_store
672                            .delete_service_addr_device_session(
673                                &recipient
674                                    .to_protocol_address(*extra_device_id)?,
675                            )
676                            .await?;
677                    }
678
679                    for missing_device_id in &m.missing_devices {
680                        tracing::debug!(
681                            "creating session with missing device {}",
682                            missing_device_id
683                        );
684                        let remote_address = recipient
685                            .to_protocol_address(*missing_device_id)?;
686                        let pre_key = self
687                            .identified_ws
688                            .get_pre_key(&recipient, *missing_device_id)
689                            .await?;
690
691                        process_prekey_bundle(
692                            &remote_address,
693                            &self
694                                .local_aci
695                                .to_protocol_address(self.device_id)
696                                .expect("valid device id"),
697                            &mut self.protocol_store.clone(),
698                            &mut self.protocol_store,
699                            &pre_key,
700                            SystemTime::now(),
701                            &mut rng,
702                        )
703                        .await
704                        .map_err(|e| {
705                            error!("failed to create session: {}", e);
706                            MessageSenderError::UntrustedIdentity {
707                                address: recipient,
708                            }
709                        })?;
710                    }
711                },
712                Err(ServiceError::StaleDevices(ref m)) => {
713                    tracing::debug!("{:?}", m);
714                    for extra_device_id in &m.stale_devices {
715                        tracing::debug!(
716                            "dropping session with device {}",
717                            extra_device_id
718                        );
719                        self.protocol_store
720                            .delete_service_addr_device_session(
721                                &recipient
722                                    .to_protocol_address(*extra_device_id)?,
723                            )
724                            .await?;
725                    }
726                },
727                Err(ServiceError::ProofRequiredError(ref p)) => {
728                    tracing::debug!("{:?}", p);
729                    return Err(MessageSenderError::ProofRequired {
730                        token: p.token.clone(),
731                        options: p.options.clone(),
732                    });
733                },
734                Err(ServiceError::UnregisteredRecipient) => {
735                    tracing::debug!(?recipient, "recipient is not registered");
736                    return Err(MessageSenderError::NotFound {
737                        service_id: recipient,
738                    });
739                },
740                Err(e) => {
741                    tracing::debug!(
742                        "Default error handler for ws.send_messages: {}",
743                        e
744                    );
745                    return Err(MessageSenderError::ServiceError(e));
746                },
747            }
748        }
749
750        Err(MessageSenderError::MaximumRetriesLimitExceeded)
751    }
752
753    /// Upload contact details to the CDN and send a sync message
754    #[tracing::instrument(
755        skip(self, unidentified_access, contacts, recipient),
756        fields(unidentified_access = unidentified_access.is_some(), recipient = recipient.service_id_string()),
757    )]
758    pub async fn send_contact_details<Contacts>(
759        &mut self,
760        recipient: &ServiceId,
761        unidentified_access: Option<UnidentifiedAccess>,
762        // XXX It may be interesting to use an intermediary type,
763        //     instead of ContactDetails directly,
764        //     because it allows us to add the avatar content.
765        contacts: Contacts,
766        online: bool,
767        complete: bool,
768    ) -> Result<(), MessageSenderError>
769    where
770        Contacts: IntoIterator<Item = ContactDetails>,
771    {
772        let ptr = self.upload_contact_details(contacts).await?;
773
774        let msg = SyncMessage {
775            content: Some(crate::proto::sync_message::Content::Contacts(
776                sync_message::Contacts {
777                    blob: Some(ptr),
778                    complete: Some(complete),
779                },
780            )),
781            ..SyncMessage::with_padding(&mut rng())
782        };
783
784        self.send_sync_message(msg).await?;
785
786        Ok(())
787    }
788
789    /// Send `MessageRequestResponse` synchronization message with either a recipient ACI or a GroupV2 ID
790    #[tracing::instrument(skip(self), fields(recipient = recipient.service_id_string()))]
791    pub async fn send_message_request_response(
792        &mut self,
793        recipient: &ServiceId,
794        thread: &ThreadIdentifier,
795        action: message_request_response::Type,
796    ) -> Result<(), MessageSenderError> {
797        let message_request_response = match thread {
798            ThreadIdentifier::Aci(aci) => {
799                tracing::debug!(
800                    "sending message request response {:?} for recipient {:?}",
801                    action,
802                    aci
803                );
804                MessageRequestResponse {
805                    thread_aci: Some(aci.to_string()),
806                    thread_aci_binary: Some(aci.into_bytes().to_vec()),
807                    group_id: None,
808                    r#type: Some(action.into()),
809                }
810            },
811            ThreadIdentifier::Group(id) => {
812                tracing::debug!(
813                    "sending message request response {:?} for group {:?}",
814                    action,
815                    id
816                );
817                MessageRequestResponse {
818                    thread_aci: None,
819                    thread_aci_binary: None,
820                    group_id: Some(id.to_vec()),
821                    r#type: Some(action.into()),
822                }
823            },
824        };
825
826        let msg = SyncMessage {
827            content: Some(
828                crate::proto::sync_message::Content::MessageRequestResponse(
829                    message_request_response,
830                ),
831            ),
832            ..SyncMessage::with_padding(&mut rng())
833        };
834
835        let ts = Utc::now().timestamp_millis() as u64;
836        self.send_message(recipient, None, msg, ts, false, false)
837            .await?;
838
839        Ok(())
840    }
841
842    /// Send a `SyncMessage` to own devices, if any.
843    pub async fn send_sync_message(
844        &mut self,
845        sync: impl Into<SyncMessage>,
846    ) -> Result<(), MessageSenderError> {
847        if self.is_multi_device().await {
848            let content = sync.into().into();
849            let timestamp = Utc::now().timestamp_millis() as u64;
850            debug!(
851                "sending multi-device sync message with content {content:?}"
852            );
853            self.try_send_message(
854                self.local_aci.into(),
855                None,
856                &content,
857                timestamp,
858                false,
859                false,
860            )
861            .await?;
862        }
863        Ok(())
864    }
865
866    /// Send a `SyncMessage` request message
867    #[tracing::instrument(skip(self))]
868    pub async fn send_sync_message_request(
869        &mut self,
870        recipient: &ServiceId,
871        request_type: sync_message::request::Type,
872    ) -> Result<(), MessageSenderError> {
873        if self.device_id == *DEFAULT_DEVICE_ID {
874            return Err(MessageSenderError::SendSyncMessageError(request_type));
875        }
876
877        let msg = SyncMessage {
878            content: Some(crate::proto::sync_message::Content::Request(
879                sync_message::Request {
880                    r#type: Some(request_type.into()),
881                },
882            )),
883            ..SyncMessage::with_padding(&mut rng())
884        };
885        self.send_sync_message(msg).await?;
886
887        Ok(())
888    }
889
890    #[tracing::instrument(level = "trace", skip(self))]
891    fn create_pni_signature(
892        &mut self,
893    ) -> Result<crate::proto::PniSignatureMessage, MessageSenderError> {
894        let mut rng = rng();
895        let signature = self
896            .pni_identity
897            .expect("PNI key set when PNI signature requested")
898            .sign_alternate_identity(
899                self.aci_identity.identity_key(),
900                &mut rng,
901            )?;
902        Ok(crate::proto::PniSignatureMessage {
903            pni: Some(self.local_pni.service_id_binary()),
904            signature: Some(signature.into()),
905        })
906    }
907
908    // Equivalent with `getEncryptedMessages`
909    #[tracing::instrument(
910        level = "trace",
911        skip(self, unidentified_access, content),
912        fields(unidentified_access = unidentified_access.is_some(), recipient = recipient.service_id_string()),
913    )]
914    async fn create_encrypted_messages(
915        &mut self,
916        recipient: &ServiceId,
917        unidentified_access: Option<&SenderCertificate>,
918        content: &[u8],
919    ) -> Result<Option<EncryptedMessages>, MessageSenderError> {
920        let mut messages = vec![];
921
922        let mut devices: HashSet<DeviceId> = self
923            .protocol_store
924            .get_sub_device_sessions(recipient)
925            .await?
926            .into_iter()
927            .collect();
928
929        // always send to the primary device no matter what
930        devices.insert(*DEFAULT_DEVICE_ID);
931
932        // never try to send messages to the sender device
933        match recipient {
934            ServiceId::Aci(aci) => {
935                if *aci == self.local_aci {
936                    devices.remove(&self.device_id);
937                }
938            },
939            ServiceId::Pni(pni) => {
940                if *pni == self.local_pni {
941                    devices.remove(&self.device_id);
942                }
943            },
944        };
945
946        for device_id in devices {
947            trace!("sending message to device {}", device_id);
948            // `create_encrypted_message` may fail with `SessionNotFound` if the session is corrupted;
949            // see https://github.com/whisperfish/libsignal-client/commit/601454d20.
950            // If this happens, delete the session and retry.
951            for _attempt in 0..2 {
952                match self
953                    .create_encrypted_message(
954                        recipient,
955                        unidentified_access,
956                        device_id,
957                        content,
958                    )
959                    .await
960                {
961                    Ok(message) => {
962                        messages.push(message);
963                        break;
964                    },
965                    Err(MessageSenderError::ServiceError(
966                        ServiceError::SignalProtocolError(
967                            SignalProtocolError::SessionNotFound(
968                                SessionNotFound {
969                                    address: Some(addr),
970                                    op,
971                                },
972                            ),
973                        ),
974                    )) => {
975                        // SessionNotFound is returned on certain session corruption.
976                        // Since delete_session *creates* a session if it doesn't exist,
977                        // the NotFound error is an indicator of session corruption.
978                        // Try to delete this session, if it gets succesfully deleted, retry.  Otherwise, fail.
979                        tracing::warn!("Potential session corruption for {}, deleting session", addr);
980                        match self.protocol_store.delete_session(&addr).await {
981                            Ok(()) => continue,
982                            Err(error) => {
983                                tracing::warn!(%error, %addr, "failed to delete session");
984                                return Err(
985                                    SignalProtocolError::SessionNotFound(
986                                        SessionNotFound::new(addr, op),
987                                    )
988                                    .into(),
989                                );
990                            },
991                        }
992                    },
993                    Err(e) => return Err(e),
994                }
995            }
996        }
997
998        if messages.is_empty() {
999            Ok(None)
1000        } else {
1001            Ok(Some(EncryptedMessages {
1002                messages,
1003                used_identity_key: self
1004                    .protocol_store
1005                    .get_identity(
1006                        &recipient.to_protocol_address(*DEFAULT_DEVICE_ID),
1007                    )
1008                    .await?
1009                    .ok_or(MessageSenderError::UntrustedIdentity {
1010                        address: *recipient,
1011                    })?,
1012            }))
1013        }
1014    }
1015
1016    /// Equivalent to `getEncryptedMessage`
1017    ///
1018    /// When no session with the recipient exists, we need to create one.
1019    #[tracing::instrument(
1020        level = "trace",
1021        skip(self, unidentified_access, content),
1022        fields(unidentified_access = unidentified_access.is_some(), recipient = recipient.service_id_string()),
1023    )]
1024    pub(crate) async fn create_encrypted_message(
1025        &mut self,
1026        recipient: &ServiceId,
1027        unidentified_access: Option<&SenderCertificate>,
1028        device_id: DeviceId,
1029        content: &[u8],
1030    ) -> Result<OutgoingPushMessage, MessageSenderError> {
1031        let recipient_protocol_address =
1032            recipient.to_protocol_address(device_id);
1033
1034        tracing::trace!(
1035            "encrypting message for {}",
1036            recipient_protocol_address
1037        );
1038
1039        // establish a session with the recipient/device if necessary
1040        // no need to establish a session with ourselves (and our own current device)
1041        if self
1042            .protocol_store
1043            .load_session(&recipient_protocol_address)
1044            .await?
1045            .is_none()
1046        {
1047            info!(
1048                "establishing new session with {}",
1049                recipient_protocol_address
1050            );
1051            let pre_keys = match self
1052                .identified_ws
1053                .get_pre_keys(recipient, device_id)
1054                .await
1055            {
1056                Ok(ok) => {
1057                    tracing::trace!("Get prekeys OK");
1058                    ok
1059                },
1060                Err(ServiceError::NotFoundError) => {
1061                    return Err(MessageSenderError::NotFound {
1062                        service_id: *recipient,
1063                    });
1064                },
1065                Err(e) => Err(e)?,
1066            };
1067
1068            let mut rng = rng();
1069
1070            for pre_key_bundle in pre_keys {
1071                if recipient == &self.local_aci
1072                    && self.device_id == pre_key_bundle.device_id()?
1073                {
1074                    trace!("not establishing a session with myself!");
1075                    continue;
1076                }
1077
1078                let pre_key_address = get_preferred_protocol_address(
1079                    &self.protocol_store,
1080                    recipient,
1081                    pre_key_bundle.device_id()?,
1082                )
1083                .await?;
1084
1085                process_prekey_bundle(
1086                    &pre_key_address,
1087                    &self
1088                        .local_aci
1089                        .to_protocol_address(self.device_id)
1090                        .expect("valid device id"),
1091                    &mut self.protocol_store.clone(),
1092                    &mut self.protocol_store,
1093                    &pre_key_bundle,
1094                    SystemTime::now(),
1095                    &mut rng,
1096                )
1097                .await?;
1098            }
1099        }
1100
1101        let message = self
1102            .cipher
1103            .encrypt(
1104                &recipient_protocol_address,
1105                unidentified_access,
1106                content,
1107                &mut rng(),
1108            )
1109            .instrument(tracing::trace_span!("encrypting message"))
1110            .await?;
1111
1112        Ok(message)
1113    }
1114
1115    fn create_multi_device_sent_transcript_content<'a>(
1116        &mut self,
1117        recipient: Option<&ServiceId>,
1118        content_body: ContentBody,
1119        timestamp: u64,
1120        send_message_results: impl IntoIterator<Item = &'a SendMessageResult>,
1121    ) -> Option<ContentBody> {
1122        use sync_message::sent::UnidentifiedDeliveryStatus;
1123        let (message, edit_message) = match content_body {
1124            ContentBody::DataMessage(m) => (Some(m), None),
1125            ContentBody::EditMessage(m) => (None, Some(m)),
1126            content_body => {
1127                tracing::trace!(?content_body, "not syncing to self");
1128                return None;
1129            },
1130        };
1131        let unidentified_status: Vec<UnidentifiedDeliveryStatus> =
1132            send_message_results
1133                .into_iter()
1134                .filter_map(|result| result.as_ref().ok())
1135                .map(|sent| {
1136                    let SentMessage {
1137                        recipient,
1138                        unidentified,
1139                        used_identity_key,
1140                        ..
1141                    } = sent;
1142                    UnidentifiedDeliveryStatus {
1143                        destination_service_id: Some(
1144                            recipient.service_id_string(),
1145                        ),
1146                        destination_service_id_binary: Some(
1147                            recipient.service_id_binary(),
1148                        ),
1149                        unidentified: Some(*unidentified),
1150                        destination_pni_identity_key: Some(
1151                            used_identity_key.serialize().into(),
1152                        ),
1153                    }
1154                })
1155                .collect();
1156        Some(ContentBody::SynchronizeMessage(SyncMessage {
1157            content: Some(sync_message::Content::Sent(sync_message::Sent {
1158                destination_service_id: recipient
1159                    .map(ServiceId::service_id_string),
1160                destination_service_id_binary: recipient
1161                    .map(ServiceId::service_id_binary),
1162                destination_e164: None,
1163                expiration_start_timestamp: message
1164                    .as_ref()
1165                    .and_then(|m| m.expire_timer)
1166                    .map(|_| timestamp),
1167                message,
1168                edit_message,
1169                timestamp: Some(timestamp),
1170                unidentified_status,
1171                ..Default::default()
1172            })),
1173            ..SyncMessage::with_padding(&mut rng())
1174        }))
1175    }
1176}