1 package org
.asamk
.signal
.manager
.helper
;
3 import org
.asamk
.signal
.manager
.SignalDependencies
;
4 import org
.asamk
.signal
.manager
.groups
.GroupId
;
5 import org
.asamk
.signal
.manager
.groups
.GroupNotFoundException
;
6 import org
.asamk
.signal
.manager
.groups
.GroupSendingNotAllowedException
;
7 import org
.asamk
.signal
.manager
.groups
.GroupUtils
;
8 import org
.asamk
.signal
.manager
.groups
.NotAGroupMemberException
;
9 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
10 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfo
;
11 import org
.asamk
.signal
.manager
.storage
.recipients
.Profile
;
12 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
13 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientResolver
;
14 import org
.slf4j
.Logger
;
15 import org
.slf4j
.LoggerFactory
;
16 import org
.whispersystems
.libsignal
.InvalidKeyException
;
17 import org
.whispersystems
.libsignal
.InvalidRegistrationIdException
;
18 import org
.whispersystems
.libsignal
.NoSessionException
;
19 import org
.whispersystems
.libsignal
.protocol
.DecryptionErrorMessage
;
20 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
21 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
22 import org
.whispersystems
.signalservice
.api
.crypto
.ContentHint
;
23 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
24 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
25 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
26 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
27 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
28 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceTypingMessage
;
29 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
30 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
31 import org
.whispersystems
.signalservice
.api
.push
.DistributionId
;
32 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
33 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NotFoundException
;
34 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.ProofRequiredException
;
35 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.RateLimitException
;
36 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
37 import org
.whispersystems
.signalservice
.internal
.push
.exceptions
.InvalidUnidentifiedAccessHeaderException
;
39 import java
.io
.IOException
;
40 import java
.util
.ArrayList
;
41 import java
.util
.HashSet
;
42 import java
.util
.List
;
45 import java
.util
.concurrent
.TimeUnit
;
46 import java
.util
.stream
.Collectors
;
48 public class SendHelper
{
50 private final static Logger logger
= LoggerFactory
.getLogger(SendHelper
.class);
52 private final SignalAccount account
;
53 private final SignalDependencies dependencies
;
54 private final UnidentifiedAccessHelper unidentifiedAccessHelper
;
55 private final SignalServiceAddressResolver addressResolver
;
56 private final RecipientResolver recipientResolver
;
57 private final IdentityFailureHandler identityFailureHandler
;
58 private final GroupProvider groupProvider
;
59 private final ProfileProvider profileProvider
;
60 private final RecipientRegistrationRefresher recipientRegistrationRefresher
;
63 final SignalAccount account
,
64 final SignalDependencies dependencies
,
65 final UnidentifiedAccessHelper unidentifiedAccessHelper
,
66 final SignalServiceAddressResolver addressResolver
,
67 final RecipientResolver recipientResolver
,
68 final IdentityFailureHandler identityFailureHandler
,
69 final GroupProvider groupProvider
,
70 final ProfileProvider profileProvider
,
71 final RecipientRegistrationRefresher recipientRegistrationRefresher
73 this.account
= account
;
74 this.dependencies
= dependencies
;
75 this.unidentifiedAccessHelper
= unidentifiedAccessHelper
;
76 this.addressResolver
= addressResolver
;
77 this.recipientResolver
= recipientResolver
;
78 this.identityFailureHandler
= identityFailureHandler
;
79 this.groupProvider
= groupProvider
;
80 this.profileProvider
= profileProvider
;
81 this.recipientRegistrationRefresher
= recipientRegistrationRefresher
;
85 * Send a single message to one recipient.
86 * The message is extended with the current expiration timer.
88 public SendMessageResult
sendMessage(
89 final SignalServiceDataMessage
.Builder messageBuilder
, final RecipientId recipientId
90 ) throws IOException
{
91 final var contact
= account
.getContactStore().getContact(recipientId
);
92 final var expirationTime
= contact
!= null ? contact
.getMessageExpirationTime() : 0;
93 messageBuilder
.withExpiration(expirationTime
);
94 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
96 final var message
= messageBuilder
.build();
97 final var result
= sendMessage(message
, recipientId
);
98 handleSendMessageResult(result
);
103 * Send a group message to the given group
104 * The message is extended with the current expiration timer for the group and the group context.
106 public List
<SendMessageResult
> sendAsGroupMessage(
107 SignalServiceDataMessage
.Builder messageBuilder
, GroupId groupId
108 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
, GroupSendingNotAllowedException
{
109 final var g
= getGroupForSending(groupId
);
110 return sendAsGroupMessage(messageBuilder
, g
);
113 private List
<SendMessageResult
> sendAsGroupMessage(
114 final SignalServiceDataMessage
.Builder messageBuilder
, final GroupInfo g
115 ) throws IOException
, GroupSendingNotAllowedException
{
116 GroupUtils
.setGroupContext(messageBuilder
, g
);
117 messageBuilder
.withExpiration(g
.getMessageExpirationTimer());
119 final var message
= messageBuilder
.build();
120 final var recipients
= g
.getMembersWithout(account
.getSelfRecipientId());
122 if (g
.isAnnouncementGroup() && !g
.isAdmin(account
.getSelfRecipientId())) {
123 if (message
.getBody().isPresent()
124 || message
.getAttachments().isPresent()
125 || message
.getQuote().isPresent()
126 || message
.getPreviews().isPresent()
127 || message
.getMentions().isPresent()
128 || message
.getSticker().isPresent()) {
129 throw new GroupSendingNotAllowedException(g
.getGroupId(), g
.getTitle());
133 return sendGroupMessage(message
, recipients
, g
.getDistributionId());
137 * Send a complete group message to the given recipients (should be current/old/new members)
138 * This method should only be used for create/update/quit group messages.
140 public List
<SendMessageResult
> sendGroupMessage(
141 final SignalServiceDataMessage message
,
142 final Set
<RecipientId
> recipientIds
,
143 final DistributionId distributionId
144 ) throws IOException
{
145 List
<SendMessageResult
> result
= sendGroupMessageInternal(message
, recipientIds
, distributionId
);
147 for (var r
: result
) {
148 handleSendMessageResult(r
);
154 public SendMessageResult
sendDeliveryReceipt(
155 RecipientId recipientId
, List
<Long
> messageIds
157 var receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
159 System
.currentTimeMillis());
161 return sendReceiptMessage(receiptMessage
, recipientId
);
164 public SendMessageResult
sendReceiptMessage(
165 final SignalServiceReceiptMessage receiptMessage
, final RecipientId recipientId
167 return handleSendMessage(recipientId
,
168 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendReceipt(address
,
173 public SendMessageResult
sendRetryReceipt(
174 DecryptionErrorMessage errorMessage
, RecipientId recipientId
, Optional
<GroupId
> groupId
176 logger
.debug("Sending retry receipt for {} to {}, device: {}",
177 errorMessage
.getTimestamp(),
179 errorMessage
.getDeviceId());
180 return handleSendMessage(recipientId
,
181 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendRetryReceipt(address
,
183 groupId
.transform(GroupId
::serialize
),
187 public SendMessageResult
sendNullMessage(RecipientId recipientId
) {
188 return handleSendMessage(recipientId
, SignalServiceMessageSender
::sendNullMessage
);
191 public SendMessageResult
sendSelfMessage(
192 SignalServiceDataMessage
.Builder messageBuilder
194 final var recipientId
= account
.getSelfRecipientId();
195 final var contact
= account
.getContactStore().getContact(recipientId
);
196 final var expirationTime
= contact
!= null ? contact
.getMessageExpirationTime() : 0;
197 messageBuilder
.withExpiration(expirationTime
);
199 var message
= messageBuilder
.build();
200 return sendSelfMessage(message
);
203 public SendMessageResult
sendSyncMessage(SignalServiceSyncMessage message
) {
204 var messageSender
= dependencies
.getMessageSender();
206 return messageSender
.sendSyncMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
207 } catch (UnregisteredUserException e
) {
208 var address
= addressResolver
.resolveSignalServiceAddress(account
.getSelfRecipientId());
209 return SendMessageResult
.unregisteredFailure(address
);
210 } catch (ProofRequiredException e
) {
211 var address
= addressResolver
.resolveSignalServiceAddress(account
.getSelfRecipientId());
212 return SendMessageResult
.proofRequiredFailure(address
, e
);
213 } catch (RateLimitException e
) {
214 var address
= addressResolver
.resolveSignalServiceAddress(account
.getSelfRecipientId());
215 logger
.warn("Sending failed due to rate limiting from the signal server: {}", e
.getMessage());
216 return SendMessageResult
.networkFailure(address
);
217 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
218 var address
= addressResolver
.resolveSignalServiceAddress(account
.getSelfRecipientId());
219 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
220 } catch (IOException e
) {
221 var address
= addressResolver
.resolveSignalServiceAddress(account
.getSelfRecipientId());
222 logger
.warn("Failed to send message due to IO exception: {}", e
.getMessage());
223 return SendMessageResult
.networkFailure(address
);
227 public SendMessageResult
sendTypingMessage(
228 SignalServiceTypingMessage message
, RecipientId recipientId
230 return handleSendMessage(recipientId
,
231 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendTyping(address
,
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 messageSender
= dependencies
.getMessageSender();
244 final var recipientIdList
= new ArrayList
<>(g
.getMembersWithout(account
.getSelfRecipientId()));
245 final var addresses
= recipientIdList
.stream().map(addressResolver
::resolveSignalServiceAddress
).toList();
246 return messageSender
.sendTyping(addresses
,
247 unidentifiedAccessHelper
.getAccessFor(recipientIdList
),
252 private GroupInfo
getGroupForSending(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
253 var g
= groupProvider
.getGroup(groupId
);
255 throw new GroupNotFoundException(groupId
);
257 if (!g
.isMember(account
.getSelfRecipientId())) {
258 throw new NotAGroupMemberException(groupId
, g
.getTitle());
263 private List
<SendMessageResult
> sendGroupMessageInternal(
264 final SignalServiceDataMessage message
,
265 final Set
<RecipientId
> recipientIds
,
266 final DistributionId distributionId
267 ) throws IOException
{
268 // isRecipientUpdate is true if we've already sent this message to some recipients in the past, otherwise false.
269 final var isRecipientUpdate
= false;
270 Set
<RecipientId
> senderKeyTargets
= distributionId
== null
272 : getSenderKeyCapableRecipientIds(recipientIds
);
273 final var allResults
= new ArrayList
<SendMessageResult
>(recipientIds
.size());
275 if (senderKeyTargets
.size() > 0) {
276 final var results
= sendGroupMessageInternalWithSenderKey(message
,
281 if (results
== null) {
282 senderKeyTargets
= Set
.of();
284 results
.stream().filter(SendMessageResult
::isSuccess
).forEach(allResults
::add
);
285 final var failedTargets
= results
.stream()
286 .filter(r
-> !r
.isSuccess())
287 .map(r
-> recipientResolver
.resolveRecipient(r
.getAddress()))
289 if (failedTargets
.size() > 0) {
290 senderKeyTargets
= new HashSet
<>(senderKeyTargets
);
291 failedTargets
.forEach(senderKeyTargets
::remove
);
296 final var legacyTargets
= new HashSet
<>(recipientIds
);
297 legacyTargets
.removeAll(senderKeyTargets
);
298 final boolean onlyTargetIsSelfWithLinkedDevice
= recipientIds
.isEmpty() && account
.isMultiDevice();
300 if (legacyTargets
.size() > 0 || onlyTargetIsSelfWithLinkedDevice
) {
301 if (legacyTargets
.size() > 0) {
302 logger
.debug("Need to do {} legacy sends.", legacyTargets
.size());
304 logger
.debug("Need to do a legacy send to send a sync message for a group of only ourselves.");
307 final List
<SendMessageResult
> results
= sendGroupMessageInternalWithLegacy(message
,
309 isRecipientUpdate
|| allResults
.size() > 0);
310 allResults
.addAll(results
);
316 private Set
<RecipientId
> getSenderKeyCapableRecipientIds(final Set
<RecipientId
> recipientIds
) {
317 final var selfProfile
= profileProvider
.getProfile(account
.getSelfRecipientId());
318 if (selfProfile
== null || !selfProfile
.getCapabilities().contains(Profile
.Capability
.senderKey
)) {
319 logger
.debug("Not all of our devices support sender key. Using legacy.");
323 final var senderKeyTargets
= new HashSet
<RecipientId
>();
324 for (final var recipientId
: recipientIds
) {
325 // TODO filter out unregistered
326 final var profile
= profileProvider
.getProfile(recipientId
);
327 if (profile
== null || !profile
.getCapabilities().contains(Profile
.Capability
.senderKey
)) {
331 final var access
= unidentifiedAccessHelper
.getAccessFor(recipientId
);
332 if (!access
.isPresent() || !access
.get().getTargetUnidentifiedAccess().isPresent()) {
336 final var identity
= account
.getIdentityKeyStore().getIdentity(recipientId
);
337 if (identity
== null || !identity
.getTrustLevel().isTrusted()) {
341 senderKeyTargets
.add(recipientId
);
344 if (senderKeyTargets
.size() < 2) {
345 logger
.debug("Too few sender-key-capable users ({}). Doing all legacy sends.", senderKeyTargets
.size());
349 logger
.debug("Can use sender key for {}/{} recipients.", senderKeyTargets
.size(), recipientIds
.size());
350 return senderKeyTargets
;
353 private List
<SendMessageResult
> sendGroupMessageInternalWithLegacy(
354 final SignalServiceDataMessage message
, final Set
<RecipientId
> recipientIds
, final boolean isRecipientUpdate
355 ) throws IOException
{
356 final var recipientIdList
= new ArrayList
<>(recipientIds
);
357 final var addresses
= recipientIdList
.stream().map(addressResolver
::resolveSignalServiceAddress
).toList();
358 final var unidentifiedAccesses
= unidentifiedAccessHelper
.getAccessFor(recipientIdList
);
359 final var messageSender
= dependencies
.getMessageSender();
361 final var results
= messageSender
.sendDataMessage(addresses
,
362 unidentifiedAccesses
,
366 SignalServiceMessageSender
.LegacyGroupEvents
.EMPTY
,
367 sendResult
-> logger
.trace("Partial message send result: {}", sendResult
.isSuccess()),
370 final var successCount
= results
.stream().filter(SendMessageResult
::isSuccess
).count();
371 logger
.debug("Successfully sent using 1:1 to {}/{} legacy targets.", successCount
, recipientIdList
.size());
373 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
378 private List
<SendMessageResult
> sendGroupMessageInternalWithSenderKey(
379 final SignalServiceDataMessage message
,
380 final Set
<RecipientId
> recipientIds
,
381 final DistributionId distributionId
,
382 final boolean isRecipientUpdate
383 ) throws IOException
{
384 final var recipientIdList
= new ArrayList
<>(recipientIds
);
385 final var messageSender
= dependencies
.getMessageSender();
387 long keyCreateTime
= account
.getSenderKeyStore()
388 .getCreateTimeForOurKey(account
.getSelfRecipientId(), account
.getDeviceId(), distributionId
);
389 long keyAge
= System
.currentTimeMillis() - keyCreateTime
;
391 if (keyCreateTime
!= -1 && keyAge
> TimeUnit
.DAYS
.toMillis(14)) {
392 logger
.debug("DistributionId {} was created at {} and is {} ms old (~{} days). Rotating.",
396 TimeUnit
.MILLISECONDS
.toDays(keyAge
));
397 account
.getSenderKeyStore().deleteOurKey(account
.getSelfRecipientId(), distributionId
);
400 List
<SignalServiceAddress
> addresses
= recipientIdList
.stream()
401 .map(addressResolver
::resolveSignalServiceAddress
)
402 .collect(Collectors
.toList());
403 List
<UnidentifiedAccess
> unidentifiedAccesses
= recipientIdList
.stream()
404 .map(unidentifiedAccessHelper
::getAccessFor
)
406 .map(UnidentifiedAccessPair
::getTargetUnidentifiedAccess
)
408 .collect(Collectors
.toList());
411 List
<SendMessageResult
> results
= messageSender
.sendGroupDataMessage(distributionId
,
413 unidentifiedAccesses
,
417 SignalServiceMessageSender
.SenderKeyGroupEvents
.EMPTY
);
419 final var successCount
= results
.stream().filter(SendMessageResult
::isSuccess
).count();
420 logger
.debug("Successfully sent using sender key to {}/{} sender key targets.",
425 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
427 } catch (InvalidUnidentifiedAccessHeaderException e
) {
428 logger
.warn("Someone had a bad UD header. Falling back to legacy sends.", e
);
430 } catch (NoSessionException e
) {
431 logger
.warn("No session. Falling back to legacy sends.", e
);
432 account
.getSenderKeyStore().deleteOurKey(account
.getSelfRecipientId(), distributionId
);
434 } catch (InvalidKeyException e
) {
435 logger
.warn("Invalid key. Falling back to legacy sends.", e
);
436 account
.getSenderKeyStore().deleteOurKey(account
.getSelfRecipientId(), distributionId
);
438 } catch (InvalidRegistrationIdException e
) {
439 logger
.warn("Invalid registrationId. Falling back to legacy sends.", e
);
441 } catch (NotFoundException e
) {
442 logger
.warn("Someone was unregistered. Falling back to legacy sends.", e
);
447 private SendMessageResult
sendMessage(
448 SignalServiceDataMessage message
, RecipientId recipientId
450 return handleSendMessage(recipientId
,
451 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendDataMessage(address
,
455 SignalServiceMessageSender
.IndividualSendEvents
.EMPTY
));
458 private SendMessageResult
handleSendMessage(RecipientId recipientId
, SenderHandler s
) {
459 var messageSender
= dependencies
.getMessageSender();
461 var address
= addressResolver
.resolveSignalServiceAddress(recipientId
);
464 return s
.send(messageSender
, address
, unidentifiedAccessHelper
.getAccessFor(recipientId
));
465 } catch (UnregisteredUserException e
) {
466 final var newRecipientId
= recipientRegistrationRefresher
.refreshRecipientRegistration(recipientId
);
467 address
= addressResolver
.resolveSignalServiceAddress(newRecipientId
);
468 return s
.send(messageSender
, address
, unidentifiedAccessHelper
.getAccessFor(newRecipientId
));
470 } catch (UnregisteredUserException e
) {
471 return SendMessageResult
.unregisteredFailure(address
);
472 } catch (ProofRequiredException e
) {
473 return SendMessageResult
.proofRequiredFailure(address
, e
);
474 } catch (RateLimitException e
) {
475 logger
.warn("Sending failed due to rate limiting from the signal server: {}", e
.getMessage());
476 return SendMessageResult
.networkFailure(address
);
477 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
478 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
479 } catch (IOException e
) {
480 logger
.warn("Failed to send message due to IO exception: {}", e
.getMessage());
481 return SendMessageResult
.networkFailure(address
);
485 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) {
486 var address
= account
.getSelfAddress();
487 var transcript
= new SentTranscriptMessage(Optional
.of(address
),
488 message
.getTimestamp(),
490 message
.getExpiresInSeconds(),
491 Map
.of(address
, true),
493 var syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
495 return sendSyncMessage(syncMessage
);
498 private void handleSendMessageResult(final SendMessageResult r
) {
499 if (r
.getIdentityFailure() != null) {
500 final var recipientId
= recipientResolver
.resolveRecipient(r
.getAddress());
501 identityFailureHandler
.handleIdentityFailure(recipientId
, r
.getIdentityFailure());
505 interface SenderHandler
{
507 SendMessageResult
send(
508 SignalServiceMessageSender messageSender
,
509 SignalServiceAddress address
,
510 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
511 ) throws IOException
, UnregisteredUserException
, ProofRequiredException
, RateLimitException
, org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;