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