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