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