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