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