]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java
Update libsignal-service-java
[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.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;
30
31 import java.io.IOException;
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36
37 public class SendHelper {
38
39 private final static Logger logger = LoggerFactory.getLogger(SendHelper.class);
40
41 private final SignalAccount account;
42 private final SignalDependencies dependencies;
43 private final UnidentifiedAccessHelper unidentifiedAccessHelper;
44 private final SignalServiceAddressResolver addressResolver;
45 private final RecipientResolver recipientResolver;
46 private final IdentityFailureHandler identityFailureHandler;
47 private final GroupProvider groupProvider;
48 private final RecipientRegistrationRefresher recipientRegistrationRefresher;
49
50 public SendHelper(
51 final SignalAccount account,
52 final SignalDependencies dependencies,
53 final UnidentifiedAccessHelper unidentifiedAccessHelper,
54 final SignalServiceAddressResolver addressResolver,
55 final RecipientResolver recipientResolver,
56 final IdentityFailureHandler identityFailureHandler,
57 final GroupProvider groupProvider,
58 final RecipientRegistrationRefresher recipientRegistrationRefresher
59 ) {
60 this.account = account;
61 this.dependencies = dependencies;
62 this.unidentifiedAccessHelper = unidentifiedAccessHelper;
63 this.addressResolver = addressResolver;
64 this.recipientResolver = recipientResolver;
65 this.identityFailureHandler = identityFailureHandler;
66 this.groupProvider = groupProvider;
67 this.recipientRegistrationRefresher = recipientRegistrationRefresher;
68 }
69
70 /**
71 * Send a single message to one recipient.
72 * The message is extended with the current expiration timer.
73 */
74 public SendMessageResult sendMessage(
75 final SignalServiceDataMessage.Builder messageBuilder, final RecipientId recipientId
76 ) throws IOException {
77 final var contact = account.getContactStore().getContact(recipientId);
78 final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0;
79 messageBuilder.withExpiration(expirationTime);
80 messageBuilder.withProfileKey(account.getProfileKey().serialize());
81
82 final var message = messageBuilder.build();
83 final var result = sendMessage(message, recipientId);
84 handlePossibleIdentityFailure(result);
85 return result;
86 }
87
88 /**
89 * Send a group message to the given group
90 * The message is extended with the current expiration timer for the group and the group context.
91 */
92 public List<SendMessageResult> sendAsGroupMessage(
93 SignalServiceDataMessage.Builder messageBuilder, GroupId groupId
94 ) throws IOException, GroupNotFoundException, NotAGroupMemberException, GroupSendingNotAllowedException {
95 final var g = getGroupForSending(groupId);
96 return sendAsGroupMessage(messageBuilder, g);
97 }
98
99 private List<SendMessageResult> sendAsGroupMessage(
100 final SignalServiceDataMessage.Builder messageBuilder, final GroupInfo g
101 ) throws IOException, GroupSendingNotAllowedException {
102 GroupUtils.setGroupContext(messageBuilder, g);
103 messageBuilder.withExpiration(g.getMessageExpirationTimer());
104
105 final var message = messageBuilder.build();
106 final var recipients = g.getMembersWithout(account.getSelfRecipientId());
107
108 if (g.isAnnouncementGroup() && !g.isAdmin(account.getSelfRecipientId())) {
109 if (message.getBody().isPresent()
110 || message.getAttachments().isPresent()
111 || message.getQuote().isPresent()
112 || message.getPreviews().isPresent()
113 || message.getMentions().isPresent()
114 || message.getSticker().isPresent()) {
115 throw new GroupSendingNotAllowedException(g.getGroupId(), g.getTitle());
116 }
117 }
118
119 return sendGroupMessage(message, recipients);
120 }
121
122 /**
123 * Send a complete group message to the given recipients (should be current/old/new members)
124 * This method should only be used for create/update/quit group messages.
125 */
126 public List<SendMessageResult> sendGroupMessage(
127 final SignalServiceDataMessage message, final Set<RecipientId> recipientIds
128 ) throws IOException {
129 List<SendMessageResult> result = sendGroupMessageInternal(message, recipientIds);
130
131 for (var r : result) {
132 handlePossibleIdentityFailure(r);
133 }
134
135 return result;
136 }
137
138 public SendMessageResult sendDeliveryReceipt(
139 RecipientId recipientId, List<Long> messageIds
140 ) {
141 var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
142 messageIds,
143 System.currentTimeMillis());
144
145 return sendReceiptMessage(receiptMessage, recipientId);
146 }
147
148 public SendMessageResult sendReceiptMessage(
149 final SignalServiceReceiptMessage receiptMessage, final RecipientId recipientId
150 ) {
151 return handleSendMessage(recipientId,
152 (messageSender, address, unidentifiedAccess) -> messageSender.sendReceipt(address,
153 unidentifiedAccess,
154 receiptMessage));
155 }
156
157 public SendMessageResult sendRetryReceipt(
158 DecryptionErrorMessage errorMessage, RecipientId recipientId, Optional<GroupId> groupId
159 ) {
160 logger.debug("Sending retry receipt for {} to {}, device: {}",
161 errorMessage.getTimestamp(),
162 recipientId,
163 errorMessage.getDeviceId());
164 return handleSendMessage(recipientId,
165 (messageSender, address, unidentifiedAccess) -> messageSender.sendRetryReceipt(address,
166 unidentifiedAccess,
167 groupId.transform(GroupId::serialize),
168 errorMessage));
169 }
170
171 public SendMessageResult sendNullMessage(RecipientId recipientId) {
172 return handleSendMessage(recipientId, SignalServiceMessageSender::sendNullMessage);
173 }
174
175 public SendMessageResult sendSelfMessage(
176 SignalServiceDataMessage.Builder messageBuilder
177 ) {
178 final var recipientId = account.getSelfRecipientId();
179 final var contact = account.getContactStore().getContact(recipientId);
180 final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0;
181 messageBuilder.withExpiration(expirationTime);
182
183 var message = messageBuilder.build();
184 return sendSelfMessage(message);
185 }
186
187 public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message) {
188 var messageSender = dependencies.getMessageSender();
189 try {
190 return messageSender.sendSyncMessage(message, unidentifiedAccessHelper.getAccessForSync());
191 } catch (UnregisteredUserException e) {
192 var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
193 return SendMessageResult.unregisteredFailure(address);
194 } catch (ProofRequiredException e) {
195 var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
196 return SendMessageResult.proofRequiredFailure(address, e);
197 } catch (RateLimitException e) {
198 var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
199 logger.warn("Sending failed due to rate limiting from the signal server: {}", e.getMessage());
200 return SendMessageResult.networkFailure(address);
201 } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
202 var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
203 return SendMessageResult.identityFailure(address, e.getIdentityKey());
204 } catch (IOException e) {
205 var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
206 logger.warn("Failed to send message due to IO exception: {}", e.getMessage());
207 return SendMessageResult.networkFailure(address);
208 }
209 }
210
211 public SendMessageResult sendTypingMessage(
212 SignalServiceTypingMessage message, RecipientId recipientId
213 ) {
214 return handleSendMessage(recipientId,
215 (messageSender, address, unidentifiedAccess) -> messageSender.sendTyping(address,
216 unidentifiedAccess,
217 message));
218 }
219
220 public List<SendMessageResult> sendGroupTypingMessage(
221 SignalServiceTypingMessage message, GroupId groupId
222 ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
223 final var g = getGroupForSending(groupId);
224 if (g.isAnnouncementGroup() && !g.isAdmin(account.getSelfRecipientId())) {
225 throw new GroupSendingNotAllowedException(groupId, g.getTitle());
226 }
227 final var messageSender = dependencies.getMessageSender();
228 final var recipientIdList = new ArrayList<>(g.getMembersWithout(account.getSelfRecipientId()));
229 final var addresses = recipientIdList.stream().map(addressResolver::resolveSignalServiceAddress).toList();
230 return messageSender.sendTyping(addresses,
231 unidentifiedAccessHelper.getAccessFor(recipientIdList),
232 message,
233 null);
234 }
235
236 private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
237 var g = groupProvider.getGroup(groupId);
238 if (g == null) {
239 throw new GroupNotFoundException(groupId);
240 }
241 if (!g.isMember(account.getSelfRecipientId())) {
242 throw new NotAGroupMemberException(groupId, g.getTitle());
243 }
244 return g;
245 }
246
247 private List<SendMessageResult> sendGroupMessageInternal(
248 final SignalServiceDataMessage message, final Set<RecipientId> recipientIds
249 ) throws IOException {
250 try {
251 var messageSender = dependencies.getMessageSender();
252 // isRecipientUpdate is true if we've already sent this message to some recipients in the past, otherwise false.
253 final var isRecipientUpdate = false;
254 final var recipientIdList = new ArrayList<>(recipientIds);
255 final var addresses = recipientIdList.stream().map(addressResolver::resolveSignalServiceAddress).toList();
256 return messageSender.sendDataMessage(addresses,
257 unidentifiedAccessHelper.getAccessFor(recipientIdList),
258 isRecipientUpdate,
259 ContentHint.DEFAULT,
260 message,
261 SignalServiceMessageSender.LegacyGroupEvents.EMPTY,
262 sendResult -> logger.trace("Partial message send result: {}", sendResult.isSuccess()),
263 () -> false);
264 } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
265 return List.of();
266 }
267 }
268
269 private SendMessageResult sendMessage(
270 SignalServiceDataMessage message, RecipientId recipientId
271 ) {
272 return handleSendMessage(recipientId,
273 (messageSender, address, unidentifiedAccess) -> messageSender.sendDataMessage(address,
274 unidentifiedAccess,
275 ContentHint.DEFAULT,
276 message,
277 SignalServiceMessageSender.IndividualSendEvents.EMPTY));
278 }
279
280 private SendMessageResult handleSendMessage(RecipientId recipientId, SenderHandler s) {
281 var messageSender = dependencies.getMessageSender();
282
283 var address = addressResolver.resolveSignalServiceAddress(recipientId);
284 try {
285 try {
286 return s.send(messageSender, address, unidentifiedAccessHelper.getAccessFor(recipientId));
287 } catch (UnregisteredUserException e) {
288 final var newRecipientId = recipientRegistrationRefresher.refreshRecipientRegistration(recipientId);
289 address = addressResolver.resolveSignalServiceAddress(newRecipientId);
290 return s.send(messageSender, address, unidentifiedAccessHelper.getAccessFor(newRecipientId));
291 }
292 } catch (UnregisteredUserException e) {
293 return SendMessageResult.unregisteredFailure(address);
294 } catch (ProofRequiredException e) {
295 return SendMessageResult.proofRequiredFailure(address, e);
296 } catch (RateLimitException e) {
297 logger.warn("Sending failed due to rate limiting from the signal server: {}", e.getMessage());
298 return SendMessageResult.networkFailure(address);
299 } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
300 return SendMessageResult.identityFailure(address, e.getIdentityKey());
301 } catch (IOException e) {
302 logger.warn("Failed to send message due to IO exception: {}", e.getMessage());
303 return SendMessageResult.networkFailure(address);
304 }
305 }
306
307 private SendMessageResult sendSelfMessage(SignalServiceDataMessage message) {
308 var address = account.getSelfAddress();
309 var transcript = new SentTranscriptMessage(Optional.of(address),
310 message.getTimestamp(),
311 message,
312 message.getExpiresInSeconds(),
313 Map.of(address, true),
314 false);
315 var syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript);
316
317 return sendSyncMessage(syncMessage);
318 }
319
320 private void handlePossibleIdentityFailure(final SendMessageResult r) {
321 if (r.getIdentityFailure() != null) {
322 final var recipientId = recipientResolver.resolveRecipient(r.getAddress());
323 identityFailureHandler.handleIdentityFailure(recipientId, r.getIdentityFailure());
324 }
325 }
326
327 interface SenderHandler {
328
329 SendMessageResult send(
330 SignalServiceMessageSender messageSender,
331 SignalServiceAddress address,
332 Optional<UnidentifiedAccessPair> unidentifiedAccess
333 ) throws IOException, UnregisteredUserException, ProofRequiredException, RateLimitException, org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
334 }
335 }