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
.Contact
;
15 import org
.asamk
.signal
.manager
.storage
.recipients
.Profile
;
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
.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
;
50 import java
.util
.Optional
;
52 import java
.util
.concurrent
.TimeUnit
;
53 import java
.util
.concurrent
.atomic
.AtomicLong
;
54 import java
.util
.stream
.Collectors
;
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
, final RecipientId recipientId
76 ) throws IOException
{
77 var contact
= account
.getContactStore().getContact(recipientId
);
78 if (contact
== null || !contact
.isProfileSharingEnabled()) {
79 final var contactBuilder
= contact
== null ? Contact
.newBuilder() : Contact
.newBuilder(contact
);
80 contact
= contactBuilder
.withProfileSharingEnabled(true).build();
81 account
.getContactStore().storeContact(recipientId
, contact
);
84 final var expirationTime
= contact
.getMessageExpirationTime();
85 messageBuilder
.withExpiration(expirationTime
);
87 if (!contact
.isBlocked()) {
88 final var profileKey
= account
.getProfileKey().serialize();
89 messageBuilder
.withProfileKey(profileKey
);
92 final var message
= messageBuilder
.build();
93 return sendMessage(message
, recipientId
);
97 * Send a group message to the given group
98 * The message is extended with the current expiration timer for the group and the group context.
100 public List
<SendMessageResult
> sendAsGroupMessage(
101 SignalServiceDataMessage
.Builder messageBuilder
, GroupId groupId
102 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
, GroupSendingNotAllowedException
{
103 final var g
= getGroupForSending(groupId
);
104 return sendAsGroupMessage(messageBuilder
, g
);
108 * Send a complete group message to the given recipients (should be current/old/new members)
109 * This method should only be used for create/update/quit group messages.
111 public List
<SendMessageResult
> sendGroupMessage(
112 final SignalServiceDataMessage message
,
113 final Set
<RecipientId
> recipientIds
,
114 final DistributionId distributionId
115 ) throws IOException
{
116 return sendGroupMessage(message
, recipientIds
, distributionId
, ContentHint
.IMPLICIT
);
119 public SendMessageResult
sendReceiptMessage(
120 final SignalServiceReceiptMessage receiptMessage
, final RecipientId recipientId
122 final var messageSendLogStore
= account
.getMessageSendLogStore();
123 final var result
= handleSendMessage(recipientId
,
124 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendReceipt(address
,
127 messageSendLogStore
.insertIfPossible(receiptMessage
.getWhen(), result
, ContentHint
.IMPLICIT
);
128 handleSendMessageResult(result
);
132 public SendMessageResult
sendProfileKey(RecipientId recipientId
) {
133 logger
.debug("Sending updated profile key to recipient: {}", recipientId
);
134 final var profileKey
= account
.getProfileKey().serialize();
135 final var message
= SignalServiceDataMessage
.newBuilder()
136 .asProfileKeyUpdate(true)
137 .withProfileKey(profileKey
)
139 return handleSendMessage(recipientId
,
140 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendDataMessage(address
,
142 ContentHint
.IMPLICIT
,
144 SignalServiceMessageSender
.IndividualSendEvents
.EMPTY
));
147 public SendMessageResult
sendRetryReceipt(
148 DecryptionErrorMessage errorMessage
, RecipientId recipientId
, Optional
<GroupId
> groupId
150 logger
.debug("Sending retry receipt for {} to {}, device: {}",
151 errorMessage
.getTimestamp(),
153 errorMessage
.getDeviceId());
154 final var result
= handleSendMessage(recipientId
,
155 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendRetryReceipt(address
,
157 groupId
.map(GroupId
::serialize
),
159 handleSendMessageResult(result
);
163 public SendMessageResult
sendNullMessage(RecipientId recipientId
) {
164 final var result
= handleSendMessage(recipientId
, SignalServiceMessageSender
::sendNullMessage
);
165 handleSendMessageResult(result
);
169 public SendMessageResult
sendSelfMessage(
170 SignalServiceDataMessage
.Builder messageBuilder
172 final var recipientId
= account
.getSelfRecipientId();
173 final var contact
= account
.getContactStore().getContact(recipientId
);
174 final var expirationTime
= contact
!= null ? contact
.getMessageExpirationTime() : 0;
175 messageBuilder
.withExpiration(expirationTime
);
177 var message
= messageBuilder
.build();
178 return sendSelfMessage(message
);
181 public SendMessageResult
sendSyncMessage(SignalServiceSyncMessage message
) {
182 var messageSender
= dependencies
.getMessageSender();
184 return messageSender
.sendSyncMessage(message
, context
.getUnidentifiedAccessHelper().getAccessForSync());
185 } catch (UnregisteredUserException e
) {
186 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
187 return SendMessageResult
.unregisteredFailure(address
);
188 } catch (ProofRequiredException e
) {
189 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
190 return SendMessageResult
.proofRequiredFailure(address
, e
);
191 } catch (RateLimitException e
) {
192 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
193 logger
.warn("Sending failed due to rate limiting from the signal server: {}", e
.getMessage());
194 return SendMessageResult
.rateLimitFailure(address
, e
);
195 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
196 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
197 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
198 } catch (IOException e
) {
199 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(account
.getSelfRecipientId());
200 logger
.warn("Failed to send message due to IO exception: {}", e
.getMessage());
201 return SendMessageResult
.networkFailure(address
);
205 public SendMessageResult
sendTypingMessage(
206 SignalServiceTypingMessage message
, RecipientId recipientId
208 final var result
= handleSendMessage(recipientId
,
209 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendTyping(List
.of(address
),
210 List
.of(unidentifiedAccess
),
213 handleSendMessageResult(result
);
217 public List
<SendMessageResult
> sendGroupTypingMessage(
218 SignalServiceTypingMessage message
, GroupId groupId
219 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, GroupSendingNotAllowedException
{
220 final var g
= getGroupForSending(groupId
);
221 if (g
.isAnnouncementGroup() && !g
.isAdmin(account
.getSelfRecipientId())) {
222 throw new GroupSendingNotAllowedException(groupId
, g
.getTitle());
224 final var distributionId
= g
.getDistributionId();
225 final var recipientIds
= g
.getMembersWithout(account
.getSelfRecipientId());
227 return sendGroupTypingMessage(message
, recipientIds
, distributionId
);
230 public SendMessageResult
resendMessage(
231 final RecipientId recipientId
, final long timestamp
, final MessageSendLogEntry messageSendLogEntry
233 logger
.trace("Resending message {} to {}", timestamp
, recipientId
);
234 if (messageSendLogEntry
.groupId().isEmpty()) {
235 return handleSendMessage(recipientId
,
236 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.resendContent(address
,
239 messageSendLogEntry
.content(),
240 messageSendLogEntry
.contentHint(),
244 final var groupId
= messageSendLogEntry
.groupId().get();
245 final var group
= account
.getGroupStore().getGroup(groupId
);
248 logger
.debug("Could not find a matching group for the groupId {}! Skipping message send.",
251 } else if (!group
.getMembers().contains(recipientId
)) {
252 logger
.warn("The target user is no longer in the group {}! Skipping message send.", groupId
.toBase64());
256 final var senderKeyDistributionMessage
= dependencies
.getMessageSender()
257 .getOrCreateNewGroupSession(group
.getDistributionId());
258 final var distributionBytes
= ByteString
.copyFrom(senderKeyDistributionMessage
.serialize());
259 final var contentToSend
= messageSendLogEntry
.content()
261 .setSenderKeyDistributionMessage(distributionBytes
)
264 final var result
= handleSendMessage(recipientId
,
265 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.resendContent(address
,
269 messageSendLogEntry
.contentHint(),
270 Optional
.of(group
.getGroupId().serialize())));
272 if (result
.isSuccess()) {
273 final var address
= context
.getRecipientHelper().resolveSignalServiceAddress(recipientId
);
274 final var addresses
= result
.getSuccess()
277 .map(device
-> new SignalProtocolAddress(address
.getIdentifier(), device
))
278 .collect(Collectors
.toList());
280 account
.getSenderKeyStore().markSenderKeySharedWith(group
.getDistributionId(), addresses
);
286 private List
<SendMessageResult
> sendAsGroupMessage(
287 final SignalServiceDataMessage
.Builder messageBuilder
, final GroupInfo g
288 ) throws IOException
, GroupSendingNotAllowedException
{
289 GroupUtils
.setGroupContext(messageBuilder
, g
);
290 messageBuilder
.withExpiration(g
.getMessageExpirationTimer());
292 final var message
= messageBuilder
.build();
293 final var recipients
= g
.getMembersWithout(account
.getSelfRecipientId());
295 if (g
.isAnnouncementGroup() && !g
.isAdmin(account
.getSelfRecipientId())) {
296 if (message
.getBody().isPresent()
297 || message
.getAttachments().isPresent()
298 || message
.getQuote().isPresent()
299 || message
.getPreviews().isPresent()
300 || message
.getMentions().isPresent()
301 || message
.getSticker().isPresent()) {
302 throw new GroupSendingNotAllowedException(g
.getGroupId(), g
.getTitle());
306 return sendGroupMessage(message
, recipients
, g
.getDistributionId(), ContentHint
.RESENDABLE
);
309 private List
<SendMessageResult
> sendGroupMessage(
310 final SignalServiceDataMessage message
,
311 final Set
<RecipientId
> recipientIds
,
312 final DistributionId distributionId
,
313 final ContentHint contentHint
314 ) throws IOException
{
315 final var messageSender
= dependencies
.getMessageSender();
316 final var messageSendLogStore
= account
.getMessageSendLogStore();
317 final AtomicLong entryId
= new AtomicLong(-1);
319 final LegacySenderHandler legacySender
= (recipients
, unidentifiedAccess
, isRecipientUpdate
) -> messageSender
.sendDataMessage(
325 SignalServiceMessageSender
.LegacyGroupEvents
.EMPTY
,
327 logger
.trace("Partial message send result: {}", sendResult
.isSuccess());
328 synchronized (entryId
) {
329 if (entryId
.get() == -1) {
330 final var newId
= messageSendLogStore
.insertIfPossible(message
.getTimestamp(),
335 messageSendLogStore
.addRecipientToExistingEntryIfPossible(entryId
.get(), sendResult
);
340 final SenderKeySenderHandler senderKeySender
= (distId
, recipients
, unidentifiedAccess
, isRecipientUpdate
) -> {
341 final var res
= messageSender
.sendGroupDataMessage(distId
,
347 SignalServiceMessageSender
.SenderKeyGroupEvents
.EMPTY
);
348 synchronized (entryId
) {
349 if (entryId
.get() == -1) {
350 final var newId
= messageSendLogStore
.insertIfPossible(message
.getTimestamp(), res
, contentHint
);
353 messageSendLogStore
.addRecipientToExistingEntryIfPossible(entryId
.get(), res
);
358 final var results
= sendGroupMessageInternal(legacySender
, senderKeySender
, recipientIds
, distributionId
);
360 for (var r
: results
) {
361 handleSendMessageResult(r
);
367 private List
<SendMessageResult
> sendGroupTypingMessage(
368 final SignalServiceTypingMessage message
,
369 final Set
<RecipientId
> recipientIds
,
370 final DistributionId distributionId
371 ) throws IOException
{
372 final var messageSender
= dependencies
.getMessageSender();
373 final var results
= sendGroupMessageInternal((recipients
, unidentifiedAccess
, isRecipientUpdate
) -> messageSender
.sendTyping(
378 (distId
, recipients
, unidentifiedAccess
, isRecipientUpdate
) -> messageSender
.sendGroupTyping(distId
,
385 for (var r
: results
) {
386 handleSendMessageResult(r
);
392 private GroupInfo
getGroupForSending(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
393 var g
= context
.getGroupHelper().getGroup(groupId
);
395 throw new GroupNotFoundException(groupId
);
397 if (!g
.isMember(account
.getSelfRecipientId())) {
398 throw new NotAGroupMemberException(groupId
, g
.getTitle());
403 private List
<SendMessageResult
> sendGroupMessageInternal(
404 final LegacySenderHandler legacySender
,
405 final SenderKeySenderHandler senderKeySender
,
406 final Set
<RecipientId
> recipientIds
,
407 final DistributionId distributionId
408 ) throws IOException
{
409 long startTime
= System
.currentTimeMillis();
410 // isRecipientUpdate is true if we've already sent this message to some recipients in the past, otherwise false.
411 final var isRecipientUpdate
= false;
412 Set
<RecipientId
> senderKeyTargets
= distributionId
== null
414 : getSenderKeyCapableRecipientIds(recipientIds
);
415 final var allResults
= new ArrayList
<SendMessageResult
>(recipientIds
.size());
417 if (senderKeyTargets
.size() > 0) {
418 final var results
= sendGroupMessageInternalWithSenderKey(senderKeySender
,
423 if (results
== null) {
424 senderKeyTargets
= Set
.of();
426 results
.stream().filter(SendMessageResult
::isSuccess
).forEach(allResults
::add
);
427 final var failedTargets
= results
.stream()
428 .filter(r
-> !r
.isSuccess())
429 .map(r
-> context
.getRecipientHelper().resolveRecipient(r
.getAddress()))
431 if (failedTargets
.size() > 0) {
432 senderKeyTargets
= new HashSet
<>(senderKeyTargets
);
433 failedTargets
.forEach(senderKeyTargets
::remove
);
438 final var legacyTargets
= new HashSet
<>(recipientIds
);
439 legacyTargets
.removeAll(senderKeyTargets
);
440 final boolean onlyTargetIsSelfWithLinkedDevice
= recipientIds
.isEmpty() && account
.isMultiDevice();
442 if (legacyTargets
.size() > 0 || onlyTargetIsSelfWithLinkedDevice
) {
443 if (legacyTargets
.size() > 0) {
444 logger
.debug("Need to do {} legacy sends.", legacyTargets
.size());
446 logger
.debug("Need to do a legacy send to send a sync message for a group of only ourselves.");
449 final List
<SendMessageResult
> results
= sendGroupMessageInternalWithLegacy(legacySender
,
451 isRecipientUpdate
|| allResults
.size() > 0);
452 allResults
.addAll(results
);
454 final var duration
= Duration
.ofMillis(System
.currentTimeMillis() - startTime
);
455 logger
.debug("Sending took {}", duration
.toString());
459 private Set
<RecipientId
> getSenderKeyCapableRecipientIds(final Set
<RecipientId
> recipientIds
) {
460 final var selfProfile
= context
.getProfileHelper().getSelfProfile();
461 if (selfProfile
== null || !selfProfile
.getCapabilities().contains(Profile
.Capability
.senderKey
)) {
462 logger
.debug("Not all of our devices support sender key. Using legacy.");
466 final var senderKeyTargets
= new HashSet
<RecipientId
>();
467 final var recipientList
= new ArrayList
<>(recipientIds
);
468 final var profiles
= context
.getProfileHelper().getRecipientProfiles(recipientList
).iterator();
469 for (final var recipientId
: recipientList
) {
470 final var profile
= profiles
.next();
471 if (profile
== null || !profile
.getCapabilities().contains(Profile
.Capability
.senderKey
)) {
475 final var access
= context
.getUnidentifiedAccessHelper().getAccessFor(recipientId
);
476 if (access
.isEmpty() || access
.get().getTargetUnidentifiedAccess().isEmpty()) {
480 final var identity
= account
.getIdentityKeyStore().getIdentityInfo(recipientId
);
481 if (identity
== null || !identity
.getTrustLevel().isTrusted()) {
485 senderKeyTargets
.add(recipientId
);
488 if (senderKeyTargets
.size() < 2) {
489 logger
.debug("Too few sender-key-capable users ({}). Doing all legacy sends.", senderKeyTargets
.size());
493 logger
.debug("Can use sender key for {}/{} recipients.", senderKeyTargets
.size(), recipientIds
.size());
494 return senderKeyTargets
;
497 private List
<SendMessageResult
> sendGroupMessageInternalWithLegacy(
498 final LegacySenderHandler sender
, final Set
<RecipientId
> recipientIds
, final boolean isRecipientUpdate
499 ) throws IOException
{
500 final var recipientIdList
= new ArrayList
<>(recipientIds
);
501 final var addresses
= recipientIdList
.stream()
502 .map(context
.getRecipientHelper()::resolveSignalServiceAddress
)
504 final var unidentifiedAccesses
= context
.getUnidentifiedAccessHelper().getAccessFor(recipientIdList
);
506 final var results
= sender
.send(addresses
, unidentifiedAccesses
, isRecipientUpdate
);
508 final var successCount
= results
.stream().filter(SendMessageResult
::isSuccess
).count();
509 logger
.debug("Successfully sent using 1:1 to {}/{} legacy targets.", successCount
, recipientIdList
.size());
511 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
516 private List
<SendMessageResult
> sendGroupMessageInternalWithSenderKey(
517 final SenderKeySenderHandler sender
,
518 final Set
<RecipientId
> recipientIds
,
519 final DistributionId distributionId
,
520 final boolean isRecipientUpdate
521 ) throws IOException
{
522 final var recipientIdList
= new ArrayList
<>(recipientIds
);
524 long keyCreateTime
= account
.getSenderKeyStore()
525 .getCreateTimeForOurKey(account
.getSelfRecipientId(), account
.getDeviceId(), distributionId
);
526 long keyAge
= System
.currentTimeMillis() - keyCreateTime
;
528 if (keyCreateTime
!= -1 && keyAge
> TimeUnit
.DAYS
.toMillis(14)) {
529 logger
.debug("DistributionId {} was created at {} and is {} ms old (~{} days). Rotating.",
533 TimeUnit
.MILLISECONDS
.toDays(keyAge
));
534 account
.getSenderKeyStore().deleteOurKey(account
.getSelfRecipientId(), distributionId
);
537 List
<SignalServiceAddress
> addresses
= recipientIdList
.stream()
538 .map(context
.getRecipientHelper()::resolveSignalServiceAddress
)
539 .collect(Collectors
.toList());
540 List
<UnidentifiedAccess
> unidentifiedAccesses
= context
.getUnidentifiedAccessHelper()
541 .getAccessFor(recipientIdList
)
544 .map(UnidentifiedAccessPair
::getTargetUnidentifiedAccess
)
546 .collect(Collectors
.toList());
549 List
<SendMessageResult
> results
= sender
.send(distributionId
,
551 unidentifiedAccesses
,
554 final var successCount
= results
.stream().filter(SendMessageResult
::isSuccess
).count();
555 logger
.debug("Successfully sent using sender key to {}/{} sender key targets.",
560 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
562 } catch (InvalidUnidentifiedAccessHeaderException e
) {
563 logger
.warn("Someone had a bad UD header. Falling back to legacy sends.", e
);
565 } catch (NoSessionException e
) {
566 logger
.warn("No session. Falling back to legacy sends.", e
);
567 account
.getSenderKeyStore().deleteOurKey(account
.getSelfRecipientId(), distributionId
);
569 } catch (InvalidKeyException e
) {
570 logger
.warn("Invalid key. Falling back to legacy sends.", e
);
571 account
.getSenderKeyStore().deleteOurKey(account
.getSelfRecipientId(), distributionId
);
573 } catch (InvalidRegistrationIdException e
) {
574 logger
.warn("Invalid registrationId. Falling back to legacy sends.", e
);
576 } catch (NotFoundException e
) {
577 logger
.warn("Someone was unregistered. Falling back to legacy sends.", e
);
582 private SendMessageResult
sendMessage(
583 SignalServiceDataMessage message
, RecipientId recipientId
585 final var messageSendLogStore
= account
.getMessageSendLogStore();
586 final var result
= handleSendMessage(recipientId
,
587 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendDataMessage(address
,
589 ContentHint
.RESENDABLE
,
591 SignalServiceMessageSender
.IndividualSendEvents
.EMPTY
));
592 messageSendLogStore
.insertIfPossible(message
.getTimestamp(), result
, ContentHint
.RESENDABLE
);
593 handleSendMessageResult(result
);
597 private SendMessageResult
handleSendMessage(RecipientId recipientId
, SenderHandler s
) {
598 var messageSender
= dependencies
.getMessageSender();
600 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(recipientId
);
603 return s
.send(messageSender
, address
, context
.getUnidentifiedAccessHelper().getAccessFor(recipientId
));
604 } catch (UnregisteredUserException e
) {
605 final RecipientId newRecipientId
;
607 newRecipientId
= context
.getRecipientHelper().refreshRegisteredUser(recipientId
);
608 } catch (UnregisteredRecipientException ex
) {
609 return SendMessageResult
.unregisteredFailure(address
);
611 address
= context
.getRecipientHelper().resolveSignalServiceAddress(newRecipientId
);
612 return s
.send(messageSender
,
614 context
.getUnidentifiedAccessHelper().getAccessFor(newRecipientId
));
616 } catch (UnregisteredUserException e
) {
617 return SendMessageResult
.unregisteredFailure(address
);
618 } catch (ProofRequiredException e
) {
619 return SendMessageResult
.proofRequiredFailure(address
, e
);
620 } catch (RateLimitException e
) {
621 logger
.warn("Sending failed due to rate limiting from the signal server: {}", e
.getMessage());
622 return SendMessageResult
.rateLimitFailure(address
, e
);
623 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
624 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
625 } catch (IOException e
) {
626 logger
.warn("Failed to send message due to IO exception: {}", e
.getMessage());
627 return SendMessageResult
.networkFailure(address
);
631 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) {
632 var address
= account
.getSelfAddress();
633 var transcript
= new SentTranscriptMessage(Optional
.of(address
),
634 message
.getTimestamp(),
635 Optional
.of(message
),
636 message
.getExpiresInSeconds(),
637 Map
.of(address
, true),
641 var syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
643 return sendSyncMessage(syncMessage
);
646 private void handleSendMessageResult(final SendMessageResult r
) {
647 if (r
.isSuccess() && !r
.getSuccess().isUnidentified()) {
648 final var recipientId
= context
.getRecipientHelper().resolveRecipient(r
.getAddress());
649 final var profile
= account
.getProfileStore().getProfile(recipientId
);
650 if (profile
!= null && (
651 profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.ENABLED
652 || profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.UNRESTRICTED
654 account
.getProfileStore()
655 .storeProfile(recipientId
,
656 Profile
.newBuilder(profile
)
657 .withUnidentifiedAccessMode(Profile
.UnidentifiedAccessMode
.UNKNOWN
)
661 if (r
.isUnregisteredFailure()) {
662 final var recipientId
= context
.getRecipientHelper().resolveRecipient(r
.getAddress());
663 final var profile
= account
.getProfileStore().getProfile(recipientId
);
664 if (profile
!= null && (
665 profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.ENABLED
666 || profile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.UNRESTRICTED
668 account
.getProfileStore()
669 .storeProfile(recipientId
,
670 Profile
.newBuilder(profile
)
671 .withUnidentifiedAccessMode(Profile
.UnidentifiedAccessMode
.UNKNOWN
)
675 if (r
.getIdentityFailure() != null) {
676 final var recipientId
= context
.getRecipientHelper().resolveRecipient(r
.getAddress());
677 context
.getIdentityHelper().handleIdentityFailure(recipientId
, r
.getIdentityFailure());
681 interface SenderHandler
{
683 SendMessageResult
send(
684 SignalServiceMessageSender messageSender
,
685 SignalServiceAddress address
,
686 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
687 ) throws IOException
, UnregisteredUserException
, ProofRequiredException
, RateLimitException
, org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
690 interface SenderKeySenderHandler
{
692 List
<SendMessageResult
> send(
693 DistributionId distributionId
,
694 List
<SignalServiceAddress
> recipients
,
695 List
<UnidentifiedAccess
> unidentifiedAccess
,
696 boolean isRecipientUpdate
697 ) throws IOException
, UntrustedIdentityException
, NoSessionException
, InvalidKeyException
, InvalidRegistrationIdException
;
700 interface LegacySenderHandler
{
702 List
<SendMessageResult
> send(
703 List
<SignalServiceAddress
> recipients
,
704 List
<Optional
<UnidentifiedAccessPair
>> unidentifiedAccess
,
705 boolean isRecipientUpdate
706 ) throws IOException
, UntrustedIdentityException
;