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