1 package org
.asamk
.signal
.manager
.helper
;
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
;
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
;
47 import java
.util
.concurrent
.TimeUnit
;
48 import java
.util
.stream
.Collectors
;
50 public class SendHelper
{
52 private final static Logger logger
= LoggerFactory
.getLogger(SendHelper
.class);
54 private final SignalAccount account
;
55 private final SignalDependencies dependencies
;
56 private final Context context
;
58 public SendHelper(final Context context
) {
59 this.account
= context
.getAccount();
60 this.dependencies
= context
.getDependencies();
61 this.context
= context
;
65 * Send a single message to one recipient.
66 * The message is extended with the current expiration timer.
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());
76 final var message
= messageBuilder
.build();
77 final var result
= sendMessage(message
, recipientId
);
78 handleSendMessageResult(result
);
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.
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
);
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());
99 final var message
= messageBuilder
.build();
100 final var recipients
= g
.getMembersWithout(account
.getSelfRecipientId());
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());
113 return sendGroupMessage(message
, recipients
, g
.getDistributionId());
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.
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(
132 SignalServiceMessageSender
.LegacyGroupEvents
.EMPTY
,
133 sendResult
-> logger
.trace("Partial message send result: {}", sendResult
.isSuccess()),
135 (distId
, recipients
, unidentifiedAccess
, isRecipientUpdate
) -> messageSender
.sendGroupDataMessage(distId
,
141 SignalServiceMessageSender
.SenderKeyGroupEvents
.EMPTY
),
145 for (var r
: results
) {
146 handleSendMessageResult(r
);
152 public SendMessageResult
sendDeliveryReceipt(
153 RecipientId recipientId
, List
<Long
> messageIds
155 var receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
157 System
.currentTimeMillis());
159 return sendReceiptMessage(receiptMessage
, recipientId
);
162 public SendMessageResult
sendReceiptMessage(
163 final SignalServiceReceiptMessage receiptMessage
, final RecipientId recipientId
165 return handleSendMessage(recipientId
,
166 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendReceipt(address
,
171 public SendMessageResult
sendRetryReceipt(
172 DecryptionErrorMessage errorMessage
, RecipientId recipientId
, Optional
<GroupId
> groupId
174 logger
.debug("Sending retry receipt for {} to {}, device: {}",
175 errorMessage
.getTimestamp(),
177 errorMessage
.getDeviceId());
178 return handleSendMessage(recipientId
,
179 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendRetryReceipt(address
,
181 groupId
.transform(GroupId
::serialize
),
185 public SendMessageResult
sendNullMessage(RecipientId recipientId
) {
186 return handleSendMessage(recipientId
, SignalServiceMessageSender
::sendNullMessage
);
189 public SendMessageResult
sendSelfMessage(
190 SignalServiceDataMessage
.Builder messageBuilder
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
);
197 var message
= messageBuilder
.build();
198 return sendSelfMessage(message
);
201 public SendMessageResult
sendSyncMessage(SignalServiceSyncMessage message
) {
202 var messageSender
= dependencies
.getMessageSender();
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
);
225 public SendMessageResult
sendTypingMessage(
226 SignalServiceTypingMessage message
, RecipientId recipientId
228 return handleSendMessage(recipientId
,
229 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendTyping(address
,
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());
241 final var distributionId
= g
.getDistributionId();
242 final var recipientIds
= g
.getMembersWithout(account
.getSelfRecipientId());
244 return sendGroupTypingMessage(message
, recipientIds
, distributionId
);
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(
258 (distId
, recipients
, unidentifiedAccess
, isRecipientUpdate
) -> messageSender
.sendGroupTyping(distId
,
265 for (var r
: results
) {
266 handleSendMessageResult(r
);
272 private GroupInfo
getGroupForSending(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
273 var g
= context
.getGroupHelper().getGroup(groupId
);
275 throw new GroupNotFoundException(groupId
);
277 if (!g
.isMember(account
.getSelfRecipientId())) {
278 throw new NotAGroupMemberException(groupId
, g
.getTitle());
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
294 : getSenderKeyCapableRecipientIds(recipientIds
);
295 final var allResults
= new ArrayList
<SendMessageResult
>(recipientIds
.size());
297 if (senderKeyTargets
.size() > 0) {
298 final var results
= sendGroupMessageInternalWithSenderKey(senderKeySender
,
303 if (results
== null) {
304 senderKeyTargets
= Set
.of();
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()))
311 if (failedTargets
.size() > 0) {
312 senderKeyTargets
= new HashSet
<>(senderKeyTargets
);
313 failedTargets
.forEach(senderKeyTargets
::remove
);
318 final var legacyTargets
= new HashSet
<>(recipientIds
);
319 legacyTargets
.removeAll(senderKeyTargets
);
320 final boolean onlyTargetIsSelfWithLinkedDevice
= recipientIds
.isEmpty() && account
.isMultiDevice();
322 if (legacyTargets
.size() > 0 || onlyTargetIsSelfWithLinkedDevice
) {
323 if (legacyTargets
.size() > 0) {
324 logger
.debug("Need to do {} legacy sends.", legacyTargets
.size());
326 logger
.debug("Need to do a legacy send to send a sync message for a group of only ourselves.");
329 final List
<SendMessageResult
> results
= sendGroupMessageInternalWithLegacy(legacySender
,
331 isRecipientUpdate
|| allResults
.size() > 0);
332 allResults
.addAll(results
);
334 final var duration
= Duration
.ofMillis(System
.currentTimeMillis() - startTime
);
335 logger
.debug("Sending took {}", duration
.toString());
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.");
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
)) {
355 final var access
= context
.getUnidentifiedAccessHelper().getAccessFor(recipientId
);
356 if (!access
.isPresent() || !access
.get().getTargetUnidentifiedAccess().isPresent()) {
360 final var identity
= account
.getIdentityKeyStore().getIdentity(recipientId
);
361 if (identity
== null || !identity
.getTrustLevel().isTrusted()) {
365 senderKeyTargets
.add(recipientId
);
368 if (senderKeyTargets
.size() < 2) {
369 logger
.debug("Too few sender-key-capable users ({}). Doing all legacy sends.", senderKeyTargets
.size());
373 logger
.debug("Can use sender key for {}/{} recipients.", senderKeyTargets
.size(), recipientIds
.size());
374 return senderKeyTargets
;
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
)
384 final var unidentifiedAccesses
= context
.getUnidentifiedAccessHelper().getAccessFor(recipientIdList
);
386 final var results
= sender
.send(addresses
, unidentifiedAccesses
, isRecipientUpdate
);
388 final var successCount
= results
.stream().filter(SendMessageResult
::isSuccess
).count();
389 logger
.debug("Successfully sent using 1:1 to {}/{} legacy targets.", successCount
, recipientIdList
.size());
391 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
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
);
404 long keyCreateTime
= account
.getSenderKeyStore()
405 .getCreateTimeForOurKey(account
.getSelfRecipientId(), account
.getDeviceId(), distributionId
);
406 long keyAge
= System
.currentTimeMillis() - keyCreateTime
;
408 if (keyCreateTime
!= -1 && keyAge
> TimeUnit
.DAYS
.toMillis(14)) {
409 logger
.debug("DistributionId {} was created at {} and is {} ms old (~{} days). Rotating.",
413 TimeUnit
.MILLISECONDS
.toDays(keyAge
));
414 account
.getSenderKeyStore().deleteOurKey(account
.getSelfRecipientId(), distributionId
);
417 List
<SignalServiceAddress
> addresses
= recipientIdList
.stream()
418 .map(context
.getRecipientHelper()::resolveSignalServiceAddress
)
419 .collect(Collectors
.toList());
420 List
<UnidentifiedAccess
> unidentifiedAccesses
= context
.getUnidentifiedAccessHelper()
421 .getAccessFor(recipientIdList
)
424 .map(UnidentifiedAccessPair
::getTargetUnidentifiedAccess
)
426 .collect(Collectors
.toList());
429 List
<SendMessageResult
> results
= sender
.send(distributionId
,
431 unidentifiedAccesses
,
434 final var successCount
= results
.stream().filter(SendMessageResult
::isSuccess
).count();
435 logger
.debug("Successfully sent using sender key to {}/{} sender key targets.",
440 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
442 } catch (InvalidUnidentifiedAccessHeaderException e
) {
443 logger
.warn("Someone had a bad UD header. Falling back to legacy sends.", e
);
445 } catch (NoSessionException e
) {
446 logger
.warn("No session. Falling back to legacy sends.", e
);
447 account
.getSenderKeyStore().deleteOurKey(account
.getSelfRecipientId(), distributionId
);
449 } catch (InvalidKeyException e
) {
450 logger
.warn("Invalid key. Falling back to legacy sends.", e
);
451 account
.getSenderKeyStore().deleteOurKey(account
.getSelfRecipientId(), distributionId
);
453 } catch (InvalidRegistrationIdException e
) {
454 logger
.warn("Invalid registrationId. Falling back to legacy sends.", e
);
456 } catch (NotFoundException e
) {
457 logger
.warn("Someone was unregistered. Falling back to legacy sends.", e
);
462 private SendMessageResult
sendMessage(
463 SignalServiceDataMessage message
, RecipientId recipientId
465 return handleSendMessage(recipientId
,
466 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendDataMessage(address
,
470 SignalServiceMessageSender
.IndividualSendEvents
.EMPTY
));
473 private SendMessageResult
handleSendMessage(RecipientId recipientId
, SenderHandler s
) {
474 var messageSender
= dependencies
.getMessageSender();
476 var address
= context
.getRecipientHelper().resolveSignalServiceAddress(recipientId
);
479 return s
.send(messageSender
, address
, context
.getUnidentifiedAccessHelper().getAccessFor(recipientId
));
480 } catch (UnregisteredUserException e
) {
481 final RecipientId newRecipientId
;
483 newRecipientId
= context
.getRecipientHelper().refreshRegisteredUser(recipientId
);
484 } catch (UnregisteredRecipientException ex
) {
485 return SendMessageResult
.unregisteredFailure(address
);
487 address
= context
.getRecipientHelper().resolveSignalServiceAddress(newRecipientId
);
488 return s
.send(messageSender
,
490 context
.getUnidentifiedAccessHelper().getAccessFor(newRecipientId
));
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
);
507 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) {
508 var address
= account
.getSelfAddress();
509 var transcript
= new SentTranscriptMessage(Optional
.of(address
),
510 message
.getTimestamp(),
512 message
.getExpiresInSeconds(),
513 Map
.of(address
, true),
515 var syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
517 return sendSyncMessage(syncMessage
);
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
528 account
.getRecipientStore()
529 .storeProfile(recipientId
,
530 Profile
.newBuilder(profile
)
531 .withUnidentifiedAccessMode(Profile
.UnidentifiedAccessMode
.UNKNOWN
)
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
542 account
.getRecipientStore()
543 .storeProfile(recipientId
,
544 Profile
.newBuilder(profile
)
545 .withUnidentifiedAccessMode(Profile
.UnidentifiedAccessMode
.UNKNOWN
)
549 if (r
.getIdentityFailure() != null) {
550 final var recipientId
= context
.getRecipientHelper().resolveRecipient(r
.getAddress());
551 context
.getIdentityHelper().handleIdentityFailure(recipientId
, r
.getIdentityFailure());
555 interface SenderHandler
{
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
;
564 interface SenderKeySenderHandler
{
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
;
574 interface LegacySenderHandler
{
576 List
<SendMessageResult
> send(
577 List
<SignalServiceAddress
> recipients
,
578 List
<Optional
<UnidentifiedAccessPair
>> unidentifiedAccess
,
579 boolean isRecipientUpdate
580 ) throws IOException
, UntrustedIdentityException
;