1 package org
.asamk
.signal
.manager
.helper
;
3 import org
.asamk
.signal
.manager
.api
.Contact
;
4 import org
.asamk
.signal
.manager
.api
.GroupId
;
5 import org
.asamk
.signal
.manager
.api
.GroupNotFoundException
;
6 import org
.asamk
.signal
.manager
.api
.GroupSendingNotAllowedException
;
7 import org
.asamk
.signal
.manager
.api
.NotAGroupMemberException
;
8 import org
.asamk
.signal
.manager
.api
.Profile
;
9 import org
.asamk
.signal
.manager
.api
.UnregisteredRecipientException
;
10 import org
.asamk
.signal
.manager
.groups
.GroupUtils
;
11 import org
.asamk
.signal
.manager
.internal
.SignalDependencies
;
12 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
13 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfo
;
14 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
15 import org
.asamk
.signal
.manager
.storage
.sendLog
.MessageSendLogEntry
;
16 import org
.signal
.libsignal
.protocol
.InvalidKeyException
;
17 import org
.signal
.libsignal
.protocol
.InvalidRegistrationIdException
;
18 import org
.signal
.libsignal
.protocol
.NoSessionException
;
19 import org
.signal
.libsignal
.protocol
.SignalProtocolAddress
;
20 import org
.signal
.libsignal
.protocol
.message
.DecryptionErrorMessage
;
21 import org
.slf4j
.Logger
;
22 import org
.slf4j
.LoggerFactory
;
23 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
24 import org
.whispersystems
.signalservice
.api
.crypto
.ContentHint
;
25 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
26 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
27 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
28 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
29 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
30 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEditMessage
;
31 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
32 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceTypingMessage
;
33 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
34 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
35 import org
.whispersystems
.signalservice
.api
.push
.DistributionId
;
36 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
37 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NotFoundException
;
38 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.ProofRequiredException
;
39 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.RateLimitException
;
40 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
41 import org
.whispersystems
.signalservice
.internal
.push
.exceptions
.InvalidUnidentifiedAccessHeaderException
;
42 import org
.whispersystems
.signalservice
.internal
.push
.http
.PartialSendCompleteListener
;
44 import java
.io
.IOException
;
45 import java
.time
.Duration
;
46 import java
.util
.ArrayList
;
47 import java
.util
.HashSet
;
48 import java
.util
.List
;
50 import java
.util
.Optional
;
52 import java
.util
.concurrent
.TimeUnit
;
53 import java
.util
.concurrent
.atomic
.AtomicLong
;
55 import okio
.ByteString
;
57 public class SendHelper
{
59 private static final Logger logger
= LoggerFactory
.getLogger(SendHelper
.class);
61 private final SignalAccount account
;
62 private final SignalDependencies dependencies
;
63 private final Context context
;
65 public SendHelper(final Context context
) {
66 this.account
= context
.getAccount();
67 this.dependencies
= context
.getDependencies();
68 this.context
= context
;
72 * Send a single message to one recipient.
73 * The message is extended with the current expiration timer.
75 public SendMessageResult
sendMessage(
76 final SignalServiceDataMessage
.Builder messageBuilder
,
77 final RecipientId recipientId
,
78 Optional
<Long
> editTargetTimestamp
80 var contact
= account
.getContactStore().getContact(recipientId
);
81 if (contact
== null || !contact
.isProfileSharingEnabled() || contact
.isHidden()) {
82 final var contactBuilder
= contact
== null ? Contact
.newBuilder() : Contact
.newBuilder(contact
);
83 contact
= contactBuilder
.withIsProfileSharingEnabled(true).withIsHidden(false).build();
84 account
.getContactStore().storeContact(recipientId
, contact
);
87 final var expirationTime
= contact
.messageExpirationTime();
88 messageBuilder
.withExpiration(expirationTime
);
90 if (!contact
.isBlocked()) {
91 final var profileKey
= account
.getProfileKey().serialize();
92 messageBuilder
.withProfileKey(profileKey
);
95 final var message
= messageBuilder
.build();
96 return sendMessage(message
, recipientId
, editTargetTimestamp
);
100 * Send a group message to the given group
101 * The message is extended with the current expiration timer for the group and the group context.
103 public List
<SendMessageResult
> sendAsGroupMessage(
104 final SignalServiceDataMessage
.Builder messageBuilder
,
105 final GroupId groupId
,
106 final boolean includeSelf
,
107 final Optional
<Long
> editTargetTimestamp
108 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
, GroupSendingNotAllowedException
{
109 final var g
= getGroupForSending(groupId
);
110 return sendAsGroupMessage(messageBuilder
, g
, includeSelf
, editTargetTimestamp
);
114 * Send a complete group message to the given recipients (should be current/old/new members)
115 * This method should only be used for create/update/quit group messages.
117 public List
<SendMessageResult
> sendGroupMessage(
118 final SignalServiceDataMessage message
,
119 final Set
<RecipientId
> recipientIds
,
120 final DistributionId distributionId
121 ) throws IOException
{
122 return sendGroupMessage(message
, recipientIds
, distributionId
, ContentHint
.IMPLICIT
, Optional
.empty());
125 public SendMessageResult
sendReceiptMessage(
126 final SignalServiceReceiptMessage receiptMessage
, final RecipientId recipientId
128 final var messageSendLogStore
= account
.getMessageSendLogStore();
129 final var result
= handleSendMessage(recipientId
,
130 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendReceipt(address
,
134 messageSendLogStore
.insertIfPossible(receiptMessage
.getWhen(), result
, ContentHint
.IMPLICIT
, false);
135 handleSendMessageResult(result
);
139 public SendMessageResult
sendProfileKey(RecipientId recipientId
) {
140 logger
.debug("Sending updated profile key to recipient: {}", recipientId
);
141 final var profileKey
= account
.getProfileKey().serialize();
142 final var message
= SignalServiceDataMessage
.newBuilder()
143 .asProfileKeyUpdate(true)
144 .withProfileKey(profileKey
)
146 return handleSendMessage(recipientId
,
147 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendDataMessage(address
,
149 ContentHint
.IMPLICIT
,
151 SignalServiceMessageSender
.IndividualSendEvents
.EMPTY
,
156 public SendMessageResult
sendRetryReceipt(
157 DecryptionErrorMessage errorMessage
, RecipientId recipientId
, Optional
<GroupId
> groupId
159 logger
.debug("Sending retry receipt for {} to {}, device: {}",
160 errorMessage
.getTimestamp(),
162 errorMessage
.getDeviceId());
163 final var result
= handleSendMessage(recipientId
,
164 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendRetryReceipt(address
,
166 groupId
.map(GroupId
::serialize
),
168 handleSendMessageResult(result
);
172 public SendMessageResult
sendNullMessage(RecipientId recipientId
) {
173 final var result
= handleSendMessage(recipientId
, SignalServiceMessageSender
::sendNullMessage
);
174 handleSendMessageResult(result
);
178 public SendMessageResult
sendSelfMessage(
179 SignalServiceDataMessage
.Builder messageBuilder
, Optional
<Long
> editTargetTimestamp
181 final var recipientId
= account
.getSelfRecipientId();
182 final var contact
= account
.getContactStore().getContact(recipientId
);
183 final var expirationTime
= contact
!= null ? contact
.messageExpirationTime() : 0;
184 messageBuilder
.withExpiration(expirationTime
);
186 var message
= messageBuilder
.build();
187 return sendSelfMessage(message
, editTargetTimestamp
);
190 public SendMessageResult
sendSyncMessage(SignalServiceSyncMessage message
) {
191 var messageSender
= dependencies
.getMessageSender();
192 if (!account
.isMultiDevice()) {
193 logger
.trace("Not sending sync message because there are no linked devices.");
194 return SendMessageResult
.success(account
.getSelfAddress(), List
.of(), false, false, 0, Optional
.empty());
197 return messageSender
.sendSyncMessage(message
, context
.getUnidentifiedAccessHelper().getAccessForSync());
198 } catch (UnregisteredUserException e
) {
199 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
200 return SendMessageResult
.unregisteredFailure(address
);
201 } catch (ProofRequiredException e
) {
202 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
203 return SendMessageResult
.proofRequiredFailure(address
, e
);
204 } catch (RateLimitException e
) {
205 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
206 logger
.warn("Sending failed due to rate limiting from the signal server: {}", e
.getMessage());
207 return SendMessageResult
.rateLimitFailure(address
, e
);
208 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
209 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
210 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
211 } catch (IOException e
) {
212 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
213 logger
.warn("Failed to send message due to IO exception: {}", e
.getMessage());
214 logger
.debug("Exception", e
);
215 return SendMessageResult
.networkFailure(address
);
219 public SendMessageResult
sendTypingMessage(
220 SignalServiceTypingMessage message
, RecipientId recipientId
222 final var result
= handleSendMessage(recipientId
,
223 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendTyping(List
.of(address
),
224 List
.of(unidentifiedAccess
),
227 handleSendMessageResult(result
);
231 public List
<SendMessageResult
> sendGroupTypingMessage(
232 SignalServiceTypingMessage message
, GroupId groupId
233 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, GroupSendingNotAllowedException
{
234 final var g
= getGroupForSending(groupId
);
235 if (g
.isAnnouncementGroup() && !g
.isAdmin(account
.getSelfRecipientId())) {
236 throw new GroupSendingNotAllowedException(groupId
, g
.getTitle());
238 final var distributionId
= g
.getDistributionId();
239 final var recipientIds
= g
.getMembersWithout(account
.getSelfRecipientId());
241 return sendGroupTypingMessage(message
, recipientIds
, distributionId
);
244 public SendMessageResult
resendMessage(
245 final RecipientId recipientId
, final long timestamp
, final MessageSendLogEntry messageSendLogEntry
247 logger
.trace("Resending message {} to {}", timestamp
, recipientId
);
248 if (messageSendLogEntry
.groupId().isEmpty()) {
249 return handleSendMessage(recipientId
,
250 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.resendContent(address
,
253 messageSendLogEntry
.content(),
254 messageSendLogEntry
.contentHint(),
256 messageSendLogEntry
.urgent()));
259 final var groupId
= messageSendLogEntry
.groupId().get();
260 final var group
= account
.getGroupStore().getGroup(groupId
);
263 logger
.debug("Could not find a matching group for the groupId {}! Skipping message send.",
266 } else if (!group
.getMembers().contains(recipientId
)) {
267 logger
.warn("The target user is no longer in the group {}! Skipping message send.", groupId
.toBase64());
271 final var senderKeyDistributionMessage
= dependencies
.getMessageSender()
272 .getOrCreateNewGroupSession(group
.getDistributionId());
273 final var distributionBytes
= ByteString
.of(senderKeyDistributionMessage
.serialize());
274 final var contentToSend
= messageSendLogEntry
.content()
276 .senderKeyDistributionMessage(distributionBytes
)
279 final var result
= handleSendMessage(recipientId
,
280 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.resendContent(address
,
284 messageSendLogEntry
.contentHint(),
285 Optional
.of(group
.getGroupId().serialize()),
286 messageSendLogEntry
.urgent()));
288 if (result
.isSuccess()) {
289 final var address
= context
.getRecipientHelper().resolveSignalServiceAddress(recipientId
);
290 final var addresses
= result
.getSuccess()
293 .map(device
-> new SignalProtocolAddress(address
.getIdentifier(), device
))
296 account
.getSenderKeyStore().markSenderKeySharedWith(group
.getDistributionId(), addresses
);
302 private List
<SendMessageResult
> sendAsGroupMessage(
303 final SignalServiceDataMessage
.Builder messageBuilder
,
305 final boolean includeSelf
,
306 final Optional
<Long
> editTargetTimestamp
307 ) throws IOException
, GroupSendingNotAllowedException
{
308 GroupUtils
.setGroupContext(messageBuilder
, g
);
309 messageBuilder
.withExpiration(g
.getMessageExpirationTimer());
311 final var message
= messageBuilder
.build();
312 final var recipients
= includeSelf ? g
.getMembers() : g
.getMembersWithout(account
.getSelfRecipientId());
314 if (g
.isAnnouncementGroup() && !g
.isAdmin(account
.getSelfRecipientId())) {
315 if (message
.getBody().isPresent()
316 || message
.getAttachments().isPresent()
317 || message
.getQuote().isPresent()
318 || message
.getPreviews().isPresent()
319 || message
.getMentions().isPresent()
320 || message
.getSticker().isPresent()) {
321 throw new GroupSendingNotAllowedException(g
.getGroupId(), g
.getTitle());
325 return sendGroupMessage(message
,
327 g
.getDistributionId(),
328 ContentHint
.RESENDABLE
,
329 editTargetTimestamp
);
332 private List
<SendMessageResult
> sendGroupMessage(
333 final SignalServiceDataMessage message
,
334 final Set
<RecipientId
> recipientIds
,
335 final DistributionId distributionId
,
336 final ContentHint contentHint
,
337 final Optional
<Long
> editTargetTimestamp
338 ) throws IOException
{
339 final var messageSender
= dependencies
.getMessageSender();
340 final var messageSendLogStore
= account
.getMessageSendLogStore();
341 final AtomicLong entryId
= new AtomicLong(-1);
343 final var urgent
= true;
344 final PartialSendCompleteListener partialSendCompleteListener
= sendResult
-> {
345 logger
.trace("Partial message send result: {}", sendResult
.isSuccess());
346 synchronized (entryId
) {
347 if (entryId
.get() == -1) {
348 final var newId
= messageSendLogStore
.insertIfPossible(message
.getTimestamp(),
354 messageSendLogStore
.addRecipientToExistingEntryIfPossible(entryId
.get(), sendResult
);
358 final LegacySenderHandler legacySender
= (recipients
, unidentifiedAccess
, isRecipientUpdate
) ->
359 editTargetTimestamp
.isEmpty()
360 ? messageSender
.sendDataMessage(recipients
,
365 SignalServiceMessageSender
.LegacyGroupEvents
.EMPTY
,
366 partialSendCompleteListener
,
369 : messageSender
.sendEditMessage(recipients
,
374 SignalServiceMessageSender
.LegacyGroupEvents
.EMPTY
,
375 partialSendCompleteListener
,
378 editTargetTimestamp
.get());
379 final SenderKeySenderHandler senderKeySender
= (distId
, recipients
, unidentifiedAccess
, isRecipientUpdate
) -> messageSender
.sendGroupDataMessage(
386 SignalServiceMessageSender
.SenderKeyGroupEvents
.EMPTY
,
389 editTargetTimestamp
.map(timestamp
-> new SignalServiceEditMessage(timestamp
, message
)).orElse(null),
391 logger
.trace("Partial message send results: {}", sendResult
.size());
392 synchronized (entryId
) {
393 if (entryId
.get() == -1) {
394 final var newId
= messageSendLogStore
.insertIfPossible(message
.getTimestamp(),
400 messageSendLogStore
.addRecipientToExistingEntryIfPossible(entryId
.get(), sendResult
);
403 synchronized (entryId
) {
404 if (entryId
.get() == -1) {
405 final var newId
= messageSendLogStore
.insertIfPossible(message
.getTimestamp(),
411 messageSendLogStore
.addRecipientToExistingEntryIfPossible(entryId
.get(), sendResult
);
415 final var results
= sendGroupMessageInternal(legacySender
, senderKeySender
, recipientIds
, distributionId
);
417 for (var r
: results
) {
418 handleSendMessageResult(r
);
424 private List
<SendMessageResult
> sendGroupTypingMessage(
425 final SignalServiceTypingMessage message
,
426 final Set
<RecipientId
> recipientIds
,
427 final DistributionId distributionId
428 ) throws IOException
{
429 final var messageSender
= dependencies
.getMessageSender();
430 final var results
= sendGroupMessageInternal((recipients
, unidentifiedAccess
, isRecipientUpdate
) -> messageSender
.sendTyping(
435 (distId
, recipients
, unidentifiedAccess
, isRecipientUpdate
) -> messageSender
.sendGroupTyping(distId
,
442 for (var r
: results
) {
443 handleSendMessageResult(r
);
449 private GroupInfo
getGroupForSending(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
450 var g
= context
.getGroupHelper().getGroup(groupId
);
452 throw new GroupNotFoundException(groupId
);
454 if (!g
.isMember(account
.getSelfRecipientId())) {
455 throw new NotAGroupMemberException(groupId
, g
.getTitle());
457 if (!g
.isProfileSharingEnabled()) {
458 g
.setProfileSharingEnabled(true);
459 account
.getGroupStore().updateGroup(g
);
464 private List
<SendMessageResult
> sendGroupMessageInternal(
465 final LegacySenderHandler legacySender
,
466 final SenderKeySenderHandler senderKeySender
,
467 final Set
<RecipientId
> recipientIds
,
468 final DistributionId distributionId
469 ) throws IOException
{
470 long startTime
= System
.currentTimeMillis();
471 // isRecipientUpdate is true if we've already sent this message to some recipients in the past, otherwise false.
472 final var isRecipientUpdate
= false;
473 Set
<RecipientId
> senderKeyTargets
= distributionId
== null
475 : getSenderKeyCapableRecipientIds(recipientIds
);
476 final var allResults
= new ArrayList
<SendMessageResult
>(recipientIds
.size());
478 if (!senderKeyTargets
.isEmpty()) {
479 final var results
= sendGroupMessageInternalWithSenderKey(senderKeySender
,
484 if (results
== null) {
485 senderKeyTargets
= Set
.of();
487 results
.stream().filter(SendMessageResult
::isSuccess
).forEach(allResults
::add
);
488 final var recipientResolver
= account
.getRecipientResolver();
489 final var failedTargets
= results
.stream()
490 .filter(r
-> !r
.isSuccess())
491 .map(r
-> recipientResolver
.resolveRecipient(r
.getAddress()))
493 if (!failedTargets
.isEmpty()) {
494 senderKeyTargets
= new HashSet
<>(senderKeyTargets
);
495 failedTargets
.forEach(senderKeyTargets
::remove
);
500 final var legacyTargets
= new HashSet
<>(recipientIds
);
501 legacyTargets
.removeAll(senderKeyTargets
);
502 final boolean onlyTargetIsSelfWithLinkedDevice
= recipientIds
.isEmpty() && account
.isMultiDevice();
504 if (!legacyTargets
.isEmpty() || onlyTargetIsSelfWithLinkedDevice
) {
505 if (!legacyTargets
.isEmpty()) {
506 logger
.debug("Need to do {} legacy sends.", legacyTargets
.size());
508 logger
.debug("Need to do a legacy send to send a sync message for a group of only ourselves.");
511 final List
<SendMessageResult
> results
= sendGroupMessageInternalWithLegacy(legacySender
,
513 isRecipientUpdate
|| !allResults
.isEmpty());
514 allResults
.addAll(results
);
516 final var duration
= Duration
.ofMillis(System
.currentTimeMillis() - startTime
);
517 logger
.debug("Sending took {}", duration
.toString());
521 private Set
<RecipientId
> getSenderKeyCapableRecipientIds(final Set
<RecipientId
> recipientIds
) {
522 final var selfProfile
= context
.getProfileHelper().getSelfProfile();
523 if (selfProfile
== null || !selfProfile
.getCapabilities().contains(Profile
.Capability
.senderKey
)) {
524 logger
.debug("Not all of our devices support sender key. Using legacy.");
528 final var senderKeyTargets
= new HashSet
<RecipientId
>();
529 final var recipientList
= new ArrayList
<>(recipientIds
);
530 final var profiles
= context
.getProfileHelper().getRecipientProfiles(recipientList
).iterator();
531 for (final var recipientId
: recipientList
) {
532 final var profile
= profiles
.next();
533 if (profile
== null || !profile
.getCapabilities().contains(Profile
.Capability
.senderKey
)) {
537 final var access
= context
.getUnidentifiedAccessHelper().getAccessFor(recipientId
);
538 if (access
.isEmpty() || access
.get().getTargetUnidentifiedAccess().isEmpty()) {
542 final var serviceId
= account
.getRecipientAddressResolver()
543 .resolveRecipientAddress(recipientId
)
546 if (serviceId
== null) {
549 final var identity
= account
.getIdentityKeyStore().getIdentityInfo(serviceId
);
550 if (identity
== null || !identity
.getTrustLevel().isTrusted()) {
554 senderKeyTargets
.add(recipientId
);
557 if (senderKeyTargets
.size() < 2) {
558 logger
.debug("Too few sender-key-capable users ({}). Doing all legacy sends.", senderKeyTargets
.size());
562 logger
.debug("Can use sender key for {}/{} recipients.", senderKeyTargets
.size(), recipientIds
.size());
563 return senderKeyTargets
;
566 private List
<SendMessageResult
> sendGroupMessageInternalWithLegacy(
567 final LegacySenderHandler sender
, final Set
<RecipientId
> recipientIds
, final boolean isRecipientUpdate
568 ) throws IOException
{
569 final var recipientIdList
= new ArrayList
<>(recipientIds
);
570 final var addresses
= recipientIdList
.stream()
571 .map(context
.getRecipientHelper()::resolveSignalServiceAddress
)
573 final var unidentifiedAccesses
= context
.getUnidentifiedAccessHelper().getAccessFor(recipientIdList
);
575 final var results
= sender
.send(addresses
, unidentifiedAccesses
, isRecipientUpdate
);
577 final var successCount
= results
.stream().filter(SendMessageResult
::isSuccess
).count();
578 logger
.debug("Successfully sent using 1:1 to {}/{} legacy targets.", successCount
, recipientIdList
.size());
580 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
585 private List
<SendMessageResult
> sendGroupMessageInternalWithSenderKey(
586 final SenderKeySenderHandler sender
,
587 final Set
<RecipientId
> recipientIds
,
588 final DistributionId distributionId
,
589 final boolean isRecipientUpdate
590 ) throws IOException
{
591 final var recipientIdList
= new ArrayList
<>(recipientIds
);
593 long keyCreateTime
= account
.getSenderKeyStore()
594 .getCreateTimeForOurKey(account
.getAci(), account
.getDeviceId(), distributionId
);
595 long keyAge
= System
.currentTimeMillis() - keyCreateTime
;
597 if (keyCreateTime
!= -1 && keyAge
> TimeUnit
.DAYS
.toMillis(14)) {
598 logger
.debug("DistributionId {} was created at {} and is {} ms old (~{} days). Rotating.",
602 TimeUnit
.MILLISECONDS
.toDays(keyAge
));
603 account
.getSenderKeyStore().deleteOurKey(account
.getAci(), distributionId
);
606 List
<SignalServiceAddress
> addresses
= recipientIdList
.stream()
607 .map(context
.getRecipientHelper()::resolveSignalServiceAddress
)
609 List
<UnidentifiedAccess
> unidentifiedAccesses
= context
.getUnidentifiedAccessHelper()
610 .getAccessFor(recipientIdList
)
613 .map(UnidentifiedAccessPair
::getTargetUnidentifiedAccess
)
618 List
<SendMessageResult
> results
= sender
.send(distributionId
,
620 unidentifiedAccesses
,
623 final var successCount
= results
.stream().filter(SendMessageResult
::isSuccess
).count();
624 logger
.debug("Successfully sent using sender key to {}/{} sender key targets.",
629 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
631 } catch (InvalidUnidentifiedAccessHeaderException e
) {
632 logger
.warn("Someone had a bad UD header. Falling back to legacy sends.", e
);
634 } catch (NoSessionException e
) {
635 logger
.warn("No session. Falling back to legacy sends.", e
);
636 account
.getSenderKeyStore().deleteOurKey(account
.getAci(), distributionId
);
638 } catch (InvalidKeyException e
) {
639 logger
.warn("Invalid key. Falling back to legacy sends.", e
);
640 account
.getSenderKeyStore().deleteOurKey(account
.getAci(), distributionId
);
642 } catch (InvalidRegistrationIdException e
) {
643 logger
.warn("Invalid registrationId. Falling back to legacy sends.", e
);
645 } catch (NotFoundException e
) {
646 logger
.warn("Someone was unregistered. Falling back to legacy sends.", e
);
648 } catch (IOException e
) {
649 if (e
.getCause() instanceof InvalidKeyException
) {
650 logger
.warn("Invalid key. Falling back to legacy sends.", e
);
658 private SendMessageResult
sendMessage(
659 SignalServiceDataMessage message
, RecipientId recipientId
, Optional
<Long
> editTargetTimestamp
661 final var messageSendLogStore
= account
.getMessageSendLogStore();
662 final var urgent
= true;
663 final var includePniSignature
= false;
664 final var result
= handleSendMessage(recipientId
,
665 editTargetTimestamp
.isEmpty()
666 ?
(messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendDataMessage(address
,
668 ContentHint
.RESENDABLE
,
670 SignalServiceMessageSender
.IndividualSendEvents
.EMPTY
,
673 : (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendEditMessage(address
,
675 ContentHint
.RESENDABLE
,
677 SignalServiceMessageSender
.IndividualSendEvents
.EMPTY
,
679 editTargetTimestamp
.get()));
680 messageSendLogStore
.insertIfPossible(message
.getTimestamp(), result
, ContentHint
.RESENDABLE
, urgent
);
681 handleSendMessageResult(result
);
685 private SendMessageResult
handleSendMessage(RecipientId recipientId
, SenderHandler s
) {
686 var messageSender
= dependencies
.getMessageSender();
688 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(recipientId
);
691 return s
.send(messageSender
, address
, context
.getUnidentifiedAccessHelper().getAccessFor(recipientId
));
692 } catch (UnregisteredUserException e
) {
693 final RecipientId newRecipientId
;
695 newRecipientId
= context
.getRecipientHelper().refreshRegisteredUser(recipientId
);
696 } catch (UnregisteredRecipientException ex
) {
697 return SendMessageResult
.unregisteredFailure(address
);
699 address
= context
.getRecipientHelper().resolveSignalServiceAddress(newRecipientId
);
700 return s
.send(messageSender
,
702 context
.getUnidentifiedAccessHelper().getAccessFor(newRecipientId
));
704 } catch (UnregisteredUserException e
) {
705 return SendMessageResult
.unregisteredFailure(address
);
706 } catch (ProofRequiredException e
) {
707 return SendMessageResult
.proofRequiredFailure(address
, e
);
708 } catch (RateLimitException e
) {
709 logger
.warn("Sending failed due to rate limiting from the signal server: {}", e
.getMessage());
710 return SendMessageResult
.rateLimitFailure(address
, e
);
711 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
712 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
713 } catch (IOException e
) {
714 logger
.warn("Failed to send message due to IO exception: {}", e
.getMessage());
715 logger
.debug("Exception", e
);
716 return SendMessageResult
.networkFailure(address
);
720 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
, Optional
<Long
> editTargetTimestamp
) {
721 var address
= account
.getSelfAddress();
722 var transcript
= new SentTranscriptMessage(Optional
.of(address
),
723 message
.getTimestamp(),
724 editTargetTimestamp
.isEmpty() ? Optional
.of(message
) : Optional
.empty(),
725 message
.getExpiresInSeconds(),
726 Map
.of(address
.getServiceId(), true),
730 editTargetTimestamp
.map((timestamp
) -> new SignalServiceEditMessage(timestamp
, message
)));
731 var syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
733 return sendSyncMessage(syncMessage
);
736 private void handleSendMessageResult(final SendMessageResult r
) {
737 if (r
.isSuccess() && !r
.getSuccess().isUnidentified()) {
738 final var recipientId
= account
.getRecipientResolver().resolveRecipient(r
.getAddress());
739 final var profile
= account
.getProfileStore().getProfile(recipientId
);
740 if (profile
!= null && (
741 profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.ENABLED
742 || profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.UNRESTRICTED
744 account
.getProfileStore()
745 .storeProfile(recipientId
,
746 Profile
.newBuilder(profile
)
747 .withUnidentifiedAccessMode(Profile
.UnidentifiedAccessMode
.UNKNOWN
)
751 if (r
.isUnregisteredFailure()) {
752 final var recipientId
= account
.getRecipientResolver().resolveRecipient(r
.getAddress());
753 final var profile
= account
.getProfileStore().getProfile(recipientId
);
754 if (profile
!= null && (
755 profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.ENABLED
756 || profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.UNRESTRICTED
758 account
.getProfileStore()
759 .storeProfile(recipientId
,
760 Profile
.newBuilder(profile
)
761 .withUnidentifiedAccessMode(Profile
.UnidentifiedAccessMode
.UNKNOWN
)
765 if (r
.getIdentityFailure() != null) {
766 final var recipientId
= account
.getRecipientResolver().resolveRecipient(r
.getAddress());
767 context
.getIdentityHelper()
768 .handleIdentityFailure(recipientId
, r
.getAddress().getServiceId(), r
.getIdentityFailure());
772 interface SenderHandler
{
774 SendMessageResult
send(
775 SignalServiceMessageSender messageSender
,
776 SignalServiceAddress address
,
777 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
778 ) throws IOException
, UnregisteredUserException
, ProofRequiredException
, RateLimitException
, org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
781 interface SenderKeySenderHandler
{
783 List
<SendMessageResult
> send(
784 DistributionId distributionId
,
785 List
<SignalServiceAddress
> recipients
,
786 List
<UnidentifiedAccess
> unidentifiedAccess
,
787 boolean isRecipientUpdate
788 ) throws IOException
, UntrustedIdentityException
, NoSessionException
, InvalidKeyException
, InvalidRegistrationIdException
;
791 interface LegacySenderHandler
{
793 List
<SendMessageResult
> send(
794 List
<SignalServiceAddress
> recipients
,
795 List
<Optional
<UnidentifiedAccessPair
>> unidentifiedAccess
,
796 boolean isRecipientUpdate
797 ) throws IOException
, UntrustedIdentityException
;