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