]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java
Update dependencies
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / helper / SendHelper.java
1 package org.asamk.signal.manager.helper;
2
3 import org.asamk.signal.manager.api.Contact;
4 import org.asamk.signal.manager.api.GroupId;
5 import org.asamk.signal.manager.api.GroupNotFoundException;
6 import org.asamk.signal.manager.api.GroupSendingNotAllowedException;
7 import org.asamk.signal.manager.api.NotAGroupMemberException;
8 import org.asamk.signal.manager.api.Profile;
9 import org.asamk.signal.manager.api.UnregisteredRecipientException;
10 import org.asamk.signal.manager.groups.GroupUtils;
11 import org.asamk.signal.manager.internal.SignalDependencies;
12 import org.asamk.signal.manager.storage.SignalAccount;
13 import org.asamk.signal.manager.storage.groups.GroupInfo;
14 import org.asamk.signal.manager.storage.recipients.RecipientId;
15 import org.asamk.signal.manager.storage.sendLog.MessageSendLogEntry;
16 import org.signal.libsignal.protocol.InvalidKeyException;
17 import org.signal.libsignal.protocol.InvalidRegistrationIdException;
18 import org.signal.libsignal.protocol.NoSessionException;
19 import org.signal.libsignal.protocol.SignalProtocolAddress;
20 import org.signal.libsignal.protocol.message.DecryptionErrorMessage;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
23 import org.whispersystems.signalservice.api.SignalServiceMessageSender;
24 import org.whispersystems.signalservice.api.crypto.ContentHint;
25 import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
26 import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
27 import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
28 import org.whispersystems.signalservice.api.messages.SendMessageResult;
29 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
30 import org.whispersystems.signalservice.api.messages.SignalServiceEditMessage;
31 import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
32 import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
33 import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
34 import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
35 import org.whispersystems.signalservice.api.push.DistributionId;
36 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
37 import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
38 import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException;
39 import org.whispersystems.signalservice.api.push.exceptions.RateLimitException;
40 import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
41 import org.whispersystems.signalservice.internal.push.exceptions.InvalidUnidentifiedAccessHeaderException;
42 import org.whispersystems.signalservice.internal.push.http.PartialSendCompleteListener;
43
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;
49 import java.util.Map;
50 import java.util.Optional;
51 import java.util.Set;
52 import java.util.concurrent.TimeUnit;
53 import java.util.concurrent.atomic.AtomicLong;
54
55 import okio.ByteString;
56
57 public class SendHelper {
58
59 private static final Logger logger = LoggerFactory.getLogger(SendHelper.class);
60
61 private final SignalAccount account;
62 private final SignalDependencies dependencies;
63 private final Context context;
64
65 public SendHelper(final Context context) {
66 this.account = context.getAccount();
67 this.dependencies = context.getDependencies();
68 this.context = context;
69 }
70
71 /**
72 * Send a single message to one recipient.
73 * The message is extended with the current expiration timer.
74 */
75 public SendMessageResult sendMessage(
76 final SignalServiceDataMessage.Builder messageBuilder,
77 final RecipientId recipientId,
78 Optional<Long> editTargetTimestamp
79 ) {
80 var contact = account.getContactStore().getContact(recipientId);
81 if (contact == null || !contact.isProfileSharingEnabled() || contact.isHidden()) {
82 final var contactBuilder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
83 contact = contactBuilder.withIsProfileSharingEnabled(true).withIsHidden(false).build();
84 account.getContactStore().storeContact(recipientId, contact);
85 }
86
87 final var expirationTime = contact.messageExpirationTime();
88 messageBuilder.withExpiration(expirationTime);
89
90 if (!contact.isBlocked()) {
91 final var profileKey = account.getProfileKey().serialize();
92 messageBuilder.withProfileKey(profileKey);
93 }
94
95 final var message = messageBuilder.build();
96 return sendMessage(message, recipientId, editTargetTimestamp);
97 }
98
99 /**
100 * Send a group message to the given group
101 * The message is extended with the current expiration timer for the group and the group context.
102 */
103 public List<SendMessageResult> sendAsGroupMessage(
104 final SignalServiceDataMessage.Builder messageBuilder,
105 final GroupId groupId,
106 final boolean includeSelf,
107 final Optional<Long> editTargetTimestamp
108 ) throws IOException, GroupNotFoundException, NotAGroupMemberException, GroupSendingNotAllowedException {
109 final var g = getGroupForSending(groupId);
110 return sendAsGroupMessage(messageBuilder, g, includeSelf, editTargetTimestamp);
111 }
112
113 /**
114 * Send a complete group message to the given recipients (should be current/old/new members)
115 * This method should only be used for create/update/quit group messages.
116 */
117 public List<SendMessageResult> sendGroupMessage(
118 final SignalServiceDataMessage message,
119 final Set<RecipientId> recipientIds,
120 final DistributionId distributionId
121 ) throws IOException {
122 return sendGroupMessage(message, recipientIds, distributionId, ContentHint.IMPLICIT, Optional.empty());
123 }
124
125 public SendMessageResult sendReceiptMessage(
126 final SignalServiceReceiptMessage receiptMessage, final RecipientId recipientId
127 ) {
128 final var messageSendLogStore = account.getMessageSendLogStore();
129 final var result = handleSendMessage(recipientId,
130 (messageSender, address, unidentifiedAccess) -> messageSender.sendReceipt(address,
131 unidentifiedAccess,
132 receiptMessage,
133 false));
134 messageSendLogStore.insertIfPossible(receiptMessage.getWhen(), result, ContentHint.IMPLICIT, false);
135 handleSendMessageResult(result);
136 return result;
137 }
138
139 public SendMessageResult sendProfileKey(RecipientId recipientId) {
140 logger.debug("Sending updated profile key to recipient: {}", recipientId);
141 final var profileKey = account.getProfileKey().serialize();
142 final var message = SignalServiceDataMessage.newBuilder()
143 .asProfileKeyUpdate(true)
144 .withProfileKey(profileKey)
145 .build();
146 return handleSendMessage(recipientId,
147 (messageSender, address, unidentifiedAccess) -> messageSender.sendDataMessage(address,
148 unidentifiedAccess,
149 ContentHint.IMPLICIT,
150 message,
151 SignalServiceMessageSender.IndividualSendEvents.EMPTY,
152 false,
153 false));
154 }
155
156 public SendMessageResult sendRetryReceipt(
157 DecryptionErrorMessage errorMessage, RecipientId recipientId, Optional<GroupId> groupId
158 ) {
159 logger.debug("Sending retry receipt for {} to {}, device: {}",
160 errorMessage.getTimestamp(),
161 recipientId,
162 errorMessage.getDeviceId());
163 final var result = handleSendMessage(recipientId,
164 (messageSender, address, unidentifiedAccess) -> messageSender.sendRetryReceipt(address,
165 unidentifiedAccess,
166 groupId.map(GroupId::serialize),
167 errorMessage));
168 handleSendMessageResult(result);
169 return result;
170 }
171
172 public SendMessageResult sendNullMessage(RecipientId recipientId) {
173 final var result = handleSendMessage(recipientId, SignalServiceMessageSender::sendNullMessage);
174 handleSendMessageResult(result);
175 return result;
176 }
177
178 public SendMessageResult sendSelfMessage(
179 SignalServiceDataMessage.Builder messageBuilder, Optional<Long> editTargetTimestamp
180 ) {
181 final var recipientId = account.getSelfRecipientId();
182 final var contact = account.getContactStore().getContact(recipientId);
183 final var expirationTime = contact != null ? contact.messageExpirationTime() : 0;
184 messageBuilder.withExpiration(expirationTime);
185
186 var message = messageBuilder.build();
187 return sendSelfMessage(message, editTargetTimestamp);
188 }
189
190 public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message) {
191 var messageSender = dependencies.getMessageSender();
192 if (!account.isMultiDevice()) {
193 logger.trace("Not sending sync message because there are no linked devices.");
194 return SendMessageResult.success(account.getSelfAddress(), List.of(), false, false, 0, Optional.empty());
195 }
196 try {
197 return messageSender.sendSyncMessage(message, context.getUnidentifiedAccessHelper().getAccessForSync());
198 } catch (UnregisteredUserException e) {
199 var address = context.getRecipientHelper().resolveSignalServiceAddress(account.getSelfRecipientId());
200 return SendMessageResult.unregisteredFailure(address);
201 } catch (ProofRequiredException e) {
202 var address = context.getRecipientHelper().resolveSignalServiceAddress(account.getSelfRecipientId());
203 return SendMessageResult.proofRequiredFailure(address, e);
204 } catch (RateLimitException e) {
205 var address = context.getRecipientHelper().resolveSignalServiceAddress(account.getSelfRecipientId());
206 logger.warn("Sending failed due to rate limiting from the signal server: {}", e.getMessage());
207 return SendMessageResult.rateLimitFailure(address, e);
208 } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
209 var address = context.getRecipientHelper().resolveSignalServiceAddress(account.getSelfRecipientId());
210 return SendMessageResult.identityFailure(address, e.getIdentityKey());
211 } catch (IOException e) {
212 var address = context.getRecipientHelper().resolveSignalServiceAddress(account.getSelfRecipientId());
213 logger.warn("Failed to send message due to IO exception: {}", e.getMessage());
214 logger.debug("Exception", e);
215 return SendMessageResult.networkFailure(address);
216 }
217 }
218
219 public SendMessageResult sendTypingMessage(
220 SignalServiceTypingMessage message, RecipientId recipientId
221 ) {
222 final var result = handleSendMessage(recipientId,
223 (messageSender, address, unidentifiedAccess) -> messageSender.sendTyping(List.of(address),
224 List.of(unidentifiedAccess),
225 message,
226 null).getFirst());
227 handleSendMessageResult(result);
228 return result;
229 }
230
231 public List<SendMessageResult> sendGroupTypingMessage(
232 SignalServiceTypingMessage message, GroupId groupId
233 ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
234 final var g = getGroupForSending(groupId);
235 if (g.isAnnouncementGroup() && !g.isAdmin(account.getSelfRecipientId())) {
236 throw new GroupSendingNotAllowedException(groupId, g.getTitle());
237 }
238 final var distributionId = g.getDistributionId();
239 final var recipientIds = g.getMembersWithout(account.getSelfRecipientId());
240
241 return sendGroupTypingMessage(message, recipientIds, distributionId);
242 }
243
244 public SendMessageResult resendMessage(
245 final RecipientId recipientId, final long timestamp, final MessageSendLogEntry messageSendLogEntry
246 ) {
247 logger.trace("Resending message {} to {}", timestamp, recipientId);
248 if (messageSendLogEntry.groupId().isEmpty()) {
249 return handleSendMessage(recipientId,
250 (messageSender, address, unidentifiedAccess) -> messageSender.resendContent(address,
251 unidentifiedAccess,
252 timestamp,
253 messageSendLogEntry.content(),
254 messageSendLogEntry.contentHint(),
255 Optional.empty(),
256 messageSendLogEntry.urgent()));
257 }
258
259 final var groupId = messageSendLogEntry.groupId().get();
260 final var group = account.getGroupStore().getGroup(groupId);
261
262 if (group == null) {
263 logger.debug("Could not find a matching group for the groupId {}! Skipping message send.",
264 groupId.toBase64());
265 return null;
266 } else if (!group.getMembers().contains(recipientId)) {
267 logger.warn("The target user is no longer in the group {}! Skipping message send.", groupId.toBase64());
268 return null;
269 }
270
271 final var senderKeyDistributionMessage = dependencies.getMessageSender()
272 .getOrCreateNewGroupSession(group.getDistributionId());
273 final var distributionBytes = ByteString.of(senderKeyDistributionMessage.serialize());
274 final var contentToSend = messageSendLogEntry.content()
275 .newBuilder()
276 .senderKeyDistributionMessage(distributionBytes)
277 .build();
278
279 final var result = handleSendMessage(recipientId,
280 (messageSender, address, unidentifiedAccess) -> messageSender.resendContent(address,
281 unidentifiedAccess,
282 timestamp,
283 contentToSend,
284 messageSendLogEntry.contentHint(),
285 Optional.of(group.getGroupId().serialize()),
286 messageSendLogEntry.urgent()));
287
288 if (result.isSuccess()) {
289 final var address = context.getRecipientHelper().resolveSignalServiceAddress(recipientId);
290 final var addresses = result.getSuccess()
291 .getDevices()
292 .stream()
293 .map(device -> new SignalProtocolAddress(address.getIdentifier(), device))
294 .toList();
295
296 account.getSenderKeyStore().markSenderKeySharedWith(group.getDistributionId(), addresses);
297 }
298
299 return result;
300 }
301
302 private List<SendMessageResult> sendAsGroupMessage(
303 final SignalServiceDataMessage.Builder messageBuilder,
304 final GroupInfo g,
305 final boolean includeSelf,
306 final Optional<Long> editTargetTimestamp
307 ) throws IOException, GroupSendingNotAllowedException {
308 GroupUtils.setGroupContext(messageBuilder, g);
309 messageBuilder.withExpiration(g.getMessageExpirationTimer());
310
311 final var message = messageBuilder.build();
312 final var recipients = includeSelf ? g.getMembers() : g.getMembersWithout(account.getSelfRecipientId());
313
314 if (g.isAnnouncementGroup() && !g.isAdmin(account.getSelfRecipientId())) {
315 if (message.getBody().isPresent()
316 || message.getAttachments().isPresent()
317 || message.getQuote().isPresent()
318 || message.getPreviews().isPresent()
319 || message.getMentions().isPresent()
320 || message.getSticker().isPresent()) {
321 throw new GroupSendingNotAllowedException(g.getGroupId(), g.getTitle());
322 }
323 }
324
325 return sendGroupMessage(message,
326 recipients,
327 g.getDistributionId(),
328 ContentHint.RESENDABLE,
329 editTargetTimestamp);
330 }
331
332 private List<SendMessageResult> sendGroupMessage(
333 final SignalServiceDataMessage message,
334 final Set<RecipientId> recipientIds,
335 final DistributionId distributionId,
336 final ContentHint contentHint,
337 final Optional<Long> editTargetTimestamp
338 ) throws IOException {
339 final var messageSender = dependencies.getMessageSender();
340 final var messageSendLogStore = account.getMessageSendLogStore();
341 final AtomicLong entryId = new AtomicLong(-1);
342
343 final var urgent = true;
344 final PartialSendCompleteListener partialSendCompleteListener = sendResult -> {
345 logger.trace("Partial message send result: {}", sendResult.isSuccess());
346 synchronized (entryId) {
347 if (entryId.get() == -1) {
348 final var newId = messageSendLogStore.insertIfPossible(message.getTimestamp(),
349 sendResult,
350 contentHint,
351 urgent);
352 entryId.set(newId);
353 } else {
354 messageSendLogStore.addRecipientToExistingEntryIfPossible(entryId.get(), sendResult);
355 }
356 }
357 };
358 final LegacySenderHandler legacySender = (recipients, unidentifiedAccess, isRecipientUpdate) ->
359 editTargetTimestamp.isEmpty()
360 ? messageSender.sendDataMessage(recipients,
361 unidentifiedAccess,
362 isRecipientUpdate,
363 contentHint,
364 message,
365 SignalServiceMessageSender.LegacyGroupEvents.EMPTY,
366 partialSendCompleteListener,
367 () -> false,
368 urgent)
369 : messageSender.sendEditMessage(recipients,
370 unidentifiedAccess,
371 isRecipientUpdate,
372 contentHint,
373 message,
374 SignalServiceMessageSender.LegacyGroupEvents.EMPTY,
375 partialSendCompleteListener,
376 () -> false,
377 urgent,
378 editTargetTimestamp.get());
379 final SenderKeySenderHandler senderKeySender = (distId, recipients, unidentifiedAccess, isRecipientUpdate) -> messageSender.sendGroupDataMessage(
380 distId,
381 recipients,
382 unidentifiedAccess,
383 isRecipientUpdate,
384 contentHint,
385 message,
386 SignalServiceMessageSender.SenderKeyGroupEvents.EMPTY,
387 urgent,
388 false,
389 editTargetTimestamp.map(timestamp -> new SignalServiceEditMessage(timestamp, message)).orElse(null),
390 sendResult -> {
391 logger.trace("Partial message send results: {}", sendResult.size());
392 synchronized (entryId) {
393 if (entryId.get() == -1) {
394 final var newId = messageSendLogStore.insertIfPossible(message.getTimestamp(),
395 sendResult,
396 contentHint,
397 urgent);
398 entryId.set(newId);
399 } else {
400 messageSendLogStore.addRecipientToExistingEntryIfPossible(entryId.get(), sendResult);
401 }
402 }
403 synchronized (entryId) {
404 if (entryId.get() == -1) {
405 final var newId = messageSendLogStore.insertIfPossible(message.getTimestamp(),
406 sendResult,
407 contentHint,
408 urgent);
409 entryId.set(newId);
410 } else {
411 messageSendLogStore.addRecipientToExistingEntryIfPossible(entryId.get(), sendResult);
412 }
413 }
414 });
415 final var results = sendGroupMessageInternal(legacySender, senderKeySender, recipientIds, distributionId);
416
417 for (var r : results) {
418 handleSendMessageResult(r);
419 }
420
421 return results;
422 }
423
424 private List<SendMessageResult> sendGroupTypingMessage(
425 final SignalServiceTypingMessage message,
426 final Set<RecipientId> recipientIds,
427 final DistributionId distributionId
428 ) throws IOException {
429 final var messageSender = dependencies.getMessageSender();
430 final var results = sendGroupMessageInternal((recipients, unidentifiedAccess, isRecipientUpdate) -> messageSender.sendTyping(
431 recipients,
432 unidentifiedAccess,
433 message,
434 () -> false),
435 (distId, recipients, unidentifiedAccess, isRecipientUpdate) -> messageSender.sendGroupTyping(distId,
436 recipients,
437 unidentifiedAccess,
438 message),
439 recipientIds,
440 distributionId);
441
442 for (var r : results) {
443 handleSendMessageResult(r);
444 }
445
446 return results;
447 }
448
449 private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
450 var g = context.getGroupHelper().getGroup(groupId);
451 if (g == null) {
452 throw new GroupNotFoundException(groupId);
453 }
454 if (!g.isMember(account.getSelfRecipientId())) {
455 throw new NotAGroupMemberException(groupId, g.getTitle());
456 }
457 if (!g.isProfileSharingEnabled()) {
458 g.setProfileSharingEnabled(true);
459 account.getGroupStore().updateGroup(g);
460 }
461 return g;
462 }
463
464 private List<SendMessageResult> sendGroupMessageInternal(
465 final LegacySenderHandler legacySender,
466 final SenderKeySenderHandler senderKeySender,
467 final Set<RecipientId> recipientIds,
468 final DistributionId distributionId
469 ) throws IOException {
470 long startTime = System.currentTimeMillis();
471 // isRecipientUpdate is true if we've already sent this message to some recipients in the past, otherwise false.
472 final var isRecipientUpdate = false;
473 Set<RecipientId> senderKeyTargets = distributionId == null
474 ? Set.of()
475 : getSenderKeyCapableRecipientIds(recipientIds);
476 final var allResults = new ArrayList<SendMessageResult>(recipientIds.size());
477
478 if (!senderKeyTargets.isEmpty()) {
479 final var results = sendGroupMessageInternalWithSenderKey(senderKeySender,
480 senderKeyTargets,
481 distributionId,
482 isRecipientUpdate);
483
484 if (results == null) {
485 senderKeyTargets = Set.of();
486 } else {
487 results.stream().filter(SendMessageResult::isSuccess).forEach(allResults::add);
488 final var recipientResolver = account.getRecipientResolver();
489 final var failedTargets = results.stream()
490 .filter(r -> !r.isSuccess())
491 .map(r -> recipientResolver.resolveRecipient(r.getAddress()))
492 .toList();
493 if (!failedTargets.isEmpty()) {
494 senderKeyTargets = new HashSet<>(senderKeyTargets);
495 failedTargets.forEach(senderKeyTargets::remove);
496 }
497 }
498 }
499
500 final var legacyTargets = new HashSet<>(recipientIds);
501 legacyTargets.removeAll(senderKeyTargets);
502 final boolean onlyTargetIsSelfWithLinkedDevice = recipientIds.isEmpty() && account.isMultiDevice();
503
504 if (!legacyTargets.isEmpty() || onlyTargetIsSelfWithLinkedDevice) {
505 if (!legacyTargets.isEmpty()) {
506 logger.debug("Need to do {} legacy sends.", legacyTargets.size());
507 } else {
508 logger.debug("Need to do a legacy send to send a sync message for a group of only ourselves.");
509 }
510
511 final List<SendMessageResult> results = sendGroupMessageInternalWithLegacy(legacySender,
512 legacyTargets,
513 isRecipientUpdate || !allResults.isEmpty());
514 allResults.addAll(results);
515 }
516 final var duration = Duration.ofMillis(System.currentTimeMillis() - startTime);
517 logger.debug("Sending took {}", duration.toString());
518 return allResults;
519 }
520
521 private Set<RecipientId> getSenderKeyCapableRecipientIds(final Set<RecipientId> recipientIds) {
522 final var selfProfile = context.getProfileHelper().getSelfProfile();
523 if (selfProfile == null || !selfProfile.getCapabilities().contains(Profile.Capability.senderKey)) {
524 logger.debug("Not all of our devices support sender key. Using legacy.");
525 return Set.of();
526 }
527
528 final var senderKeyTargets = new HashSet<RecipientId>();
529 final var recipientList = new ArrayList<>(recipientIds);
530 final var profiles = context.getProfileHelper().getRecipientProfiles(recipientList).iterator();
531 for (final var recipientId : recipientList) {
532 final var profile = profiles.next();
533 if (profile == null || !profile.getCapabilities().contains(Profile.Capability.senderKey)) {
534 continue;
535 }
536
537 final var access = context.getUnidentifiedAccessHelper().getAccessFor(recipientId);
538 if (access.isEmpty() || access.get().getTargetUnidentifiedAccess().isEmpty()) {
539 continue;
540 }
541
542 final var serviceId = account.getRecipientAddressResolver()
543 .resolveRecipientAddress(recipientId)
544 .serviceId()
545 .orElse(null);
546 if (serviceId == null) {
547 continue;
548 }
549 final var identity = account.getIdentityKeyStore().getIdentityInfo(serviceId);
550 if (identity == null || !identity.getTrustLevel().isTrusted()) {
551 continue;
552 }
553
554 senderKeyTargets.add(recipientId);
555 }
556
557 if (senderKeyTargets.size() < 2) {
558 logger.debug("Too few sender-key-capable users ({}). Doing all legacy sends.", senderKeyTargets.size());
559 return Set.of();
560 }
561
562 logger.debug("Can use sender key for {}/{} recipients.", senderKeyTargets.size(), recipientIds.size());
563 return senderKeyTargets;
564 }
565
566 private List<SendMessageResult> sendGroupMessageInternalWithLegacy(
567 final LegacySenderHandler sender, final Set<RecipientId> recipientIds, final boolean isRecipientUpdate
568 ) throws IOException {
569 final var recipientIdList = new ArrayList<>(recipientIds);
570 final var addresses = recipientIdList.stream()
571 .map(context.getRecipientHelper()::resolveSignalServiceAddress)
572 .toList();
573 final var unidentifiedAccesses = context.getUnidentifiedAccessHelper().getAccessFor(recipientIdList);
574 try {
575 final var results = sender.send(addresses, unidentifiedAccesses, isRecipientUpdate);
576
577 final var successCount = results.stream().filter(SendMessageResult::isSuccess).count();
578 logger.debug("Successfully sent using 1:1 to {}/{} legacy targets.", successCount, recipientIdList.size());
579 return results;
580 } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
581 return List.of();
582 }
583 }
584
585 private List<SendMessageResult> sendGroupMessageInternalWithSenderKey(
586 final SenderKeySenderHandler sender,
587 final Set<RecipientId> recipientIds,
588 final DistributionId distributionId,
589 final boolean isRecipientUpdate
590 ) throws IOException {
591 final var recipientIdList = new ArrayList<>(recipientIds);
592
593 long keyCreateTime = account.getSenderKeyStore()
594 .getCreateTimeForOurKey(account.getAci(), account.getDeviceId(), distributionId);
595 long keyAge = System.currentTimeMillis() - keyCreateTime;
596
597 if (keyCreateTime != -1 && keyAge > TimeUnit.DAYS.toMillis(14)) {
598 logger.debug("DistributionId {} was created at {} and is {} ms old (~{} days). Rotating.",
599 distributionId,
600 keyCreateTime,
601 keyAge,
602 TimeUnit.MILLISECONDS.toDays(keyAge));
603 account.getSenderKeyStore().deleteOurKey(account.getAci(), distributionId);
604 }
605
606 List<SignalServiceAddress> addresses = recipientIdList.stream()
607 .map(context.getRecipientHelper()::resolveSignalServiceAddress)
608 .toList();
609 List<UnidentifiedAccess> unidentifiedAccesses = context.getUnidentifiedAccessHelper()
610 .getAccessFor(recipientIdList)
611 .stream()
612 .map(Optional::get)
613 .map(UnidentifiedAccessPair::getTargetUnidentifiedAccess)
614 .map(Optional::get)
615 .toList();
616
617 try {
618 List<SendMessageResult> results = sender.send(distributionId,
619 addresses,
620 unidentifiedAccesses,
621 isRecipientUpdate);
622
623 final var successCount = results.stream().filter(SendMessageResult::isSuccess).count();
624 logger.debug("Successfully sent using sender key to {}/{} sender key targets.",
625 successCount,
626 addresses.size());
627
628 return results;
629 } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
630 return null;
631 } catch (InvalidUnidentifiedAccessHeaderException e) {
632 logger.warn("Someone had a bad UD header. Falling back to legacy sends.", e);
633 return null;
634 } catch (NoSessionException e) {
635 logger.warn("No session. Falling back to legacy sends.", e);
636 account.getSenderKeyStore().deleteOurKey(account.getAci(), distributionId);
637 return null;
638 } catch (InvalidKeyException e) {
639 logger.warn("Invalid key. Falling back to legacy sends.", e);
640 account.getSenderKeyStore().deleteOurKey(account.getAci(), distributionId);
641 return null;
642 } catch (InvalidRegistrationIdException e) {
643 logger.warn("Invalid registrationId. Falling back to legacy sends.", e);
644 return null;
645 } catch (NotFoundException e) {
646 logger.warn("Someone was unregistered. Falling back to legacy sends.", e);
647 return null;
648 } catch (IOException e) {
649 if (e.getCause() instanceof InvalidKeyException) {
650 logger.warn("Invalid key. Falling back to legacy sends.", e);
651 return null;
652 } else {
653 throw e;
654 }
655 }
656 }
657
658 private SendMessageResult sendMessage(
659 SignalServiceDataMessage message, RecipientId recipientId, Optional<Long> editTargetTimestamp
660 ) {
661 final var messageSendLogStore = account.getMessageSendLogStore();
662 final var urgent = true;
663 final var includePniSignature = false;
664 final var result = handleSendMessage(recipientId,
665 editTargetTimestamp.isEmpty()
666 ? (messageSender, address, unidentifiedAccess) -> messageSender.sendDataMessage(address,
667 unidentifiedAccess,
668 ContentHint.RESENDABLE,
669 message,
670 SignalServiceMessageSender.IndividualSendEvents.EMPTY,
671 urgent,
672 includePniSignature)
673 : (messageSender, address, unidentifiedAccess) -> messageSender.sendEditMessage(address,
674 unidentifiedAccess,
675 ContentHint.RESENDABLE,
676 message,
677 SignalServiceMessageSender.IndividualSendEvents.EMPTY,
678 urgent,
679 editTargetTimestamp.get()));
680 messageSendLogStore.insertIfPossible(message.getTimestamp(), result, ContentHint.RESENDABLE, urgent);
681 handleSendMessageResult(result);
682 return result;
683 }
684
685 private SendMessageResult handleSendMessage(RecipientId recipientId, SenderHandler s) {
686 var messageSender = dependencies.getMessageSender();
687
688 var address = context.getRecipientHelper().resolveSignalServiceAddress(recipientId);
689 try {
690 try {
691 return s.send(messageSender, address, context.getUnidentifiedAccessHelper().getAccessFor(recipientId));
692 } catch (UnregisteredUserException e) {
693 final RecipientId newRecipientId;
694 try {
695 newRecipientId = context.getRecipientHelper().refreshRegisteredUser(recipientId);
696 } catch (UnregisteredRecipientException ex) {
697 return SendMessageResult.unregisteredFailure(address);
698 }
699 address = context.getRecipientHelper().resolveSignalServiceAddress(newRecipientId);
700 return s.send(messageSender,
701 address,
702 context.getUnidentifiedAccessHelper().getAccessFor(newRecipientId));
703 }
704 } catch (UnregisteredUserException e) {
705 return SendMessageResult.unregisteredFailure(address);
706 } catch (ProofRequiredException e) {
707 return SendMessageResult.proofRequiredFailure(address, e);
708 } catch (RateLimitException e) {
709 logger.warn("Sending failed due to rate limiting from the signal server: {}", e.getMessage());
710 return SendMessageResult.rateLimitFailure(address, e);
711 } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
712 return SendMessageResult.identityFailure(address, e.getIdentityKey());
713 } catch (IOException e) {
714 logger.warn("Failed to send message due to IO exception: {}", e.getMessage());
715 logger.debug("Exception", e);
716 return SendMessageResult.networkFailure(address);
717 }
718 }
719
720 private SendMessageResult sendSelfMessage(SignalServiceDataMessage message, Optional<Long> editTargetTimestamp) {
721 var address = account.getSelfAddress();
722 var transcript = new SentTranscriptMessage(Optional.of(address),
723 message.getTimestamp(),
724 editTargetTimestamp.isEmpty() ? Optional.of(message) : Optional.empty(),
725 message.getExpiresInSeconds(),
726 Map.of(address.getServiceId(), true),
727 false,
728 Optional.empty(),
729 Set.of(),
730 editTargetTimestamp.map((timestamp) -> new SignalServiceEditMessage(timestamp, message)));
731 var syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript);
732
733 return sendSyncMessage(syncMessage);
734 }
735
736 private void handleSendMessageResult(final SendMessageResult r) {
737 if (r.isSuccess() && !r.getSuccess().isUnidentified()) {
738 final var recipientId = account.getRecipientResolver().resolveRecipient(r.getAddress());
739 final var profile = account.getProfileStore().getProfile(recipientId);
740 if (profile != null && (
741 profile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.ENABLED
742 || profile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED
743 )) {
744 account.getProfileStore()
745 .storeProfile(recipientId,
746 Profile.newBuilder(profile)
747 .withUnidentifiedAccessMode(Profile.UnidentifiedAccessMode.UNKNOWN)
748 .build());
749 }
750 }
751 if (r.isUnregisteredFailure()) {
752 final var recipientId = account.getRecipientResolver().resolveRecipient(r.getAddress());
753 final var profile = account.getProfileStore().getProfile(recipientId);
754 if (profile != null && (
755 profile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.ENABLED
756 || profile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED
757 )) {
758 account.getProfileStore()
759 .storeProfile(recipientId,
760 Profile.newBuilder(profile)
761 .withUnidentifiedAccessMode(Profile.UnidentifiedAccessMode.UNKNOWN)
762 .build());
763 }
764 }
765 if (r.getIdentityFailure() != null) {
766 final var recipientId = account.getRecipientResolver().resolveRecipient(r.getAddress());
767 context.getIdentityHelper()
768 .handleIdentityFailure(recipientId, r.getAddress().getServiceId(), r.getIdentityFailure());
769 }
770 }
771
772 interface SenderHandler {
773
774 SendMessageResult send(
775 SignalServiceMessageSender messageSender,
776 SignalServiceAddress address,
777 Optional<UnidentifiedAccessPair> unidentifiedAccess
778 ) throws IOException, UnregisteredUserException, ProofRequiredException, RateLimitException, org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
779 }
780
781 interface SenderKeySenderHandler {
782
783 List<SendMessageResult> send(
784 DistributionId distributionId,
785 List<SignalServiceAddress> recipients,
786 List<UnidentifiedAccess> unidentifiedAccess,
787 boolean isRecipientUpdate
788 ) throws IOException, UntrustedIdentityException, NoSessionException, InvalidKeyException, InvalidRegistrationIdException;
789 }
790
791 interface LegacySenderHandler {
792
793 List<SendMessageResult> send(
794 List<SignalServiceAddress> recipients,
795 List<Optional<UnidentifiedAccessPair>> unidentifiedAccess,
796 boolean isRecipientUpdate
797 ) throws IOException, UntrustedIdentityException;
798 }
799 }