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.as_ref(),
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 message (`content`) to an address (`recipient`).
530    #[tracing::instrument(
531        level = "trace",
532        skip(self, unidentified_access, content_body, recipient),
533        fields(unidentified_access = unidentified_access.is_some(), recipient = recipient.service_id_string()),
534    )]
535    async fn try_send_message(
536        &mut self,
537        recipient: ServiceId,
538        mut unidentified_access: Option<&UnidentifiedAccess>,
539        content_body: &ContentBody,
540        timestamp: u64,
541        include_pni_signature: bool,
542        online: bool,
543    ) -> SendMessageResult {
544        trace!("trying to send a message");
545
546        use prost::Message;
547
548        let mut content = content_body.clone().into_proto();
549        if include_pni_signature {
550            content.pni_signature_message = Some(self.create_pni_signature()?);
551        }
552
553        let content_bytes = content.encode_to_vec();
554
555        let mut rng = rng();
556
557        for _ in 0..4u8 {
558            let Some(EncryptedMessages {
559                messages,
560                used_identity_key,
561            }) = self
562                .create_encrypted_messages(
563                    &recipient,
564                    unidentified_access.map(|x| &x.certificate),
565                    &content_bytes,
566                )
567                .await?
568            else {
569                // this can happen for example when a device is primary, without any secondaries
570                // and we send a message to ourselves (which is only a SyncMessage { sent: ... })
571                // addressed to self
572                return Err(MessageSenderError::NoMessagesToSend);
573            };
574
575            let messages = OutgoingPushMessages {
576                destination: recipient,
577                timestamp,
578                messages,
579                online,
580            };
581
582            let send = if let Some(unidentified) = &unidentified_access {
583                tracing::debug!("sending via unidentified");
584                self.unidentified_ws
585                    .send_messages_unidentified(messages, unidentified)
586                    .await
587            } else {
588                tracing::debug!("sending identified");
589                self.identified_ws.send_messages(messages).await
590            };
591
592            match send {
593                Ok(SendMessageResponse { needs_sync }) => {
594                    tracing::debug!("message sent!");
595                    return Ok(SentMessage {
596                        recipient,
597                        used_identity_key,
598                        unidentified: unidentified_access.is_some(),
599                        needs_sync,
600                    });
601                },
602                Err(ServiceError::Unauthorized)
603                    if unidentified_access.is_some() =>
604                {
605                    tracing::trace!("unauthorized error using unidentified; retry over identified");
606                    unidentified_access = None;
607                },
608                Err(ServiceError::MismatchedDevicesException(ref m)) => {
609                    tracing::debug!("{:?}", m);
610                    for extra_device_id in &m.extra_devices {
611                        tracing::debug!(
612                            "dropping session with device {}",
613                            extra_device_id
614                        );
615                        self.protocol_store
616                            .delete_service_addr_device_session(
617                                &recipient
618                                    .to_protocol_address(*extra_device_id)?,
619                            )
620                            .await?;
621                    }
622
623                    for missing_device_id in &m.missing_devices {
624                        tracing::debug!(
625                            "creating session with missing device {}",
626                            missing_device_id
627                        );
628                        let remote_address = recipient
629                            .to_protocol_address(*missing_device_id)?;
630                        let pre_key = self
631                            .identified_ws
632                            .get_pre_key(&recipient, *missing_device_id)
633                            .await?;
634
635                        process_prekey_bundle(
636                            &remote_address,
637                            &self
638                                .local_aci
639                                .to_protocol_address(self.device_id)
640                                .expect("valid device id"),
641                            &mut self.protocol_store.clone(),
642                            &mut self.protocol_store,
643                            &pre_key,
644                            SystemTime::now(),
645                            &mut rng,
646                        )
647                        .await
648                        .map_err(|e| {
649                            error!("failed to create session: {}", e);
650                            MessageSenderError::UntrustedIdentity {
651                                address: recipient,
652                            }
653                        })?;
654                    }
655                },
656                Err(ServiceError::StaleDevices(ref m)) => {
657                    tracing::debug!("{:?}", m);
658                    for extra_device_id in &m.stale_devices {
659                        tracing::debug!(
660                            "dropping session with device {}",
661                            extra_device_id
662                        );
663                        self.protocol_store
664                            .delete_service_addr_device_session(
665                                &recipient
666                                    .to_protocol_address(*extra_device_id)?,
667                            )
668                            .await?;
669                    }
670                },
671                Err(ServiceError::ProofRequiredError(ref p)) => {
672                    tracing::debug!("{:?}", p);
673                    return Err(MessageSenderError::ProofRequired {
674                        token: p.token.clone(),
675                        options: p.options.clone(),
676                    });
677                },
678                Err(ServiceError::NotFoundError) => {
679                    tracing::debug!("Not found when sending a message");
680                    return Err(MessageSenderError::NotFound {
681                        service_id: recipient,
682                    });
683                },
684                Err(e) => {
685                    tracing::debug!(
686                        "Default error handler for ws.send_messages: {}",
687                        e
688                    );
689                    return Err(MessageSenderError::ServiceError(e));
690                },
691            }
692        }
693
694        Err(MessageSenderError::MaximumRetriesLimitExceeded)
695    }
696
697    /// Upload contact details to the CDN and send a sync message
698    #[tracing::instrument(
699        skip(self, unidentified_access, contacts, recipient),
700        fields(unidentified_access = unidentified_access.is_some(), recipient = recipient.service_id_string()),
701    )]
702    pub async fn send_contact_details<Contacts>(
703        &mut self,
704        recipient: &ServiceId,
705        unidentified_access: Option<UnidentifiedAccess>,
706        // XXX It may be interesting to use an intermediary type,
707        //     instead of ContactDetails directly,
708        //     because it allows us to add the avatar content.
709        contacts: Contacts,
710        online: bool,
711        complete: bool,
712    ) -> Result<(), MessageSenderError>
713    where
714        Contacts: IntoIterator<Item = ContactDetails>,
715    {
716        let ptr = self.upload_contact_details(contacts).await?;
717
718        let msg = SyncMessage {
719            content: Some(crate::proto::sync_message::Content::Contacts(
720                sync_message::Contacts {
721                    blob: Some(ptr),
722                    complete: Some(complete),
723                },
724            )),
725            ..SyncMessage::with_padding(&mut rng())
726        };
727
728        self.send_sync_message(msg).await?;
729
730        Ok(())
731    }
732
733    /// Send `MessageRequestResponse` synchronization message with either a recipient ACI or a GroupV2 ID
734    #[tracing::instrument(skip(self), fields(recipient = recipient.service_id_string()))]
735    pub async fn send_message_request_response(
736        &mut self,
737        recipient: &ServiceId,
738        thread: &ThreadIdentifier,
739        action: message_request_response::Type,
740    ) -> Result<(), MessageSenderError> {
741        let message_request_response = match thread {
742            ThreadIdentifier::Aci(aci) => {
743                tracing::debug!(
744                    "sending message request response {:?} for recipient {:?}",
745                    action,
746                    aci
747                );
748                MessageRequestResponse {
749                    thread_aci: Some(aci.to_string()),
750                    thread_aci_binary: Some(aci.into_bytes().to_vec()),
751                    group_id: None,
752                    r#type: Some(action.into()),
753                }
754            },
755            ThreadIdentifier::Group(id) => {
756                tracing::debug!(
757                    "sending message request response {:?} for group {:?}",
758                    action,
759                    id
760                );
761                MessageRequestResponse {
762                    thread_aci: None,
763                    thread_aci_binary: None,
764                    group_id: Some(id.to_vec()),
765                    r#type: Some(action.into()),
766                }
767            },
768        };
769
770        let msg = SyncMessage {
771            content: Some(
772                crate::proto::sync_message::Content::MessageRequestResponse(
773                    message_request_response,
774                ),
775            ),
776            ..SyncMessage::with_padding(&mut rng())
777        };
778
779        let ts = Utc::now().timestamp_millis() as u64;
780        self.send_message(recipient, None, msg, ts, false, false)
781            .await?;
782
783        Ok(())
784    }
785
786    /// Send a `SyncMessage` to own devices, if any.
787    pub async fn send_sync_message(
788        &mut self,
789        sync: impl Into<SyncMessage>,
790    ) -> Result<(), MessageSenderError> {
791        if self.is_multi_device().await {
792            let content = sync.into().into();
793            let timestamp = Utc::now().timestamp_millis() as u64;
794            debug!(
795                "sending multi-device sync message with content {content:?}"
796            );
797            self.try_send_message(
798                self.local_aci.into(),
799                None,
800                &content,
801                timestamp,
802                false,
803                false,
804            )
805            .await?;
806        }
807        Ok(())
808    }
809
810    /// Send a `SyncMessage` request message
811    #[tracing::instrument(skip(self))]
812    pub async fn send_sync_message_request(
813        &mut self,
814        recipient: &ServiceId,
815        request_type: sync_message::request::Type,
816    ) -> Result<(), MessageSenderError> {
817        if self.device_id == *DEFAULT_DEVICE_ID {
818            return Err(MessageSenderError::SendSyncMessageError(request_type));
819        }
820
821        let msg = SyncMessage {
822            content: Some(crate::proto::sync_message::Content::Request(
823                sync_message::Request {
824                    r#type: Some(request_type.into()),
825                },
826            )),
827            ..SyncMessage::with_padding(&mut rng())
828        };
829        self.send_sync_message(msg).await?;
830
831        Ok(())
832    }
833
834    #[tracing::instrument(level = "trace", skip(self))]
835    fn create_pni_signature(
836        &mut self,
837    ) -> Result<crate::proto::PniSignatureMessage, MessageSenderError> {
838        let mut rng = rng();
839        let signature = self
840            .pni_identity
841            .expect("PNI key set when PNI signature requested")
842            .sign_alternate_identity(
843                self.aci_identity.identity_key(),
844                &mut rng,
845            )?;
846        Ok(crate::proto::PniSignatureMessage {
847            pni: Some(self.local_pni.service_id_binary()),
848            signature: Some(signature.into()),
849        })
850    }
851
852    // Equivalent with `getEncryptedMessages`
853    #[tracing::instrument(
854        level = "trace",
855        skip(self, unidentified_access, content),
856        fields(unidentified_access = unidentified_access.is_some(), recipient = recipient.service_id_string()),
857    )]
858    async fn create_encrypted_messages(
859        &mut self,
860        recipient: &ServiceId,
861        unidentified_access: Option<&SenderCertificate>,
862        content: &[u8],
863    ) -> Result<Option<EncryptedMessages>, MessageSenderError> {
864        let mut messages = vec![];
865
866        let mut devices: HashSet<DeviceId> = self
867            .protocol_store
868            .get_sub_device_sessions(recipient)
869            .await?
870            .into_iter()
871            .collect();
872
873        // always send to the primary device no matter what
874        devices.insert(*DEFAULT_DEVICE_ID);
875
876        // never try to send messages to the sender device
877        match recipient {
878            ServiceId::Aci(aci) => {
879                if *aci == self.local_aci {
880                    devices.remove(&self.device_id);
881                }
882            },
883            ServiceId::Pni(pni) => {
884                if *pni == self.local_pni {
885                    devices.remove(&self.device_id);
886                }
887            },
888        };
889
890        for device_id in devices {
891            trace!("sending message to device {}", device_id);
892            // `create_encrypted_message` may fail with `SessionNotFound` if the session is corrupted;
893            // see https://github.com/whisperfish/libsignal-client/commit/601454d20.
894            // If this happens, delete the session and retry.
895            for _attempt in 0..2 {
896                match self
897                    .create_encrypted_message(
898                        recipient,
899                        unidentified_access,
900                        device_id,
901                        content,
902                    )
903                    .await
904                {
905                    Ok(message) => {
906                        messages.push(message);
907                        break;
908                    },
909                    Err(MessageSenderError::ServiceError(
910                        ServiceError::SignalProtocolError(
911                            SignalProtocolError::SessionNotFound(
912                                SessionNotFound {
913                                    address: Some(addr),
914                                    op,
915                                },
916                            ),
917                        ),
918                    )) => {
919                        // SessionNotFound is returned on certain session corruption.
920                        // Since delete_session *creates* a session if it doesn't exist,
921                        // the NotFound error is an indicator of session corruption.
922                        // Try to delete this session, if it gets succesfully deleted, retry.  Otherwise, fail.
923                        tracing::warn!("Potential session corruption for {}, deleting session", addr);
924                        match self.protocol_store.delete_session(&addr).await {
925                            Ok(()) => continue,
926                            Err(error) => {
927                                tracing::warn!(%error, %addr, "failed to delete session");
928                                return Err(
929                                    SignalProtocolError::SessionNotFound(
930                                        SessionNotFound::new(addr, op),
931                                    )
932                                    .into(),
933                                );
934                            },
935                        }
936                    },
937                    Err(e) => return Err(e),
938                }
939            }
940        }
941
942        if messages.is_empty() {
943            Ok(None)
944        } else {
945            Ok(Some(EncryptedMessages {
946                messages,
947                used_identity_key: self
948                    .protocol_store
949                    .get_identity(
950                        &recipient.to_protocol_address(*DEFAULT_DEVICE_ID),
951                    )
952                    .await?
953                    .ok_or(MessageSenderError::UntrustedIdentity {
954                        address: *recipient,
955                    })?,
956            }))
957        }
958    }
959
960    /// Equivalent to `getEncryptedMessage`
961    ///
962    /// When no session with the recipient exists, we need to create one.
963    #[tracing::instrument(
964        level = "trace",
965        skip(self, unidentified_access, content),
966        fields(unidentified_access = unidentified_access.is_some(), recipient = recipient.service_id_string()),
967    )]
968    pub(crate) async fn create_encrypted_message(
969        &mut self,
970        recipient: &ServiceId,
971        unidentified_access: Option<&SenderCertificate>,
972        device_id: DeviceId,
973        content: &[u8],
974    ) -> Result<OutgoingPushMessage, MessageSenderError> {
975        let recipient_protocol_address =
976            recipient.to_protocol_address(device_id);
977
978        tracing::trace!(
979            "encrypting message for {}",
980            recipient_protocol_address
981        );
982
983        // establish a session with the recipient/device if necessary
984        // no need to establish a session with ourselves (and our own current device)
985        if self
986            .protocol_store
987            .load_session(&recipient_protocol_address)
988            .await?
989            .is_none()
990        {
991            info!(
992                "establishing new session with {}",
993                recipient_protocol_address
994            );
995            let pre_keys = match self
996                .identified_ws
997                .get_pre_keys(recipient, device_id)
998                .await
999            {
1000                Ok(ok) => {
1001                    tracing::trace!("Get prekeys OK");
1002                    ok
1003                },
1004                Err(ServiceError::NotFoundError) => {
1005                    return Err(MessageSenderError::NotFound {
1006                        service_id: *recipient,
1007                    });
1008                },
1009                Err(e) => Err(e)?,
1010            };
1011
1012            let mut rng = rng();
1013
1014            for pre_key_bundle in pre_keys {
1015                if recipient == &self.local_aci
1016                    && self.device_id == pre_key_bundle.device_id()?
1017                {
1018                    trace!("not establishing a session with myself!");
1019                    continue;
1020                }
1021
1022                let pre_key_address = get_preferred_protocol_address(
1023                    &self.protocol_store,
1024                    recipient,
1025                    pre_key_bundle.device_id()?,
1026                )
1027                .await?;
1028
1029                process_prekey_bundle(
1030                    &pre_key_address,
1031                    &self
1032                        .local_aci
1033                        .to_protocol_address(self.device_id)
1034                        .expect("valid device id"),
1035                    &mut self.protocol_store.clone(),
1036                    &mut self.protocol_store,
1037                    &pre_key_bundle,
1038                    SystemTime::now(),
1039                    &mut rng,
1040                )
1041                .await?;
1042            }
1043        }
1044
1045        let message = self
1046            .cipher
1047            .encrypt(
1048                &recipient_protocol_address,
1049                unidentified_access,
1050                content,
1051                &mut rng(),
1052            )
1053            .instrument(tracing::trace_span!("encrypting message"))
1054            .await?;
1055
1056        Ok(message)
1057    }
1058
1059    fn create_multi_device_sent_transcript_content<'a>(
1060        &mut self,
1061        recipient: Option<&ServiceId>,
1062        content_body: ContentBody,
1063        timestamp: u64,
1064        send_message_results: impl IntoIterator<Item = &'a SendMessageResult>,
1065    ) -> Option<ContentBody> {
1066        use sync_message::sent::UnidentifiedDeliveryStatus;
1067        let (message, edit_message) = match content_body {
1068            ContentBody::DataMessage(m) => (Some(m), None),
1069            ContentBody::EditMessage(m) => (None, Some(m)),
1070            content_body => {
1071                tracing::trace!(?content_body, "not syncing to self");
1072                return None;
1073            },
1074        };
1075        let unidentified_status: Vec<UnidentifiedDeliveryStatus> =
1076            send_message_results
1077                .into_iter()
1078                .filter_map(|result| result.as_ref().ok())
1079                .map(|sent| {
1080                    let SentMessage {
1081                        recipient,
1082                        unidentified,
1083                        used_identity_key,
1084                        ..
1085                    } = sent;
1086                    UnidentifiedDeliveryStatus {
1087                        destination_service_id: Some(
1088                            recipient.service_id_string(),
1089                        ),
1090                        destination_service_id_binary: Some(
1091                            recipient.service_id_binary(),
1092                        ),
1093                        unidentified: Some(*unidentified),
1094                        destination_pni_identity_key: Some(
1095                            used_identity_key.serialize().into(),
1096                        ),
1097                    }
1098                })
1099                .collect();
1100        Some(ContentBody::SynchronizeMessage(SyncMessage {
1101            content: Some(sync_message::Content::Sent(sync_message::Sent {
1102                destination_service_id: recipient
1103                    .map(ServiceId::service_id_string),
1104                destination_service_id_binary: recipient
1105                    .map(ServiceId::service_id_binary),
1106                destination_e164: None,
1107                expiration_start_timestamp: message
1108                    .as_ref()
1109                    .and_then(|m| m.expire_timer)
1110                    .map(|_| timestamp),
1111                message,
1112                edit_message,
1113                timestamp: Some(timestamp),
1114                unidentified_status,
1115                ..Default::default()
1116            })),
1117            ..SyncMessage::with_padding(&mut rng())
1118        }))
1119    }
1120}