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