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