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