1 package org
.asamk
.signal
.manager
.helper
;
3 import com
.google
.protobuf
.ByteString
;
5 import org
.asamk
.signal
.manager
.SignalDependencies
;
6 import org
.asamk
.signal
.manager
.api
.UnregisteredRecipientException
;
7 import org
.asamk
.signal
.manager
.groups
.GroupId
;
8 import org
.asamk
.signal
.manager
.groups
.GroupNotFoundException
;
9 import org
.asamk
.signal
.manager
.groups
.GroupSendingNotAllowedException
;
10 import org
.asamk
.signal
.manager
.groups
.GroupUtils
;
11 import org
.asamk
.signal
.manager
.groups
.NotAGroupMemberException
;
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
.Profile
;
15 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
16 import org
.asamk
.signal
.manager
.storage
.sendLog
.MessageSendLogEntry
;
17 import org
.slf4j
.Logger
;
18 import org
.slf4j
.LoggerFactory
;
19 import org
.whispersystems
.libsignal
.InvalidKeyException
;
20 import org
.whispersystems
.libsignal
.InvalidRegistrationIdException
;
21 import org
.whispersystems
.libsignal
.NoSessionException
;
22 import org
.whispersystems
.libsignal
.SignalProtocolAddress
;
23 import org
.whispersystems
.libsignal
.protocol
.DecryptionErrorMessage
;
24 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
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
.SignalServiceReceiptMessage
;
33 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceTypingMessage
;
34 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
35 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
36 import org
.whispersystems
.signalservice
.api
.push
.DistributionId
;
37 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
38 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NotFoundException
;
39 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.ProofRequiredException
;
40 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.RateLimitException
;
41 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
42 import org
.whispersystems
.signalservice
.internal
.push
.exceptions
.InvalidUnidentifiedAccessHeaderException
;
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
;
51 import java
.util
.concurrent
.TimeUnit
;
52 import java
.util
.concurrent
.atomic
.AtomicLong
;
53 import java
.util
.stream
.Collectors
;
55 public class SendHelper
{
57 private final static Logger logger
= LoggerFactory
.getLogger(SendHelper
.class);
59 private final SignalAccount account
;
60 private final SignalDependencies dependencies
;
61 private final Context context
;
63 public SendHelper(final Context context
) {
64 this.account
= context
.getAccount();
65 this.dependencies
= context
.getDependencies();
66 this.context
= context
;
70 * Send a single message to one recipient.
71 * The message is extended with the current expiration timer.
73 public SendMessageResult
sendMessage(
74 final SignalServiceDataMessage
.Builder messageBuilder
, final RecipientId recipientId
75 ) throws IOException
{
76 final var contact
= account
.getContactStore().getContact(recipientId
);
77 final var expirationTime
= contact
!= null ? contact
.getMessageExpirationTime() : 0;
78 messageBuilder
.withExpiration(expirationTime
);
79 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
81 final var message
= messageBuilder
.build();
82 return sendMessage(message
, recipientId
);
86 * Send a group message to the given group
87 * The message is extended with the current expiration timer for the group and the group context.
89 public List
<SendMessageResult
> sendAsGroupMessage(
90 SignalServiceDataMessage
.Builder messageBuilder
, GroupId groupId
91 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
, GroupSendingNotAllowedException
{
92 final var g
= getGroupForSending(groupId
);
93 return sendAsGroupMessage(messageBuilder
, g
);
97 * Send a complete group message to the given recipients (should be current/old/new members)
98 * This method should only be used for create/update/quit group messages.
100 public List
<SendMessageResult
> sendGroupMessage(
101 final SignalServiceDataMessage message
,
102 final Set
<RecipientId
> recipientIds
,
103 final DistributionId distributionId
104 ) throws IOException
{
105 return sendGroupMessage(message
, recipientIds
, distributionId
, ContentHint
.IMPLICIT
);
108 public SendMessageResult
sendDeliveryReceipt(
109 RecipientId recipientId
, List
<Long
> messageIds
111 var receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
113 System
.currentTimeMillis());
115 return sendReceiptMessage(receiptMessage
, recipientId
);
118 public SendMessageResult
sendReceiptMessage(
119 final SignalServiceReceiptMessage receiptMessage
, final RecipientId recipientId
121 final var messageSendLogStore
= account
.getMessageSendLogStore();
122 final var result
= handleSendMessage(recipientId
,
123 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendReceipt(address
,
126 messageSendLogStore
.insertIfPossible(receiptMessage
.getWhen(), result
, ContentHint
.IMPLICIT
);
127 handleSendMessageResult(result
);
131 public SendMessageResult
sendRetryReceipt(
132 DecryptionErrorMessage errorMessage
, RecipientId recipientId
, Optional
<GroupId
> groupId
134 logger
.debug("Sending retry receipt for {} to {}, device: {}",
135 errorMessage
.getTimestamp(),
137 errorMessage
.getDeviceId());
138 final var result
= handleSendMessage(recipientId
,
139 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendRetryReceipt(address
,
141 groupId
.transform(GroupId
::serialize
),
143 handleSendMessageResult(result
);
147 public SendMessageResult
sendNullMessage(RecipientId recipientId
) {
148 final var result
= handleSendMessage(recipientId
, SignalServiceMessageSender
::sendNullMessage
);
149 handleSendMessageResult(result
);
153 public SendMessageResult
sendSelfMessage(
154 SignalServiceDataMessage
.Builder messageBuilder
156 final var recipientId
= account
.getSelfRecipientId();
157 final var contact
= account
.getContactStore().getContact(recipientId
);
158 final var expirationTime
= contact
!= null ? contact
.getMessageExpirationTime() : 0;
159 messageBuilder
.withExpiration(expirationTime
);
161 var message
= messageBuilder
.build();
162 return sendSelfMessage(message
);
165 public SendMessageResult
sendSyncMessage(SignalServiceSyncMessage message
) {
166 var messageSender
= dependencies
.getMessageSender();
168 return messageSender
.sendSyncMessage(message
, context
.getUnidentifiedAccessHelper().getAccessForSync());
169 } catch (UnregisteredUserException e
) {
170 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
171 return SendMessageResult
.unregisteredFailure(address
);
172 } catch (ProofRequiredException e
) {
173 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
174 return SendMessageResult
.proofRequiredFailure(address
, e
);
175 } catch (RateLimitException e
) {
176 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
177 logger
.warn("Sending failed due to rate limiting from the signal server: {}", e
.getMessage());
178 return SendMessageResult
.networkFailure(address
);
179 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
180 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
181 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
182 } catch (IOException e
) {
183 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
184 logger
.warn("Failed to send message due to IO exception: {}", e
.getMessage());
185 return SendMessageResult
.networkFailure(address
);
189 public SendMessageResult
sendTypingMessage(
190 SignalServiceTypingMessage message
, RecipientId recipientId
192 final var result
= handleSendMessage(recipientId
,
193 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendTyping(address
,
196 handleSendMessageResult(result
);
200 public List
<SendMessageResult
> sendGroupTypingMessage(
201 SignalServiceTypingMessage message
, GroupId groupId
202 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, GroupSendingNotAllowedException
{
203 final var g
= getGroupForSending(groupId
);
204 if (g
.isAnnouncementGroup() && !g
.isAdmin(account
.getSelfRecipientId())) {
205 throw new GroupSendingNotAllowedException(groupId
, g
.getTitle());
207 final var distributionId
= g
.getDistributionId();
208 final var recipientIds
= g
.getMembersWithout(account
.getSelfRecipientId());
210 return sendGroupTypingMessage(message
, recipientIds
, distributionId
);
213 public SendMessageResult
resendMessage(
214 final RecipientId recipientId
, final long timestamp
, final MessageSendLogEntry messageSendLogEntry
216 if (messageSendLogEntry
.groupId().isEmpty()) {
217 return handleSendMessage(recipientId
,
218 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.resendContent(address
,
221 messageSendLogEntry
.content(),
222 messageSendLogEntry
.contentHint(),
226 final var groupId
= messageSendLogEntry
.groupId().get();
227 final var group
= account
.getGroupStore().getGroup(groupId
);
230 logger
.debug("Could not find a matching group for the groupId {}! Skipping message send.",
233 } else if (!group
.getMembers().contains(recipientId
)) {
234 logger
.warn("The target user is no longer in the group {}! Skipping message send.", groupId
.toBase64());
238 final var senderKeyDistributionMessage
= dependencies
.getMessageSender()
239 .getOrCreateNewGroupSession(group
.getDistributionId());
240 final var distributionBytes
= ByteString
.copyFrom(senderKeyDistributionMessage
.serialize());
241 final var contentToSend
= messageSendLogEntry
.content()
243 .setSenderKeyDistributionMessage(distributionBytes
)
246 final var result
= handleSendMessage(recipientId
,
247 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.resendContent(address
,
251 messageSendLogEntry
.contentHint(),
252 Optional
.of(group
.getGroupId().serialize())));
254 if (result
.isSuccess()) {
255 final var address
= context
.getRecipientHelper().resolveSignalServiceAddress(recipientId
);
256 final var addresses
= result
.getSuccess()
259 .map(device
-> new SignalProtocolAddress(address
.getIdentifier(), device
))
260 .collect(Collectors
.toList());
262 account
.getSenderKeyStore().markSenderKeySharedWith(group
.getDistributionId(), addresses
);
268 private List
<SendMessageResult
> sendAsGroupMessage(
269 final SignalServiceDataMessage
.Builder messageBuilder
, final GroupInfo g
270 ) throws IOException
, GroupSendingNotAllowedException
{
271 GroupUtils
.setGroupContext(messageBuilder
, g
);
272 messageBuilder
.withExpiration(g
.getMessageExpirationTimer());
274 final var message
= messageBuilder
.build();
275 final var recipients
= g
.getMembersWithout(account
.getSelfRecipientId());
277 if (g
.isAnnouncementGroup() && !g
.isAdmin(account
.getSelfRecipientId())) {
278 if (message
.getBody().isPresent()
279 || message
.getAttachments().isPresent()
280 || message
.getQuote().isPresent()
281 || message
.getPreviews().isPresent()
282 || message
.getMentions().isPresent()
283 || message
.getSticker().isPresent()) {
284 throw new GroupSendingNotAllowedException(g
.getGroupId(), g
.getTitle());
288 return sendGroupMessage(message
, recipients
, g
.getDistributionId(), ContentHint
.RESENDABLE
);
291 private List
<SendMessageResult
> sendGroupMessage(
292 final SignalServiceDataMessage message
,
293 final Set
<RecipientId
> recipientIds
,
294 final DistributionId distributionId
,
295 final ContentHint contentHint
296 ) throws IOException
{
297 final var messageSender
= dependencies
.getMessageSender();
298 final var messageSendLogStore
= account
.getMessageSendLogStore();
299 final AtomicLong entryId
= new AtomicLong(-1);
301 final LegacySenderHandler legacySender
= (recipients
, unidentifiedAccess
, isRecipientUpdate
) -> messageSender
.sendDataMessage(
307 SignalServiceMessageSender
.LegacyGroupEvents
.EMPTY
,
309 logger
.trace("Partial message send result: {}", sendResult
.isSuccess());
310 synchronized (entryId
) {
311 if (entryId
.get() == -1) {
312 final var newId
= messageSendLogStore
.insertIfPossible(message
.getTimestamp(),
317 messageSendLogStore
.addRecipientToExistingEntryIfPossible(entryId
.get(), sendResult
);
322 final SenderKeySenderHandler senderKeySender
= (distId
, recipients
, unidentifiedAccess
, isRecipientUpdate
) -> {
323 final var res
= messageSender
.sendGroupDataMessage(distId
,
329 SignalServiceMessageSender
.SenderKeyGroupEvents
.EMPTY
);
330 synchronized (entryId
) {
331 if (entryId
.get() == -1) {
332 final var newId
= messageSendLogStore
.insertIfPossible(message
.getTimestamp(), res
, contentHint
);
335 messageSendLogStore
.addRecipientToExistingEntryIfPossible(entryId
.get(), res
);
340 final var results
= sendGroupMessageInternal(legacySender
, senderKeySender
, recipientIds
, distributionId
);
342 for (var r
: results
) {
343 handleSendMessageResult(r
);
349 private List
<SendMessageResult
> sendGroupTypingMessage(
350 final SignalServiceTypingMessage message
,
351 final Set
<RecipientId
> recipientIds
,
352 final DistributionId distributionId
353 ) throws IOException
{
354 final var messageSender
= dependencies
.getMessageSender();
355 final var results
= sendGroupMessageInternal((recipients
, unidentifiedAccess
, isRecipientUpdate
) -> messageSender
.sendTyping(
360 (distId
, recipients
, unidentifiedAccess
, isRecipientUpdate
) -> messageSender
.sendGroupTyping(distId
,
367 for (var r
: results
) {
368 handleSendMessageResult(r
);
374 private GroupInfo
getGroupForSending(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
375 var g
= context
.getGroupHelper().getGroup(groupId
);
377 throw new GroupNotFoundException(groupId
);
379 if (!g
.isMember(account
.getSelfRecipientId())) {
380 throw new NotAGroupMemberException(groupId
, g
.getTitle());
385 private List
<SendMessageResult
> sendGroupMessageInternal(
386 final LegacySenderHandler legacySender
,
387 final SenderKeySenderHandler senderKeySender
,
388 final Set
<RecipientId
> recipientIds
,
389 final DistributionId distributionId
390 ) throws IOException
{
391 long startTime
= System
.currentTimeMillis();
392 // isRecipientUpdate is true if we've already sent this message to some recipients in the past, otherwise false.
393 final var isRecipientUpdate
= false;
394 Set
<RecipientId
> senderKeyTargets
= distributionId
== null
396 : getSenderKeyCapableRecipientIds(recipientIds
);
397 final var allResults
= new ArrayList
<SendMessageResult
>(recipientIds
.size());
399 if (senderKeyTargets
.size() > 0) {
400 final var results
= sendGroupMessageInternalWithSenderKey(senderKeySender
,
405 if (results
== null) {
406 senderKeyTargets
= Set
.of();
408 results
.stream().filter(SendMessageResult
::isSuccess
).forEach(allResults
::add
);
409 final var failedTargets
= results
.stream()
410 .filter(r
-> !r
.isSuccess())
411 .map(r
-> context
.getRecipientHelper().resolveRecipient(r
.getAddress()))
413 if (failedTargets
.size() > 0) {
414 senderKeyTargets
= new HashSet
<>(senderKeyTargets
);
415 failedTargets
.forEach(senderKeyTargets
::remove
);
420 final var legacyTargets
= new HashSet
<>(recipientIds
);
421 legacyTargets
.removeAll(senderKeyTargets
);
422 final boolean onlyTargetIsSelfWithLinkedDevice
= recipientIds
.isEmpty() && account
.isMultiDevice();
424 if (legacyTargets
.size() > 0 || onlyTargetIsSelfWithLinkedDevice
) {
425 if (legacyTargets
.size() > 0) {
426 logger
.debug("Need to do {} legacy sends.", legacyTargets
.size());
428 logger
.debug("Need to do a legacy send to send a sync message for a group of only ourselves.");
431 final List
<SendMessageResult
> results
= sendGroupMessageInternalWithLegacy(legacySender
,
433 isRecipientUpdate
|| allResults
.size() > 0);
434 allResults
.addAll(results
);
436 final var duration
= Duration
.ofMillis(System
.currentTimeMillis() - startTime
);
437 logger
.debug("Sending took {}", duration
.toString());
441 private Set
<RecipientId
> getSenderKeyCapableRecipientIds(final Set
<RecipientId
> recipientIds
) {
442 final var selfProfile
= context
.getProfileHelper().getRecipientProfile(account
.getSelfRecipientId());
443 if (selfProfile
== null || !selfProfile
.getCapabilities().contains(Profile
.Capability
.senderKey
)) {
444 logger
.debug("Not all of our devices support sender key. Using legacy.");
448 final var senderKeyTargets
= new HashSet
<RecipientId
>();
449 final var recipientList
= new ArrayList
<>(recipientIds
);
450 final var profiles
= context
.getProfileHelper().getRecipientProfile(recipientList
).iterator();
451 for (final var recipientId
: recipientList
) {
452 final var profile
= profiles
.next();
453 if (profile
== null || !profile
.getCapabilities().contains(Profile
.Capability
.senderKey
)) {
457 final var access
= context
.getUnidentifiedAccessHelper().getAccessFor(recipientId
);
458 if (!access
.isPresent() || !access
.get().getTargetUnidentifiedAccess().isPresent()) {
462 final var identity
= account
.getIdentityKeyStore().getIdentity(recipientId
);
463 if (identity
== null || !identity
.getTrustLevel().isTrusted()) {
467 senderKeyTargets
.add(recipientId
);
470 if (senderKeyTargets
.size() < 2) {
471 logger
.debug("Too few sender-key-capable users ({}). Doing all legacy sends.", senderKeyTargets
.size());
475 logger
.debug("Can use sender key for {}/{} recipients.", senderKeyTargets
.size(), recipientIds
.size());
476 return senderKeyTargets
;
479 private List
<SendMessageResult
> sendGroupMessageInternalWithLegacy(
480 final LegacySenderHandler sender
, final Set
<RecipientId
> recipientIds
, final boolean isRecipientUpdate
481 ) throws IOException
{
482 final var recipientIdList
= new ArrayList
<>(recipientIds
);
483 final var addresses
= recipientIdList
.stream()
484 .map(context
.getRecipientHelper()::resolveSignalServiceAddress
)
486 final var unidentifiedAccesses
= context
.getUnidentifiedAccessHelper().getAccessFor(recipientIdList
);
488 final var results
= sender
.send(addresses
, unidentifiedAccesses
, isRecipientUpdate
);
490 final var successCount
= results
.stream().filter(SendMessageResult
::isSuccess
).count();
491 logger
.debug("Successfully sent using 1:1 to {}/{} legacy targets.", successCount
, recipientIdList
.size());
493 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
498 private List
<SendMessageResult
> sendGroupMessageInternalWithSenderKey(
499 final SenderKeySenderHandler sender
,
500 final Set
<RecipientId
> recipientIds
,
501 final DistributionId distributionId
,
502 final boolean isRecipientUpdate
503 ) throws IOException
{
504 final var recipientIdList
= new ArrayList
<>(recipientIds
);
506 long keyCreateTime
= account
.getSenderKeyStore()
507 .getCreateTimeForOurKey(account
.getSelfRecipientId(), account
.getDeviceId(), distributionId
);
508 long keyAge
= System
.currentTimeMillis() - keyCreateTime
;
510 if (keyCreateTime
!= -1 && keyAge
> TimeUnit
.DAYS
.toMillis(14)) {
511 logger
.debug("DistributionId {} was created at {} and is {} ms old (~{} days). Rotating.",
515 TimeUnit
.MILLISECONDS
.toDays(keyAge
));
516 account
.getSenderKeyStore().deleteOurKey(account
.getSelfRecipientId(), distributionId
);
519 List
<SignalServiceAddress
> addresses
= recipientIdList
.stream()
520 .map(context
.getRecipientHelper()::resolveSignalServiceAddress
)
521 .collect(Collectors
.toList());
522 List
<UnidentifiedAccess
> unidentifiedAccesses
= context
.getUnidentifiedAccessHelper()
523 .getAccessFor(recipientIdList
)
526 .map(UnidentifiedAccessPair
::getTargetUnidentifiedAccess
)
528 .collect(Collectors
.toList());
531 List
<SendMessageResult
> results
= sender
.send(distributionId
,
533 unidentifiedAccesses
,
536 final var successCount
= results
.stream().filter(SendMessageResult
::isSuccess
).count();
537 logger
.debug("Successfully sent using sender key to {}/{} sender key targets.",
542 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
544 } catch (InvalidUnidentifiedAccessHeaderException e
) {
545 logger
.warn("Someone had a bad UD header. Falling back to legacy sends.", e
);
547 } catch (NoSessionException e
) {
548 logger
.warn("No session. Falling back to legacy sends.", e
);
549 account
.getSenderKeyStore().deleteOurKey(account
.getSelfRecipientId(), distributionId
);
551 } catch (InvalidKeyException e
) {
552 logger
.warn("Invalid key. Falling back to legacy sends.", e
);
553 account
.getSenderKeyStore().deleteOurKey(account
.getSelfRecipientId(), distributionId
);
555 } catch (InvalidRegistrationIdException e
) {
556 logger
.warn("Invalid registrationId. Falling back to legacy sends.", e
);
558 } catch (NotFoundException e
) {
559 logger
.warn("Someone was unregistered. Falling back to legacy sends.", e
);
564 private SendMessageResult
sendMessage(
565 SignalServiceDataMessage message
, RecipientId recipientId
567 final var messageSendLogStore
= account
.getMessageSendLogStore();
568 final var result
= handleSendMessage(recipientId
,
569 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendDataMessage(address
,
571 ContentHint
.RESENDABLE
,
573 SignalServiceMessageSender
.IndividualSendEvents
.EMPTY
));
574 messageSendLogStore
.insertIfPossible(message
.getTimestamp(), result
, ContentHint
.RESENDABLE
);
575 handleSendMessageResult(result
);
579 private SendMessageResult
handleSendMessage(RecipientId recipientId
, SenderHandler s
) {
580 var messageSender
= dependencies
.getMessageSender();
582 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(recipientId
);
585 return s
.send(messageSender
, address
, context
.getUnidentifiedAccessHelper().getAccessFor(recipientId
));
586 } catch (UnregisteredUserException e
) {
587 final RecipientId newRecipientId
;
589 newRecipientId
= context
.getRecipientHelper().refreshRegisteredUser(recipientId
);
590 } catch (UnregisteredRecipientException ex
) {
591 return SendMessageResult
.unregisteredFailure(address
);
593 address
= context
.getRecipientHelper().resolveSignalServiceAddress(newRecipientId
);
594 return s
.send(messageSender
,
596 context
.getUnidentifiedAccessHelper().getAccessFor(newRecipientId
));
598 } catch (UnregisteredUserException e
) {
599 return SendMessageResult
.unregisteredFailure(address
);
600 } catch (ProofRequiredException e
) {
601 return SendMessageResult
.proofRequiredFailure(address
, e
);
602 } catch (RateLimitException e
) {
603 logger
.warn("Sending failed due to rate limiting from the signal server: {}", e
.getMessage());
604 return SendMessageResult
.networkFailure(address
);
605 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
606 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
607 } catch (IOException e
) {
608 logger
.warn("Failed to send message due to IO exception: {}", e
.getMessage());
609 return SendMessageResult
.networkFailure(address
);
613 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) {
614 var address
= account
.getSelfAddress();
615 var transcript
= new SentTranscriptMessage(Optional
.of(address
),
616 message
.getTimestamp(),
618 message
.getExpiresInSeconds(),
619 Map
.of(address
, true),
621 var syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
623 return sendSyncMessage(syncMessage
);
626 private void handleSendMessageResult(final SendMessageResult r
) {
627 if (r
.isSuccess() && !r
.getSuccess().isUnidentified()) {
628 final var recipientId
= context
.getRecipientHelper().resolveRecipient(r
.getAddress());
629 final var profile
= account
.getRecipientStore().getProfile(recipientId
);
630 if (profile
!= null && (
631 profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.ENABLED
632 || profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.UNRESTRICTED
634 account
.getRecipientStore()
635 .storeProfile(recipientId
,
636 Profile
.newBuilder(profile
)
637 .withUnidentifiedAccessMode(Profile
.UnidentifiedAccessMode
.UNKNOWN
)
641 if (r
.isUnregisteredFailure()) {
642 final var recipientId
= context
.getRecipientHelper().resolveRecipient(r
.getAddress());
643 final var profile
= account
.getRecipientStore().getProfile(recipientId
);
644 if (profile
!= null && (
645 profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.ENABLED
646 || profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.UNRESTRICTED
648 account
.getRecipientStore()
649 .storeProfile(recipientId
,
650 Profile
.newBuilder(profile
)
651 .withUnidentifiedAccessMode(Profile
.UnidentifiedAccessMode
.UNKNOWN
)
655 if (r
.getIdentityFailure() != null) {
656 final var recipientId
= context
.getRecipientHelper().resolveRecipient(r
.getAddress());
657 context
.getIdentityHelper().handleIdentityFailure(recipientId
, r
.getIdentityFailure());
661 interface SenderHandler
{
663 SendMessageResult
send(
664 SignalServiceMessageSender messageSender
,
665 SignalServiceAddress address
,
666 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
667 ) throws IOException
, UnregisteredUserException
, ProofRequiredException
, RateLimitException
, org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
670 interface SenderKeySenderHandler
{
672 List
<SendMessageResult
> send(
673 DistributionId distributionId
,
674 List
<SignalServiceAddress
> recipients
,
675 List
<UnidentifiedAccess
> unidentifiedAccess
,
676 boolean isRecipientUpdate
677 ) throws IOException
, UntrustedIdentityException
, NoSessionException
, InvalidKeyException
, InvalidRegistrationIdException
;
680 interface LegacySenderHandler
{
682 List
<SendMessageResult
> send(
683 List
<SignalServiceAddress
> recipients
,
684 List
<Optional
<UnidentifiedAccessPair
>> unidentifiedAccess
,
685 boolean isRecipientUpdate
686 ) throws IOException
, UntrustedIdentityException
;