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
.jetbrains
.annotations
.Nullable
;
17 import org
.signal
.libsignal
.protocol
.InvalidKeyException
;
18 import org
.signal
.libsignal
.protocol
.InvalidRegistrationIdException
;
19 import org
.signal
.libsignal
.protocol
.NoSessionException
;
20 import org
.signal
.libsignal
.protocol
.SignalProtocolAddress
;
21 import org
.signal
.libsignal
.protocol
.message
.DecryptionErrorMessage
;
22 import org
.slf4j
.Logger
;
23 import org
.slf4j
.LoggerFactory
;
24 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
25 import org
.whispersystems
.signalservice
.api
.crypto
.ContentHint
;
26 import org
.whispersystems
.signalservice
.api
.crypto
.SealedSenderAccess
;
27 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
28 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
29 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupSendEndorsements
;
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
;
44 import org
.whispersystems
.signalservice
.internal
.push
.http
.PartialSendCompleteListener
;
46 import java
.io
.IOException
;
47 import java
.time
.Duration
;
48 import java
.util
.ArrayList
;
49 import java
.util
.HashSet
;
50 import java
.util
.List
;
52 import java
.util
.Optional
;
54 import java
.util
.concurrent
.TimeUnit
;
55 import java
.util
.concurrent
.atomic
.AtomicLong
;
57 import okio
.ByteString
;
59 public class SendHelper
{
61 private static final Logger logger
= LoggerFactory
.getLogger(SendHelper
.class);
63 private final SignalAccount account
;
64 private final SignalDependencies dependencies
;
65 private final Context context
;
67 public SendHelper(final Context context
) {
68 this.account
= context
.getAccount();
69 this.dependencies
= context
.getDependencies();
70 this.context
= context
;
74 * Send a single message to one recipient.
75 * The message is extended with the current expiration timer.
77 public SendMessageResult
sendMessage(
78 final SignalServiceDataMessage
.Builder messageBuilder
,
79 final RecipientId recipientId
,
80 Optional
<Long
> editTargetTimestamp
82 var contact
= account
.getContactStore().getContact(recipientId
);
83 if (contact
== null || !contact
.isProfileSharingEnabled() || contact
.isHidden()) {
84 final var contactBuilder
= contact
== null ? Contact
.newBuilder() : Contact
.newBuilder(contact
);
85 contact
= contactBuilder
.withIsProfileSharingEnabled(true).withIsHidden(false).build();
86 account
.getContactStore().storeContact(recipientId
, contact
);
89 final var expirationTime
= contact
.messageExpirationTime();
90 messageBuilder
.withExpiration(expirationTime
);
92 if (!contact
.isBlocked()) {
93 final var profileKey
= account
.getProfileKey().serialize();
94 messageBuilder
.withProfileKey(profileKey
);
97 final var message
= messageBuilder
.build();
98 return sendMessage(message
, recipientId
, editTargetTimestamp
);
102 * Send a group message to the given group
103 * The message is extended with the current expiration timer for the group and the group context.
105 public List
<SendMessageResult
> sendAsGroupMessage(
106 final SignalServiceDataMessage
.Builder messageBuilder
,
107 final GroupId groupId
,
108 final boolean includeSelf
,
109 final Optional
<Long
> editTargetTimestamp
110 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
, GroupSendingNotAllowedException
{
111 final var g
= getGroupForSending(groupId
);
112 return sendAsGroupMessage(messageBuilder
, g
, includeSelf
, editTargetTimestamp
);
116 * Send a complete group message to the given recipients (should be current/old/new members)
117 * This method should only be used for create/update/quit group messages.
119 public List
<SendMessageResult
> sendGroupMessage(
120 final SignalServiceDataMessage message
,
121 final Set
<RecipientId
> recipientIds
,
122 final DistributionId distributionId
123 ) throws IOException
{
124 return sendGroupMessage(message
, recipientIds
, distributionId
, ContentHint
.IMPLICIT
, Optional
.empty());
127 public SendMessageResult
sendReceiptMessage(
128 final SignalServiceReceiptMessage receiptMessage
, final RecipientId recipientId
130 final var messageSendLogStore
= account
.getMessageSendLogStore();
131 final var result
= handleSendMessage(recipientId
,
132 (messageSender
, address
, unidentifiedAccess
, includePniSignature
) -> messageSender
.sendReceipt(address
,
135 includePniSignature
));
136 messageSendLogStore
.insertIfPossible(receiptMessage
.getWhen(), result
, ContentHint
.IMPLICIT
, false);
137 handleSendMessageResult(result
);
141 public SendMessageResult
sendProfileKey(RecipientId recipientId
) {
142 logger
.debug("Sending updated profile key to recipient: {}", recipientId
);
143 final var profileKey
= account
.getProfileKey().serialize();
144 final var message
= SignalServiceDataMessage
.newBuilder()
145 .asProfileKeyUpdate(true)
146 .withProfileKey(profileKey
)
148 return handleSendMessage(recipientId
,
149 (messageSender
, address
, unidentifiedAccess
, includePniSignature
) -> messageSender
.sendDataMessage(
152 ContentHint
.IMPLICIT
,
154 SignalServiceMessageSender
.IndividualSendEvents
.EMPTY
,
156 includePniSignature
));
159 public SendMessageResult
sendRetryReceipt(
160 DecryptionErrorMessage errorMessage
, RecipientId recipientId
, Optional
<GroupId
> groupId
162 logger
.debug("Sending retry receipt for {} to {}, device: {}",
163 errorMessage
.getTimestamp(),
165 errorMessage
.getDeviceId());
166 final var result
= handleSendMessage(recipientId
,
167 (messageSender
, address
, unidentifiedAccess
, includePniSignature
) -> messageSender
.sendRetryReceipt(
170 groupId
.map(GroupId
::serialize
),
172 handleSendMessageResult(result
);
176 public SendMessageResult
sendNullMessage(RecipientId recipientId
) {
177 final var result
= handleSendMessage(recipientId
,
178 (messageSender
, address
, unidentifiedAccess
, includePniSignature
) -> messageSender
.sendNullMessage(
180 unidentifiedAccess
));
181 handleSendMessageResult(result
);
185 public SendMessageResult
sendSelfMessage(
186 SignalServiceDataMessage
.Builder messageBuilder
, Optional
<Long
> editTargetTimestamp
188 final var recipientId
= account
.getSelfRecipientId();
189 final var contact
= account
.getContactStore().getContact(recipientId
);
190 final var expirationTime
= contact
!= null ? contact
.messageExpirationTime() : 0;
191 messageBuilder
.withExpiration(expirationTime
);
193 var message
= messageBuilder
.build();
194 return sendSelfMessage(message
, editTargetTimestamp
);
197 public SendMessageResult
sendSyncMessage(SignalServiceSyncMessage message
) {
198 var messageSender
= dependencies
.getMessageSender();
199 if (!account
.isMultiDevice()) {
200 logger
.trace("Not sending sync message because there are no linked devices.");
201 return SendMessageResult
.success(account
.getSelfAddress(), List
.of(), false, false, 0, Optional
.empty());
204 return messageSender
.sendSyncMessage(message
);
205 } catch (UnregisteredUserException e
) {
206 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
207 return SendMessageResult
.unregisteredFailure(address
);
208 } catch (ProofRequiredException e
) {
209 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
210 return SendMessageResult
.proofRequiredFailure(address
, e
);
211 } catch (RateLimitException e
) {
212 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
213 logger
.warn("Sending failed due to rate limiting from the signal server: {}", e
.getMessage());
214 return SendMessageResult
.rateLimitFailure(address
, e
);
215 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
216 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
217 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
218 } catch (IOException e
) {
219 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
220 logger
.warn("Failed to send message due to IO exception: {}", e
.getMessage());
221 logger
.debug("Exception", e
);
222 return SendMessageResult
.networkFailure(address
);
226 public SendMessageResult
sendTypingMessage(
227 SignalServiceTypingMessage message
, RecipientId recipientId
229 final var result
= handleSendMessage(recipientId
,
230 (messageSender
, address
, unidentifiedAccess
, includePniSignature
) -> messageSender
.sendTyping(List
.of(
231 address
), List
.of(unidentifiedAccess
), message
, null).getFirst());
232 handleSendMessageResult(result
);
236 public List
<SendMessageResult
> sendGroupTypingMessage(
237 SignalServiceTypingMessage message
, GroupId groupId
238 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, GroupSendingNotAllowedException
{
239 final var g
= getGroupForSending(groupId
);
240 if (g
.isAnnouncementGroup() && !g
.isAdmin(account
.getSelfRecipientId())) {
241 throw new GroupSendingNotAllowedException(groupId
, g
.getTitle());
243 final var distributionId
= g
.getDistributionId();
244 final var recipientIds
= g
.getMembersWithout(account
.getSelfRecipientId());
246 return sendGroupTypingMessage(message
, recipientIds
, distributionId
);
249 public SendMessageResult
resendMessage(
250 final RecipientId recipientId
, final long timestamp
, final MessageSendLogEntry messageSendLogEntry
252 logger
.trace("Resending message {} to {}", timestamp
, recipientId
);
253 if (messageSendLogEntry
.groupId().isEmpty()) {
254 return handleSendMessage(recipientId
,
255 (messageSender
, address
, unidentifiedAccess
, includePniSignature
) -> messageSender
.resendContent(
259 messageSendLogEntry
.content(),
260 messageSendLogEntry
.contentHint(),
262 messageSendLogEntry
.urgent()));
265 final var groupId
= messageSendLogEntry
.groupId().get();
266 final var group
= context
.getGroupHelper().getGroup(groupId
);
269 logger
.debug("Could not find a matching group for the groupId {}! Skipping message send.",
272 } else if (!group
.getMembers().contains(recipientId
)) {
273 logger
.warn("The target user is no longer in the group {}! Skipping message send.", groupId
.toBase64());
277 final var senderKeyDistributionMessage
= dependencies
.getMessageSender()
278 .getOrCreateNewGroupSession(group
.getDistributionId());
279 final var distributionBytes
= ByteString
.of(senderKeyDistributionMessage
.serialize());
280 final var contentToSend
= messageSendLogEntry
.content()
282 .senderKeyDistributionMessage(distributionBytes
)
285 final var result
= handleSendMessage(recipientId
,
286 (messageSender
, address
, unidentifiedAccess
, includePniSignature
) -> messageSender
.resendContent(address
,
290 messageSendLogEntry
.contentHint(),
291 Optional
.of(group
.getGroupId().serialize()),
292 messageSendLogEntry
.urgent()));
294 if (result
.isSuccess()) {
295 final var address
= context
.getRecipientHelper().resolveSignalServiceAddress(recipientId
);
296 final var addresses
= result
.getSuccess()
299 .map(device
-> new SignalProtocolAddress(address
.getIdentifier(), device
))
302 account
.getSenderKeyStore().markSenderKeySharedWith(group
.getDistributionId(), addresses
);
308 private List
<SendMessageResult
> sendAsGroupMessage(
309 final SignalServiceDataMessage
.Builder messageBuilder
,
311 final boolean includeSelf
,
312 final Optional
<Long
> editTargetTimestamp
313 ) throws IOException
, GroupSendingNotAllowedException
{
314 GroupUtils
.setGroupContext(messageBuilder
, g
);
315 messageBuilder
.withExpiration(g
.getMessageExpirationTimer());
317 final var message
= messageBuilder
.build();
318 final var recipients
= includeSelf ? g
.getMembers() : g
.getMembersWithout(account
.getSelfRecipientId());
320 if (g
.isAnnouncementGroup() && !g
.isAdmin(account
.getSelfRecipientId())) {
321 if (message
.getBody().isPresent()
322 || message
.getAttachments().isPresent()
323 || message
.getQuote().isPresent()
324 || message
.getPreviews().isPresent()
325 || message
.getMentions().isPresent()
326 || message
.getSticker().isPresent()) {
327 throw new GroupSendingNotAllowedException(g
.getGroupId(), g
.getTitle());
331 return sendGroupMessage(message
,
333 g
.getDistributionId(),
334 ContentHint
.RESENDABLE
,
335 editTargetTimestamp
);
338 private List
<SendMessageResult
> sendGroupMessage(
339 final SignalServiceDataMessage message
,
340 final Set
<RecipientId
> recipientIds
,
341 final DistributionId distributionId
,
342 final ContentHint contentHint
,
343 final Optional
<Long
> editTargetTimestamp
344 ) throws IOException
{
345 final var messageSender
= dependencies
.getMessageSender();
346 final var messageSendLogStore
= account
.getMessageSendLogStore();
347 final AtomicLong entryId
= new AtomicLong(-1);
349 final var urgent
= true;
350 final PartialSendCompleteListener partialSendCompleteListener
= sendResult
-> {
351 logger
.trace("Partial message send result: {}", sendResult
.isSuccess());
352 synchronized (entryId
) {
353 if (entryId
.get() == -1) {
354 final var newId
= messageSendLogStore
.insertIfPossible(message
.getTimestamp(),
360 messageSendLogStore
.addRecipientToExistingEntryIfPossible(entryId
.get(), sendResult
);
364 final LegacySenderHandler legacySender
= (recipients
, unidentifiedAccess
, isRecipientUpdate
) ->
365 editTargetTimestamp
.isEmpty()
366 ? messageSender
.sendDataMessage(recipients
,
371 SignalServiceMessageSender
.LegacyGroupEvents
.EMPTY
,
372 partialSendCompleteListener
,
375 : messageSender
.sendEditMessage(recipients
,
380 SignalServiceMessageSender
.LegacyGroupEvents
.EMPTY
,
381 partialSendCompleteListener
,
384 editTargetTimestamp
.get());
385 final SenderKeySenderHandler senderKeySender
= (distId
, recipients
, unidentifiedAccess
, groupSendEndorsements
, isRecipientUpdate
) -> messageSender
.sendGroupDataMessage(
389 groupSendEndorsements
,
393 SignalServiceMessageSender
.SenderKeyGroupEvents
.EMPTY
,
396 editTargetTimestamp
.map(timestamp
-> new SignalServiceEditMessage(timestamp
, message
)).orElse(null),
398 logger
.trace("Partial message send results: {}", sendResult
.size());
399 synchronized (entryId
) {
400 if (entryId
.get() == -1) {
401 final var newId
= messageSendLogStore
.insertIfPossible(message
.getTimestamp(),
407 messageSendLogStore
.addRecipientToExistingEntryIfPossible(entryId
.get(), sendResult
);
410 synchronized (entryId
) {
411 if (entryId
.get() == -1) {
412 final var newId
= messageSendLogStore
.insertIfPossible(message
.getTimestamp(),
418 messageSendLogStore
.addRecipientToExistingEntryIfPossible(entryId
.get(), sendResult
);
422 final var results
= sendGroupMessageInternal(legacySender
, senderKeySender
, recipientIds
, distributionId
);
424 for (var r
: results
) {
425 handleSendMessageResult(r
);
431 private List
<SendMessageResult
> sendGroupTypingMessage(
432 final SignalServiceTypingMessage message
,
433 final Set
<RecipientId
> recipientIds
,
434 final DistributionId distributionId
435 ) throws IOException
{
436 final var messageSender
= dependencies
.getMessageSender();
437 final var results
= sendGroupMessageInternal((recipients
, unidentifiedAccess
, isRecipientUpdate
) -> messageSender
.sendTyping(
442 (distId
, recipients
, unidentifiedAccess
, groupSendEndorsements
, isRecipientUpdate
) -> messageSender
.sendGroupTyping(
446 groupSendEndorsements
,
451 for (var r
: results
) {
452 handleSendMessageResult(r
);
458 private GroupInfo
getGroupForSending(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
459 var g
= context
.getGroupHelper().getGroup(groupId
);
461 throw new GroupNotFoundException(groupId
);
463 if (!g
.isMember(account
.getSelfRecipientId())) {
464 throw new NotAGroupMemberException(groupId
, g
.getTitle());
466 if (!g
.isProfileSharingEnabled()) {
467 g
.setProfileSharingEnabled(true);
468 account
.getGroupStore().updateGroup(g
);
473 private List
<SendMessageResult
> sendGroupMessageInternal(
474 final LegacySenderHandler legacySender
,
475 final SenderKeySenderHandler senderKeySender
,
476 final Set
<RecipientId
> recipientIds
,
477 final DistributionId distributionId
478 ) throws IOException
{
479 long startTime
= System
.currentTimeMillis();
480 // isRecipientUpdate is true if we've already sent this message to some recipients in the past, otherwise false.
481 final var isRecipientUpdate
= false;
482 Set
<RecipientId
> senderKeyTargets
= distributionId
== null
484 : getSenderKeyCapableRecipientIds(recipientIds
);
485 final var allResults
= new ArrayList
<SendMessageResult
>(recipientIds
.size());
487 if (!senderKeyTargets
.isEmpty()) {
488 final var results
= sendGroupMessageInternalWithSenderKey(senderKeySender
,
493 if (results
== null) {
494 senderKeyTargets
= Set
.of();
496 results
.stream().filter(SendMessageResult
::isSuccess
).forEach(allResults
::add
);
497 final var recipientResolver
= account
.getRecipientResolver();
498 final var failedTargets
= results
.stream()
499 .filter(r
-> !r
.isSuccess())
500 .map(r
-> recipientResolver
.resolveRecipient(r
.getAddress()))
502 if (!failedTargets
.isEmpty()) {
503 senderKeyTargets
= new HashSet
<>(senderKeyTargets
);
504 failedTargets
.forEach(senderKeyTargets
::remove
);
509 final var legacyTargets
= new HashSet
<>(recipientIds
);
510 legacyTargets
.removeAll(senderKeyTargets
);
511 final boolean onlyTargetIsSelfWithLinkedDevice
= recipientIds
.isEmpty() && account
.isMultiDevice();
513 if (!legacyTargets
.isEmpty() || onlyTargetIsSelfWithLinkedDevice
) {
514 if (!legacyTargets
.isEmpty()) {
515 logger
.debug("Need to do {} legacy sends.", legacyTargets
.size());
517 logger
.debug("Need to do a legacy send to send a sync message for a group of only ourselves.");
520 final List
<SendMessageResult
> results
= sendGroupMessageInternalWithLegacy(legacySender
,
522 isRecipientUpdate
|| !allResults
.isEmpty());
523 allResults
.addAll(results
);
525 final var duration
= Duration
.ofMillis(System
.currentTimeMillis() - startTime
);
526 logger
.debug("Sending took {}", duration
.toString());
530 private Set
<RecipientId
> getSenderKeyCapableRecipientIds(final Set
<RecipientId
> recipientIds
) {
531 final var senderKeyTargets
= new HashSet
<RecipientId
>();
532 final var recipientList
= new ArrayList
<>(recipientIds
);
533 for (final var recipientId
: recipientList
) {
534 final var access
= context
.getUnidentifiedAccessHelper().getSealedSenderAccessFor(recipientId
);
535 if (access
!= null) {
539 final var serviceId
= account
.getRecipientAddressResolver()
540 .resolveRecipientAddress(recipientId
)
543 if (serviceId
== null) {
546 final var identity
= account
.getIdentityKeyStore().getIdentityInfo(serviceId
);
547 if (identity
== null || !identity
.getTrustLevel().isTrusted()) {
551 senderKeyTargets
.add(recipientId
);
554 if (senderKeyTargets
.size() < 2) {
555 logger
.debug("Too few sender-key-capable users ({}). Doing all legacy sends.", senderKeyTargets
.size());
559 logger
.debug("Can use sender key for {}/{} recipients.", senderKeyTargets
.size(), recipientIds
.size());
560 return senderKeyTargets
;
563 private List
<SendMessageResult
> sendGroupMessageInternalWithLegacy(
564 final LegacySenderHandler sender
, final Set
<RecipientId
> recipientIds
, final boolean isRecipientUpdate
565 ) throws IOException
{
566 final var recipientIdList
= new ArrayList
<>(recipientIds
);
567 final var addresses
= recipientIdList
.stream()
568 .map(context
.getRecipientHelper()::resolveSignalServiceAddress
)
570 final var unidentifiedAccesses
= context
.getUnidentifiedAccessHelper()
571 .getSealedSenderAccessFor(recipientIdList
);
573 final var results
= sender
.send(addresses
, unidentifiedAccesses
, isRecipientUpdate
);
575 final var successCount
= results
.stream().filter(SendMessageResult
::isSuccess
).count();
576 logger
.debug("Successfully sent using 1:1 to {}/{} legacy targets.", successCount
, recipientIdList
.size());
578 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
583 private List
<SendMessageResult
> sendGroupMessageInternalWithSenderKey(
584 final SenderKeySenderHandler sender
,
585 final Set
<RecipientId
> recipientIds
,
586 final DistributionId distributionId
,
587 final boolean isRecipientUpdate
588 ) throws IOException
{
589 final var recipientIdList
= new ArrayList
<>(recipientIds
);
591 long keyCreateTime
= account
.getSenderKeyStore()
592 .getCreateTimeForOurKey(account
.getAci(), account
.getDeviceId(), distributionId
);
593 long keyAge
= System
.currentTimeMillis() - keyCreateTime
;
595 if (keyCreateTime
!= -1 && keyAge
> TimeUnit
.DAYS
.toMillis(14)) {
596 logger
.debug("DistributionId {} was created at {} and is {} ms old (~{} days). Rotating.",
600 TimeUnit
.MILLISECONDS
.toDays(keyAge
));
601 account
.getSenderKeyStore().deleteOurKey(account
.getAci(), distributionId
);
604 List
<SignalServiceAddress
> addresses
= recipientIdList
.stream()
605 .map(context
.getRecipientHelper()::resolveSignalServiceAddress
)
607 List
<UnidentifiedAccess
> unidentifiedAccesses
= context
.getUnidentifiedAccessHelper()
608 .getAccessFor(recipientIdList
)
612 final GroupSendEndorsements groupSendEndorsements
= null;//TODO
614 List
<SendMessageResult
> results
= sender
.send(distributionId
,
616 unidentifiedAccesses
,
617 groupSendEndorsements
,
620 final var successCount
= results
.stream().filter(SendMessageResult
::isSuccess
).count();
621 logger
.debug("Successfully sent using sender key to {}/{} sender key targets.",
626 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
628 } catch (InvalidUnidentifiedAccessHeaderException e
) {
629 logger
.warn("Someone had a bad UD header. Falling back to legacy sends.", e
);
631 } catch (NoSessionException e
) {
632 logger
.warn("No session. Falling back to legacy sends.", e
);
633 account
.getSenderKeyStore().deleteOurKey(account
.getAci(), distributionId
);
635 } catch (InvalidKeyException e
) {
636 logger
.warn("Invalid key. Falling back to legacy sends.", e
);
637 account
.getSenderKeyStore().deleteOurKey(account
.getAci(), distributionId
);
639 } catch (InvalidRegistrationIdException e
) {
640 logger
.warn("Invalid registrationId. Falling back to legacy sends.", e
);
642 } catch (NotFoundException e
) {
643 logger
.warn("Someone was unregistered. Falling back to legacy sends.", e
);
645 } catch (IOException e
) {
646 if (e
.getCause() instanceof InvalidKeyException
) {
647 logger
.warn("Invalid key. Falling back to legacy sends.", e
);
655 private SendMessageResult
sendMessage(
656 SignalServiceDataMessage message
, RecipientId recipientId
, Optional
<Long
> editTargetTimestamp
658 final var messageSendLogStore
= account
.getMessageSendLogStore();
659 final var urgent
= true;
660 final var result
= handleSendMessage(recipientId
,
661 editTargetTimestamp
.isEmpty()
662 ?
(messageSender
, address
, unidentifiedAccess
, includePniSignature
) -> messageSender
.sendDataMessage(
665 ContentHint
.RESENDABLE
,
667 SignalServiceMessageSender
.IndividualSendEvents
.EMPTY
,
670 : (messageSender
, address
, unidentifiedAccess
, includePniSignature
) -> messageSender
.sendEditMessage(
673 ContentHint
.RESENDABLE
,
675 SignalServiceMessageSender
.IndividualSendEvents
.EMPTY
,
677 editTargetTimestamp
.get()));
678 messageSendLogStore
.insertIfPossible(message
.getTimestamp(), result
, ContentHint
.RESENDABLE
, urgent
);
679 handleSendMessageResult(result
);
683 private SendMessageResult
handleSendMessage(RecipientId recipientId
, SenderHandler s
) {
684 var messageSender
= dependencies
.getMessageSender();
686 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(recipientId
);
688 final boolean includePniSignature
= account
.getRecipientStore().needsPniSignature(recipientId
);
690 return s
.send(messageSender
,
692 context
.getUnidentifiedAccessHelper().getSealedSenderAccessFor(recipientId
),
693 includePniSignature
);
694 } catch (UnregisteredUserException e
) {
695 final RecipientId newRecipientId
;
697 newRecipientId
= context
.getRecipientHelper().refreshRegisteredUser(recipientId
);
698 } catch (UnregisteredRecipientException ex
) {
699 return SendMessageResult
.unregisteredFailure(address
);
701 address
= context
.getRecipientHelper().resolveSignalServiceAddress(newRecipientId
);
702 return s
.send(messageSender
,
704 context
.getUnidentifiedAccessHelper().getSealedSenderAccessFor(newRecipientId
),
705 includePniSignature
);
707 } catch (UnregisteredUserException e
) {
708 return SendMessageResult
.unregisteredFailure(address
);
709 } catch (ProofRequiredException e
) {
710 return SendMessageResult
.proofRequiredFailure(address
, e
);
711 } catch (RateLimitException e
) {
712 logger
.warn("Sending failed due to rate limiting from the signal server: {}", e
.getMessage());
713 return SendMessageResult
.rateLimitFailure(address
, e
);
714 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
715 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
716 } catch (IOException e
) {
717 logger
.warn("Failed to send message due to IO exception: {}", e
.getMessage());
718 logger
.debug("Exception", e
);
719 return SendMessageResult
.networkFailure(address
);
723 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
, Optional
<Long
> editTargetTimestamp
) {
724 var address
= account
.getSelfAddress();
725 var transcript
= new SentTranscriptMessage(Optional
.of(address
),
726 message
.getTimestamp(),
727 editTargetTimestamp
.isEmpty() ? Optional
.of(message
) : Optional
.empty(),
728 message
.getExpiresInSeconds(),
729 Map
.of(address
.getServiceId(), true),
733 editTargetTimestamp
.map((timestamp
) -> new SignalServiceEditMessage(timestamp
, message
)));
734 var syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
736 return sendSyncMessage(syncMessage
);
739 private void handleSendMessageResult(final SendMessageResult r
) {
740 if (r
.isSuccess() && !r
.getSuccess().isUnidentified()) {
741 final var recipientId
= account
.getRecipientResolver().resolveRecipient(r
.getAddress());
742 final var profile
= account
.getProfileStore().getProfile(recipientId
);
743 if (profile
!= null && (
744 profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.ENABLED
745 || profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.UNRESTRICTED
747 account
.getProfileStore()
748 .storeProfile(recipientId
,
749 Profile
.newBuilder(profile
)
750 .withUnidentifiedAccessMode(Profile
.UnidentifiedAccessMode
.UNKNOWN
)
754 if (r
.isUnregisteredFailure()) {
755 final var recipientId
= account
.getRecipientResolver().resolveRecipient(r
.getAddress());
756 final var profile
= account
.getProfileStore().getProfile(recipientId
);
757 if (profile
!= null && (
758 profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.ENABLED
759 || profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.UNRESTRICTED
761 account
.getProfileStore()
762 .storeProfile(recipientId
,
763 Profile
.newBuilder(profile
)
764 .withUnidentifiedAccessMode(Profile
.UnidentifiedAccessMode
.UNKNOWN
)
768 if (r
.getIdentityFailure() != null) {
769 final var recipientId
= account
.getRecipientResolver().resolveRecipient(r
.getAddress());
770 context
.getIdentityHelper()
771 .handleIdentityFailure(recipientId
, r
.getAddress().getServiceId(), r
.getIdentityFailure());
775 interface SenderHandler
{
777 SendMessageResult
send(
778 SignalServiceMessageSender messageSender
,
779 SignalServiceAddress address
,
780 @Nullable SealedSenderAccess unidentifiedAccess
,
781 boolean includePniSignature
782 ) throws IOException
, UnregisteredUserException
, ProofRequiredException
, RateLimitException
, org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
785 interface SenderKeySenderHandler
{
787 List
<SendMessageResult
> send(
788 DistributionId distributionId
,
789 List
<SignalServiceAddress
> recipients
,
790 List
<UnidentifiedAccess
> unidentifiedAccess
,
791 GroupSendEndorsements groupSendEndorsements
,
792 boolean isRecipientUpdate
793 ) throws IOException
, UntrustedIdentityException
, NoSessionException
, InvalidKeyException
, InvalidRegistrationIdException
;
796 interface LegacySenderHandler
{
798 List
<SendMessageResult
> send(
799 List
<SignalServiceAddress
> recipients
,
800 List
<SealedSenderAccess
> unidentifiedAccess
,
801 boolean isRecipientUpdate
802 ) throws IOException
, UntrustedIdentityException
;