1 package org
.asamk
.signal
.manager
.helper
;
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
.RecipientId
;
12 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientResolver
;
13 import org
.slf4j
.Logger
;
14 import org
.slf4j
.LoggerFactory
;
15 import org
.whispersystems
.libsignal
.protocol
.DecryptionErrorMessage
;
16 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
17 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
18 import org
.whispersystems
.signalservice
.api
.crypto
.ContentHint
;
19 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
20 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
21 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
22 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
23 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceTypingMessage
;
24 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
25 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
26 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
27 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.ProofRequiredException
;
28 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.RateLimitException
;
29 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
31 import java
.io
.IOException
;
32 import java
.util
.ArrayList
;
33 import java
.util
.List
;
36 import java
.util
.stream
.Collectors
;
38 public class SendHelper
{
40 private final static Logger logger
= LoggerFactory
.getLogger(SendHelper
.class);
42 private final SignalAccount account
;
43 private final SignalDependencies dependencies
;
44 private final UnidentifiedAccessHelper unidentifiedAccessHelper
;
45 private final SignalServiceAddressResolver addressResolver
;
46 private final RecipientResolver recipientResolver
;
47 private final IdentityFailureHandler identityFailureHandler
;
48 private final GroupProvider groupProvider
;
49 private final RecipientRegistrationRefresher recipientRegistrationRefresher
;
52 final SignalAccount account
,
53 final SignalDependencies dependencies
,
54 final UnidentifiedAccessHelper unidentifiedAccessHelper
,
55 final SignalServiceAddressResolver addressResolver
,
56 final RecipientResolver recipientResolver
,
57 final IdentityFailureHandler identityFailureHandler
,
58 final GroupProvider groupProvider
,
59 final RecipientRegistrationRefresher recipientRegistrationRefresher
61 this.account
= account
;
62 this.dependencies
= dependencies
;
63 this.unidentifiedAccessHelper
= unidentifiedAccessHelper
;
64 this.addressResolver
= addressResolver
;
65 this.recipientResolver
= recipientResolver
;
66 this.identityFailureHandler
= identityFailureHandler
;
67 this.groupProvider
= groupProvider
;
68 this.recipientRegistrationRefresher
= recipientRegistrationRefresher
;
72 * Send a single message to one recipient.
73 * The message is extended with the current expiration timer.
75 public SendMessageResult
sendMessage(
76 final SignalServiceDataMessage
.Builder messageBuilder
, final RecipientId recipientId
77 ) throws IOException
{
78 final var contact
= account
.getContactStore().getContact(recipientId
);
79 final var expirationTime
= contact
!= null ? contact
.getMessageExpirationTime() : 0;
80 messageBuilder
.withExpiration(expirationTime
);
81 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
83 final var message
= messageBuilder
.build();
84 final var result
= sendMessage(message
, recipientId
);
85 handlePossibleIdentityFailure(result
);
90 * Send a group message to the given group
91 * The message is extended with the current expiration timer for the group and the group context.
93 public List
<SendMessageResult
> sendAsGroupMessage(
94 SignalServiceDataMessage
.Builder messageBuilder
, GroupId groupId
95 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
, GroupSendingNotAllowedException
{
96 final var g
= getGroupForSending(groupId
);
97 return sendAsGroupMessage(messageBuilder
, g
);
100 private List
<SendMessageResult
> sendAsGroupMessage(
101 final SignalServiceDataMessage
.Builder messageBuilder
, final GroupInfo g
102 ) throws IOException
, GroupSendingNotAllowedException
{
103 GroupUtils
.setGroupContext(messageBuilder
, g
);
104 messageBuilder
.withExpiration(g
.getMessageExpirationTimer());
106 final var message
= messageBuilder
.build();
107 final var recipients
= g
.getMembersWithout(account
.getSelfRecipientId());
109 if (g
.isAnnouncementGroup() && !g
.isAdmin(account
.getSelfRecipientId())) {
110 if (message
.getBody().isPresent()
111 || message
.getAttachments().isPresent()
112 || message
.getQuote().isPresent()
113 || message
.getPreviews().isPresent()
114 || message
.getMentions().isPresent()
115 || message
.getSticker().isPresent()) {
116 throw new GroupSendingNotAllowedException(g
.getGroupId(), g
.getTitle());
120 return sendGroupMessage(message
, recipients
);
124 * Send a complete group message to the given recipients (should be current/old/new members)
125 * This method should only be used for create/update/quit group messages.
127 public List
<SendMessageResult
> sendGroupMessage(
128 final SignalServiceDataMessage message
, final Set
<RecipientId
> recipientIds
129 ) throws IOException
{
130 List
<SendMessageResult
> result
= sendGroupMessageInternal(message
, recipientIds
);
132 for (var r
: result
) {
133 handlePossibleIdentityFailure(r
);
139 public SendMessageResult
sendDeliveryReceipt(
140 RecipientId recipientId
, List
<Long
> messageIds
142 var receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
144 System
.currentTimeMillis());
146 return sendReceiptMessage(receiptMessage
, recipientId
);
149 public SendMessageResult
sendReceiptMessage(
150 final SignalServiceReceiptMessage receiptMessage
, final RecipientId recipientId
152 return handleSendMessage(recipientId
,
153 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendReceipt(address
,
158 public SendMessageResult
sendRetryReceipt(
159 DecryptionErrorMessage errorMessage
, RecipientId recipientId
, Optional
<GroupId
> groupId
161 logger
.debug("Sending retry receipt for {} to {}, device: {}",
162 errorMessage
.getTimestamp(),
164 errorMessage
.getDeviceId());
165 return handleSendMessage(recipientId
,
166 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendRetryReceipt(address
,
168 groupId
.transform(GroupId
::serialize
),
172 public SendMessageResult
sendNullMessage(RecipientId recipientId
) {
173 return handleSendMessage(recipientId
, SignalServiceMessageSender
::sendNullMessage
);
176 public SendMessageResult
sendSelfMessage(
177 SignalServiceDataMessage
.Builder messageBuilder
179 final var recipientId
= account
.getSelfRecipientId();
180 final var contact
= account
.getContactStore().getContact(recipientId
);
181 final var expirationTime
= contact
!= null ? contact
.getMessageExpirationTime() : 0;
182 messageBuilder
.withExpiration(expirationTime
);
184 var message
= messageBuilder
.build();
185 return sendSelfMessage(message
);
188 public SendMessageResult
sendSyncMessage(SignalServiceSyncMessage message
) {
189 var messageSender
= dependencies
.getMessageSender();
191 return messageSender
.sendSyncMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
192 } catch (UnregisteredUserException e
) {
193 var address
= addressResolver
.resolveSignalServiceAddress(account
.getSelfRecipientId());
194 return SendMessageResult
.unregisteredFailure(address
);
195 } catch (ProofRequiredException e
) {
196 var address
= addressResolver
.resolveSignalServiceAddress(account
.getSelfRecipientId());
197 return SendMessageResult
.proofRequiredFailure(address
, e
);
198 } catch (RateLimitException e
) {
199 var address
= addressResolver
.resolveSignalServiceAddress(account
.getSelfRecipientId());
200 logger
.warn("Sending failed due to rate limiting from the signal server: {}", e
.getMessage());
201 return SendMessageResult
.networkFailure(address
);
202 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
203 var address
= addressResolver
.resolveSignalServiceAddress(account
.getSelfRecipientId());
204 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
205 } catch (IOException e
) {
206 var address
= addressResolver
.resolveSignalServiceAddress(account
.getSelfRecipientId());
207 logger
.warn("Failed to send message due to IO exception: {}", e
.getMessage());
208 return SendMessageResult
.networkFailure(address
);
212 public SendMessageResult
sendTypingMessage(
213 SignalServiceTypingMessage message
, RecipientId recipientId
215 return handleSendMessage(recipientId
,
216 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendTyping(address
,
221 public List
<SendMessageResult
> sendGroupTypingMessage(
222 SignalServiceTypingMessage message
, GroupId groupId
223 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, GroupSendingNotAllowedException
{
224 final var g
= getGroupForSending(groupId
);
225 if (g
.isAnnouncementGroup() && !g
.isAdmin(account
.getSelfRecipientId())) {
226 throw new GroupSendingNotAllowedException(groupId
, g
.getTitle());
228 final var messageSender
= dependencies
.getMessageSender();
229 final var recipientIdList
= new ArrayList
<>(g
.getMembersWithout(account
.getSelfRecipientId()));
230 final var addresses
= recipientIdList
.stream()
231 .map(addressResolver
::resolveSignalServiceAddress
)
232 .collect(Collectors
.toList());
233 return messageSender
.sendTyping(addresses
,
234 unidentifiedAccessHelper
.getAccessFor(recipientIdList
),
239 private GroupInfo
getGroupForSending(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
240 var g
= groupProvider
.getGroup(groupId
);
242 throw new GroupNotFoundException(groupId
);
244 if (!g
.isMember(account
.getSelfRecipientId())) {
245 throw new NotAGroupMemberException(groupId
, g
.getTitle());
250 private List
<SendMessageResult
> sendGroupMessageInternal(
251 final SignalServiceDataMessage message
, final Set
<RecipientId
> recipientIds
252 ) throws IOException
{
254 var messageSender
= dependencies
.getMessageSender();
255 // isRecipientUpdate is true if we've already sent this message to some recipients in the past, otherwise false.
256 final var isRecipientUpdate
= false;
257 final var recipientIdList
= new ArrayList
<>(recipientIds
);
258 final var addresses
= recipientIdList
.stream()
259 .map(addressResolver
::resolveSignalServiceAddress
)
260 .collect(Collectors
.toList());
261 return messageSender
.sendDataMessage(addresses
,
262 unidentifiedAccessHelper
.getAccessFor(recipientIdList
),
266 SignalServiceMessageSender
.LegacyGroupEvents
.EMPTY
,
267 sendResult
-> logger
.trace("Partial message send result: {}", sendResult
.isSuccess()),
269 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
274 private SendMessageResult
sendMessage(
275 SignalServiceDataMessage message
, RecipientId recipientId
277 return handleSendMessage(recipientId
,
278 (messageSender
, address
, unidentifiedAccess
) -> messageSender
.sendDataMessage(address
,
282 SignalServiceMessageSender
.IndividualSendEvents
.EMPTY
));
285 private SendMessageResult
handleSendMessage(RecipientId recipientId
, SenderHandler s
) {
286 var messageSender
= dependencies
.getMessageSender();
288 var address
= addressResolver
.resolveSignalServiceAddress(recipientId
);
291 return s
.send(messageSender
, address
, unidentifiedAccessHelper
.getAccessFor(recipientId
));
292 } catch (UnregisteredUserException e
) {
293 final var newRecipientId
= recipientRegistrationRefresher
.refreshRecipientRegistration(recipientId
);
294 address
= addressResolver
.resolveSignalServiceAddress(newRecipientId
);
295 return s
.send(messageSender
, address
, unidentifiedAccessHelper
.getAccessFor(newRecipientId
));
297 } catch (UnregisteredUserException e
) {
298 return SendMessageResult
.unregisteredFailure(address
);
299 } catch (ProofRequiredException e
) {
300 return SendMessageResult
.proofRequiredFailure(address
, e
);
301 } catch (RateLimitException e
) {
302 logger
.warn("Sending failed due to rate limiting from the signal server: {}", e
.getMessage());
303 return SendMessageResult
.networkFailure(address
);
304 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
305 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
306 } catch (IOException e
) {
307 logger
.warn("Failed to send message due to IO exception: {}", e
.getMessage());
308 return SendMessageResult
.networkFailure(address
);
312 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) {
313 var address
= account
.getSelfAddress();
314 var transcript
= new SentTranscriptMessage(Optional
.of(address
),
315 message
.getTimestamp(),
317 message
.getExpiresInSeconds(),
318 Map
.of(address
, true),
320 var syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
322 return sendSyncMessage(syncMessage
);
325 private void handlePossibleIdentityFailure(final SendMessageResult r
) {
326 if (r
.getIdentityFailure() != null) {
327 final var recipientId
= recipientResolver
.resolveRecipient(r
.getAddress());
328 identityFailureHandler
.handleIdentityFailure(recipientId
, r
.getIdentityFailure());
332 interface SenderHandler
{
334 SendMessageResult
send(
335 SignalServiceMessageSender messageSender
,
336 SignalServiceAddress address
,
337 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
338 ) throws IOException
, UnregisteredUserException
, ProofRequiredException
, RateLimitException
, org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;