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