1 package org
.asamk
.signal
.manager
.helper
;
3 import com
.google
.protobuf
.ByteString
;
5 import org
.asamk
.signal
.manager
.api
.Contact
;
6 import org
.asamk
.signal
.manager
.api
.GroupId
;
7 import org
.asamk
.signal
.manager
.api
.GroupNotFoundException
;
8 import org
.asamk
.signal
.manager
.api
.GroupSendingNotAllowedException
;
9 import org
.asamk
.signal
.manager
.api
.NotAGroupMemberException
;
10 import org
.asamk
.signal
.manager
.api
.Profile
;
11 import org
.asamk
.signal
.manager
.api
.UnregisteredRecipientException
;
12 import org
.asamk
.signal
.manager
.groups
.GroupUtils
;
13 import org
.asamk
.signal
.manager
.internal
.SignalDependencies
;
14 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
15 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfo
;
16 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
17 import org
.asamk
.signal
.manager
.storage
.sendLog
.MessageSendLogEntry
;
18 import org
.signal
.libsignal
.protocol
.InvalidKeyException
;
19 import org
.signal
.libsignal
.protocol
.InvalidRegistrationIdException
;
20 import org
.signal
.libsignal
.protocol
.NoSessionException
;
21 import org
.signal
.libsignal
.protocol
.SignalProtocolAddress
;
22 import org
.signal
.libsignal
.protocol
.message
.DecryptionErrorMessage
;
23 import org
.slf4j
.Logger
;
24 import org
.slf4j
.LoggerFactory
;
25 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
26 import org
.whispersystems
.signalservice
.api
.crypto
.ContentHint
;
27 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
28 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
29 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
30 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
31 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
32 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEditMessage
;
33 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
34 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceTypingMessage
;
35 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
36 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
37 import org
.whispersystems
.signalservice
.api
.push
.DistributionId
;
38 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
39 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NotFoundException
;
40 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.ProofRequiredException
;
41 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.RateLimitException
;
42 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
43 import org
.whispersystems
.signalservice
.internal
.push
.exceptions
.InvalidUnidentifiedAccessHeaderException
;
45 import java
.io
.IOException
;
46 import java
.time
.Duration
;
47 import java
.util
.ArrayList
;
48 import java
.util
.HashSet
;
49 import java
.util
.List
;
51 import java
.util
.Optional
;
53 import java
.util
.concurrent
.TimeUnit
;
54 import java
.util
.concurrent
.atomic
.AtomicLong
;
56 public class SendHelper
{
58 private final static Logger logger
= LoggerFactory
.getLogger(SendHelper
.class);
60 private final SignalAccount account
;
61 private final SignalDependencies dependencies
;
62 private final Context context
;
64 public SendHelper(final Context context
) {
65 this.account
= context
.getAccount();
66 this.dependencies
= context
.getDependencies();
67 this.context
= context
;
71 * Send a single message to one recipient.
72 * The message is extended with the current expiration timer.
74 public SendMessageResult
sendMessage(
75 final SignalServiceDataMessage
.Builder messageBuilder
,
76 final RecipientId recipientId
,
77 Optional
<Long
> editTargetTimestamp
79 var contact
= account
.getContactStore().getContact(recipientId
);
80 if (contact
== null || !contact
.isProfileSharingEnabled()) {
81 final var contactBuilder
= contact
== null ? Contact
.newBuilder() : Contact
.newBuilder(contact
);
82 contact
= contactBuilder
.withProfileSharingEnabled(true).build();
83 account
.getContactStore().storeContact(recipientId
, contact
);
86 final var expirationTime
= contact
.getMessageExpirationTime();
87 messageBuilder
.withExpiration(expirationTime
);
89 if (!contact
.isBlocked()) {
90 final var profileKey
= account
.getProfileKey().serialize();
91 messageBuilder
.withProfileKey(profileKey
);
94 final var message
= messageBuilder
.build();
95 return sendMessage(message
, recipientId
, editTargetTimestamp
);
99 * Send a group message to the given group
100 * The message is extended with the current expiration timer for the group and the group context.
102 public List
<SendMessageResult
> sendAsGroupMessage(
103 SignalServiceDataMessage
.Builder messageBuilder
, GroupId groupId
, Optional
<Long
> editTargetTimestamp
104 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
, GroupSendingNotAllowedException
{
105 final var g
= getGroupForSending(groupId
);
106 return sendAsGroupMessage(messageBuilder
, g
, editTargetTimestamp
);
110 * Send a complete group message to the given recipients (should be current/old/new members)
111 * This method should only be used for create/update/quit group messages.
113 public List
<SendMessageResult
> sendGroupMessage(
114 final SignalServiceDataMessage message
,
115 final Set
<RecipientId
> recipientIds
,
116 final DistributionId distributionId
117 ) throws IOException
{
118 return sendGroupMessage(message
, recipientIds
, distributionId
, ContentHint
.IMPLICIT
, Optional
.empty());
121 public SendMessageResult
sendReceiptMessage(
122 final SignalServiceReceiptMessage receiptMessage
, final RecipientId recipientId
124 final var messageSendLogStore
= account
.getMessageSendLogStore();
125 final var result
= handleSendMessage(recipientId
,
126 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendReceipt(address
,
130 messageSendLogStore
.insertIfPossible(receiptMessage
.getWhen(), result
, ContentHint
.IMPLICIT
, false);
131 handleSendMessageResult(result
);
135 public SendMessageResult
sendProfileKey(RecipientId recipientId
) {
136 logger
.debug("Sending updated profile key to recipient: {}", recipientId
);
137 final var profileKey
= account
.getProfileKey().serialize();
138 final var message
= SignalServiceDataMessage
.newBuilder()
139 .asProfileKeyUpdate(true)
140 .withProfileKey(profileKey
)
142 return handleSendMessage(recipientId
,
143 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendDataMessage(address
,
145 ContentHint
.IMPLICIT
,
147 SignalServiceMessageSender
.IndividualSendEvents
.EMPTY
,
152 public SendMessageResult
sendRetryReceipt(
153 DecryptionErrorMessage errorMessage
, RecipientId recipientId
, Optional
<GroupId
> groupId
155 logger
.debug("Sending retry receipt for {} to {}, device: {}",
156 errorMessage
.getTimestamp(),
158 errorMessage
.getDeviceId());
159 final var result
= handleSendMessage(recipientId
,
160 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendRetryReceipt(address
,
162 groupId
.map(GroupId
::serialize
),
164 handleSendMessageResult(result
);
168 public SendMessageResult
sendNullMessage(RecipientId recipientId
) {
169 final var result
= handleSendMessage(recipientId
, SignalServiceMessageSender
::sendNullMessage
);
170 handleSendMessageResult(result
);
174 public SendMessageResult
sendSelfMessage(
175 SignalServiceDataMessage
.Builder messageBuilder
, Optional
<Long
> editTargetTimestamp
177 final var recipientId
= account
.getSelfRecipientId();
178 final var contact
= account
.getContactStore().getContact(recipientId
);
179 final var expirationTime
= contact
!= null ? contact
.getMessageExpirationTime() : 0;
180 messageBuilder
.withExpiration(expirationTime
);
182 var message
= messageBuilder
.build();
183 return sendSelfMessage(message
, editTargetTimestamp
);
186 public SendMessageResult
sendSyncMessage(SignalServiceSyncMessage message
) {
187 var messageSender
= dependencies
.getMessageSender();
189 return messageSender
.sendSyncMessage(message
, context
.getUnidentifiedAccessHelper().getAccessForSync());
190 } catch (UnregisteredUserException e
) {
191 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
192 return SendMessageResult
.unregisteredFailure(address
);
193 } catch (ProofRequiredException e
) {
194 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
195 return SendMessageResult
.proofRequiredFailure(address
, e
);
196 } catch (RateLimitException e
) {
197 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
198 logger
.warn("Sending failed due to rate limiting from the signal server: {}", e
.getMessage());
199 return SendMessageResult
.rateLimitFailure(address
, e
);
200 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
201 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
202 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
203 } catch (IOException e
) {
204 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
205 logger
.warn("Failed to send message due to IO exception: {}", e
.getMessage());
206 logger
.debug("Exception", e
);
207 return SendMessageResult
.networkFailure(address
);
211 public SendMessageResult
sendTypingMessage(
212 SignalServiceTypingMessage message
, RecipientId recipientId
214 final var result
= handleSendMessage(recipientId
,
215 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendTyping(List
.of(address
),
216 List
.of(unidentifiedAccess
),
219 handleSendMessageResult(result
);
223 public List
<SendMessageResult
> sendGroupTypingMessage(
224 SignalServiceTypingMessage message
, GroupId groupId
225 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, GroupSendingNotAllowedException
{
226 final var g
= getGroupForSending(groupId
);
227 if (g
.isAnnouncementGroup() && !g
.isAdmin(account
.getSelfRecipientId())) {
228 throw new GroupSendingNotAllowedException(groupId
, g
.getTitle());
230 final var distributionId
= g
.getDistributionId();
231 final var recipientIds
= g
.getMembersWithout(account
.getSelfRecipientId());
233 return sendGroupTypingMessage(message
, recipientIds
, distributionId
);
236 public SendMessageResult
resendMessage(
237 final RecipientId recipientId
, final long timestamp
, final MessageSendLogEntry messageSendLogEntry
239 logger
.trace("Resending message {} to {}", timestamp
, recipientId
);
240 if (messageSendLogEntry
.groupId().isEmpty()) {
241 return handleSendMessage(recipientId
,
242 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.resendContent(address
,
245 messageSendLogEntry
.content(),
246 messageSendLogEntry
.contentHint(),
248 messageSendLogEntry
.urgent()));
251 final var groupId
= messageSendLogEntry
.groupId().get();
252 final var group
= account
.getGroupStore().getGroup(groupId
);
255 logger
.debug("Could not find a matching group for the groupId {}! Skipping message send.",
258 } else if (!group
.getMembers().contains(recipientId
)) {
259 logger
.warn("The target user is no longer in the group {}! Skipping message send.", groupId
.toBase64());
263 final var senderKeyDistributionMessage
= dependencies
.getMessageSender()
264 .getOrCreateNewGroupSession(group
.getDistributionId());
265 final var distributionBytes
= ByteString
.copyFrom(senderKeyDistributionMessage
.serialize());
266 final var contentToSend
= messageSendLogEntry
.content()
268 .setSenderKeyDistributionMessage(distributionBytes
)
271 final var result
= handleSendMessage(recipientId
,
272 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.resendContent(address
,
276 messageSendLogEntry
.contentHint(),
277 Optional
.of(group
.getGroupId().serialize()),
278 messageSendLogEntry
.urgent()));
280 if (result
.isSuccess()) {
281 final var address
= context
.getRecipientHelper().resolveSignalServiceAddress(recipientId
);
282 final var addresses
= result
.getSuccess()
285 .map(device
-> new SignalProtocolAddress(address
.getIdentifier(), device
))
288 account
.getSenderKeyStore().markSenderKeySharedWith(group
.getDistributionId(), addresses
);
294 private List
<SendMessageResult
> sendAsGroupMessage(
295 final SignalServiceDataMessage
.Builder messageBuilder
, final GroupInfo g
, Optional
<Long
> editTargetTimestamp
296 ) throws IOException
, GroupSendingNotAllowedException
{
297 GroupUtils
.setGroupContext(messageBuilder
, g
);
298 messageBuilder
.withExpiration(g
.getMessageExpirationTimer());
300 final var message
= messageBuilder
.build();
301 final var recipients
= g
.getMembersWithout(account
.getSelfRecipientId());
303 if (g
.isAnnouncementGroup() && !g
.isAdmin(account
.getSelfRecipientId())) {
304 if (message
.getBody().isPresent()
305 || message
.getAttachments().isPresent()
306 || message
.getQuote().isPresent()
307 || message
.getPreviews().isPresent()
308 || message
.getMentions().isPresent()
309 || message
.getSticker().isPresent()) {
310 throw new GroupSendingNotAllowedException(g
.getGroupId(), g
.getTitle());
314 return sendGroupMessage(message
,
316 g
.getDistributionId(),
317 ContentHint
.RESENDABLE
,
318 editTargetTimestamp
);
321 private List
<SendMessageResult
> sendGroupMessage(
322 final SignalServiceDataMessage message
,
323 final Set
<RecipientId
> recipientIds
,
324 final DistributionId distributionId
,
325 final ContentHint contentHint
,
326 final Optional
<Long
> editTargetTimestamp
327 ) throws IOException
{
328 final var messageSender
= dependencies
.getMessageSender();
329 final var messageSendLogStore
= account
.getMessageSendLogStore();
330 final AtomicLong entryId
= new AtomicLong(-1);
332 final var urgent
= true;
333 final LegacySenderHandler legacySender
= (recipients
, unidentifiedAccess
, isRecipientUpdate
) -> messageSender
.sendDataMessage(
339 SignalServiceMessageSender
.LegacyGroupEvents
.EMPTY
,
341 logger
.trace("Partial message send result: {}", sendResult
.isSuccess());
342 synchronized (entryId
) {
343 if (entryId
.get() == -1) {
344 final var newId
= messageSendLogStore
.insertIfPossible(message
.getTimestamp(),
350 messageSendLogStore
.addRecipientToExistingEntryIfPossible(entryId
.get(), sendResult
);
356 final SenderKeySenderHandler senderKeySender
= (distId
, recipients
, unidentifiedAccess
, isRecipientUpdate
) -> messageSender
.sendGroupDataMessage(
363 SignalServiceMessageSender
.SenderKeyGroupEvents
.EMPTY
,
366 editTargetTimestamp
.map(timestamp
-> new SignalServiceEditMessage(timestamp
, message
)).orElse(null),
368 logger
.trace("Partial message send results: {}", sendResult
.size());
369 synchronized (entryId
) {
370 if (entryId
.get() == -1) {
371 final var newId
= messageSendLogStore
.insertIfPossible(message
.getTimestamp(),
377 messageSendLogStore
.addRecipientToExistingEntryIfPossible(entryId
.get(), sendResult
);
380 synchronized (entryId
) {
381 if (entryId
.get() == -1) {
382 final var newId
= messageSendLogStore
.insertIfPossible(message
.getTimestamp(),
388 messageSendLogStore
.addRecipientToExistingEntryIfPossible(entryId
.get(), sendResult
);
392 final var results
= sendGroupMessageInternal(legacySender
, senderKeySender
, recipientIds
, distributionId
);
394 for (var r
: results
) {
395 handleSendMessageResult(r
);
401 private List
<SendMessageResult
> sendGroupTypingMessage(
402 final SignalServiceTypingMessage message
,
403 final Set
<RecipientId
> recipientIds
,
404 final DistributionId distributionId
405 ) throws IOException
{
406 final var messageSender
= dependencies
.getMessageSender();
407 final var results
= sendGroupMessageInternal((recipients
, unidentifiedAccess
, isRecipientUpdate
) -> messageSender
.sendTyping(
412 (distId
, recipients
, unidentifiedAccess
, isRecipientUpdate
) -> messageSender
.sendGroupTyping(distId
,
419 for (var r
: results
) {
420 handleSendMessageResult(r
);
426 private GroupInfo
getGroupForSending(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
427 var g
= context
.getGroupHelper().getGroup(groupId
);
429 throw new GroupNotFoundException(groupId
);
431 if (!g
.isMember(account
.getSelfRecipientId())) {
432 throw new NotAGroupMemberException(groupId
, g
.getTitle());
437 private List
<SendMessageResult
> sendGroupMessageInternal(
438 final LegacySenderHandler legacySender
,
439 final SenderKeySenderHandler senderKeySender
,
440 final Set
<RecipientId
> recipientIds
,
441 final DistributionId distributionId
442 ) throws IOException
{
443 long startTime
= System
.currentTimeMillis();
444 // isRecipientUpdate is true if we've already sent this message to some recipients in the past, otherwise false.
445 final var isRecipientUpdate
= false;
446 Set
<RecipientId
> senderKeyTargets
= distributionId
== null
448 : getSenderKeyCapableRecipientIds(recipientIds
);
449 final var allResults
= new ArrayList
<SendMessageResult
>(recipientIds
.size());
451 if (senderKeyTargets
.size() > 0) {
452 final var results
= sendGroupMessageInternalWithSenderKey(senderKeySender
,
457 if (results
== null) {
458 senderKeyTargets
= Set
.of();
460 results
.stream().filter(SendMessageResult
::isSuccess
).forEach(allResults
::add
);
461 final var failedTargets
= results
.stream()
462 .filter(r
-> !r
.isSuccess())
463 .map(r
-> context
.getRecipientHelper().resolveRecipient(r
.getAddress()))
465 if (failedTargets
.size() > 0) {
466 senderKeyTargets
= new HashSet
<>(senderKeyTargets
);
467 failedTargets
.forEach(senderKeyTargets
::remove
);
472 final var legacyTargets
= new HashSet
<>(recipientIds
);
473 legacyTargets
.removeAll(senderKeyTargets
);
474 final boolean onlyTargetIsSelfWithLinkedDevice
= recipientIds
.isEmpty() && account
.isMultiDevice();
476 if (legacyTargets
.size() > 0 || onlyTargetIsSelfWithLinkedDevice
) {
477 if (legacyTargets
.size() > 0) {
478 logger
.debug("Need to do {} legacy sends.", legacyTargets
.size());
480 logger
.debug("Need to do a legacy send to send a sync message for a group of only ourselves.");
483 final List
<SendMessageResult
> results
= sendGroupMessageInternalWithLegacy(legacySender
,
485 isRecipientUpdate
|| allResults
.size() > 0);
486 allResults
.addAll(results
);
488 final var duration
= Duration
.ofMillis(System
.currentTimeMillis() - startTime
);
489 logger
.debug("Sending took {}", duration
.toString());
493 private Set
<RecipientId
> getSenderKeyCapableRecipientIds(final Set
<RecipientId
> recipientIds
) {
494 final var selfProfile
= context
.getProfileHelper().getSelfProfile();
495 if (selfProfile
== null || !selfProfile
.getCapabilities().contains(Profile
.Capability
.senderKey
)) {
496 logger
.debug("Not all of our devices support sender key. Using legacy.");
500 final var senderKeyTargets
= new HashSet
<RecipientId
>();
501 final var recipientList
= new ArrayList
<>(recipientIds
);
502 final var profiles
= context
.getProfileHelper().getRecipientProfiles(recipientList
).iterator();
503 for (final var recipientId
: recipientList
) {
504 final var profile
= profiles
.next();
505 if (profile
== null || !profile
.getCapabilities().contains(Profile
.Capability
.senderKey
)) {
509 final var access
= context
.getUnidentifiedAccessHelper().getAccessFor(recipientId
);
510 if (access
.isEmpty() || access
.get().getTargetUnidentifiedAccess().isEmpty()) {
514 final var serviceId
= account
.getRecipientAddressResolver()
515 .resolveRecipientAddress(recipientId
)
518 if (serviceId
== null) {
521 final var identity
= account
.getIdentityKeyStore().getIdentityInfo(serviceId
);
522 if (identity
== null || !identity
.getTrustLevel().isTrusted()) {
526 senderKeyTargets
.add(recipientId
);
529 if (senderKeyTargets
.size() < 2) {
530 logger
.debug("Too few sender-key-capable users ({}). Doing all legacy sends.", senderKeyTargets
.size());
534 logger
.debug("Can use sender key for {}/{} recipients.", senderKeyTargets
.size(), recipientIds
.size());
535 return senderKeyTargets
;
538 private List
<SendMessageResult
> sendGroupMessageInternalWithLegacy(
539 final LegacySenderHandler sender
, final Set
<RecipientId
> recipientIds
, final boolean isRecipientUpdate
540 ) throws IOException
{
541 final var recipientIdList
= new ArrayList
<>(recipientIds
);
542 final var addresses
= recipientIdList
.stream()
543 .map(context
.getRecipientHelper()::resolveSignalServiceAddress
)
545 final var unidentifiedAccesses
= context
.getUnidentifiedAccessHelper().getAccessFor(recipientIdList
);
547 final var results
= sender
.send(addresses
, unidentifiedAccesses
, isRecipientUpdate
);
549 final var successCount
= results
.stream().filter(SendMessageResult
::isSuccess
).count();
550 logger
.debug("Successfully sent using 1:1 to {}/{} legacy targets.", successCount
, recipientIdList
.size());
552 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
557 private List
<SendMessageResult
> sendGroupMessageInternalWithSenderKey(
558 final SenderKeySenderHandler sender
,
559 final Set
<RecipientId
> recipientIds
,
560 final DistributionId distributionId
,
561 final boolean isRecipientUpdate
562 ) throws IOException
{
563 final var recipientIdList
= new ArrayList
<>(recipientIds
);
565 long keyCreateTime
= account
.getSenderKeyStore()
566 .getCreateTimeForOurKey(account
.getAci(), account
.getDeviceId(), distributionId
);
567 long keyAge
= System
.currentTimeMillis() - keyCreateTime
;
569 if (keyCreateTime
!= -1 && keyAge
> TimeUnit
.DAYS
.toMillis(14)) {
570 logger
.debug("DistributionId {} was created at {} and is {} ms old (~{} days). Rotating.",
574 TimeUnit
.MILLISECONDS
.toDays(keyAge
));
575 account
.getSenderKeyStore().deleteOurKey(account
.getAci(), distributionId
);
578 List
<SignalServiceAddress
> addresses
= recipientIdList
.stream()
579 .map(context
.getRecipientHelper()::resolveSignalServiceAddress
)
581 List
<UnidentifiedAccess
> unidentifiedAccesses
= context
.getUnidentifiedAccessHelper()
582 .getAccessFor(recipientIdList
)
585 .map(UnidentifiedAccessPair
::getTargetUnidentifiedAccess
)
590 List
<SendMessageResult
> results
= sender
.send(distributionId
,
592 unidentifiedAccesses
,
595 final var successCount
= results
.stream().filter(SendMessageResult
::isSuccess
).count();
596 logger
.debug("Successfully sent using sender key to {}/{} sender key targets.",
601 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
603 } catch (InvalidUnidentifiedAccessHeaderException e
) {
604 logger
.warn("Someone had a bad UD header. Falling back to legacy sends.", e
);
606 } catch (NoSessionException e
) {
607 logger
.warn("No session. Falling back to legacy sends.", e
);
608 account
.getSenderKeyStore().deleteOurKey(account
.getAci(), distributionId
);
610 } catch (InvalidKeyException e
) {
611 logger
.warn("Invalid key. Falling back to legacy sends.", e
);
612 account
.getSenderKeyStore().deleteOurKey(account
.getAci(), distributionId
);
614 } catch (InvalidRegistrationIdException e
) {
615 logger
.warn("Invalid registrationId. Falling back to legacy sends.", e
);
617 } catch (NotFoundException e
) {
618 logger
.warn("Someone was unregistered. Falling back to legacy sends.", e
);
623 private SendMessageResult
sendMessage(
624 SignalServiceDataMessage message
, RecipientId recipientId
, Optional
<Long
> editTargetTimestamp
626 final var messageSendLogStore
= account
.getMessageSendLogStore();
627 final var urgent
= true;
628 final var includePniSignature
= false;
629 final var result
= handleSendMessage(recipientId
,
630 editTargetTimestamp
.isEmpty()
631 ?
(messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendDataMessage(address
,
633 ContentHint
.RESENDABLE
,
635 SignalServiceMessageSender
.IndividualSendEvents
.EMPTY
,
638 : (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendEditMessage(address
,
640 ContentHint
.RESENDABLE
,
642 SignalServiceMessageSender
.IndividualSendEvents
.EMPTY
,
644 editTargetTimestamp
.get()));
645 messageSendLogStore
.insertIfPossible(message
.getTimestamp(), result
, ContentHint
.RESENDABLE
, urgent
);
646 handleSendMessageResult(result
);
650 private SendMessageResult
handleSendMessage(RecipientId recipientId
, SenderHandler s
) {
651 var messageSender
= dependencies
.getMessageSender();
653 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(recipientId
);
656 return s
.send(messageSender
, address
, context
.getUnidentifiedAccessHelper().getAccessFor(recipientId
));
657 } catch (UnregisteredUserException e
) {
658 final RecipientId newRecipientId
;
660 newRecipientId
= context
.getRecipientHelper().refreshRegisteredUser(recipientId
);
661 } catch (UnregisteredRecipientException ex
) {
662 return SendMessageResult
.unregisteredFailure(address
);
664 address
= context
.getRecipientHelper().resolveSignalServiceAddress(newRecipientId
);
665 return s
.send(messageSender
,
667 context
.getUnidentifiedAccessHelper().getAccessFor(newRecipientId
));
669 } catch (UnregisteredUserException e
) {
670 return SendMessageResult
.unregisteredFailure(address
);
671 } catch (ProofRequiredException e
) {
672 return SendMessageResult
.proofRequiredFailure(address
, e
);
673 } catch (RateLimitException e
) {
674 logger
.warn("Sending failed due to rate limiting from the signal server: {}", e
.getMessage());
675 return SendMessageResult
.rateLimitFailure(address
, e
);
676 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
677 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
678 } catch (IOException e
) {
679 logger
.warn("Failed to send message due to IO exception: {}", e
.getMessage());
680 logger
.debug("Exception", e
);
681 return SendMessageResult
.networkFailure(address
);
685 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
, Optional
<Long
> editTargetTimestamp
) {
686 var address
= account
.getSelfAddress();
687 var transcript
= new SentTranscriptMessage(Optional
.of(address
),
688 message
.getTimestamp(),
689 editTargetTimestamp
.isEmpty() ? Optional
.of(message
) : Optional
.empty(),
690 message
.getExpiresInSeconds(),
691 Map
.of(address
.getServiceId(), true),
695 editTargetTimestamp
.map((timestamp
) -> new SignalServiceEditMessage(timestamp
, message
)));
696 var syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
698 return sendSyncMessage(syncMessage
);
701 private void handleSendMessageResult(final SendMessageResult r
) {
702 if (r
.isSuccess() && !r
.getSuccess().isUnidentified()) {
703 final var recipientId
= context
.getRecipientHelper().resolveRecipient(r
.getAddress());
704 final var profile
= account
.getProfileStore().getProfile(recipientId
);
705 if (profile
!= null && (
706 profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.ENABLED
707 || profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.UNRESTRICTED
709 account
.getProfileStore()
710 .storeProfile(recipientId
,
711 Profile
.newBuilder(profile
)
712 .withUnidentifiedAccessMode(Profile
.UnidentifiedAccessMode
.UNKNOWN
)
716 if (r
.isUnregisteredFailure()) {
717 final var recipientId
= context
.getRecipientHelper().resolveRecipient(r
.getAddress());
718 final var profile
= account
.getProfileStore().getProfile(recipientId
);
719 if (profile
!= null && (
720 profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.ENABLED
721 || profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.UNRESTRICTED
723 account
.getProfileStore()
724 .storeProfile(recipientId
,
725 Profile
.newBuilder(profile
)
726 .withUnidentifiedAccessMode(Profile
.UnidentifiedAccessMode
.UNKNOWN
)
730 if (r
.getIdentityFailure() != null) {
731 final var recipientId
= context
.getRecipientHelper().resolveRecipient(r
.getAddress());
732 context
.getIdentityHelper()
733 .handleIdentityFailure(recipientId
, r
.getAddress().getServiceId(), r
.getIdentityFailure());
737 interface SenderHandler
{
739 SendMessageResult
send(
740 SignalServiceMessageSender messageSender
,
741 SignalServiceAddress address
,
742 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
743 ) throws IOException
, UnregisteredUserException
, ProofRequiredException
, RateLimitException
, org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
746 interface SenderKeySenderHandler
{
748 List
<SendMessageResult
> send(
749 DistributionId distributionId
,
750 List
<SignalServiceAddress
> recipients
,
751 List
<UnidentifiedAccess
> unidentifiedAccess
,
752 boolean isRecipientUpdate
753 ) throws IOException
, UntrustedIdentityException
, NoSessionException
, InvalidKeyException
, InvalidRegistrationIdException
;
756 interface LegacySenderHandler
{
758 List
<SendMessageResult
> send(
759 List
<SignalServiceAddress
> recipients
,
760 List
<Optional
<UnidentifiedAccessPair
>> unidentifiedAccess
,
761 boolean isRecipientUpdate
762 ) throws IOException
, UntrustedIdentityException
;