2 Copyright (C) 2015-2021 AsamK and contributors
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 package org
.asamk
.signal
.manager
;
19 import org
.asamk
.signal
.manager
.api
.Device
;
20 import org
.asamk
.signal
.manager
.api
.Message
;
21 import org
.asamk
.signal
.manager
.api
.RecipientIdentifier
;
22 import org
.asamk
.signal
.manager
.api
.SendGroupMessageResults
;
23 import org
.asamk
.signal
.manager
.api
.SendMessageResults
;
24 import org
.asamk
.signal
.manager
.api
.TypingAction
;
25 import org
.asamk
.signal
.manager
.config
.ServiceConfig
;
26 import org
.asamk
.signal
.manager
.config
.ServiceEnvironment
;
27 import org
.asamk
.signal
.manager
.config
.ServiceEnvironmentConfig
;
28 import org
.asamk
.signal
.manager
.groups
.GroupId
;
29 import org
.asamk
.signal
.manager
.groups
.GroupIdV1
;
30 import org
.asamk
.signal
.manager
.groups
.GroupInviteLinkUrl
;
31 import org
.asamk
.signal
.manager
.groups
.GroupLinkState
;
32 import org
.asamk
.signal
.manager
.groups
.GroupNotFoundException
;
33 import org
.asamk
.signal
.manager
.groups
.GroupPermission
;
34 import org
.asamk
.signal
.manager
.groups
.GroupSendingNotAllowedException
;
35 import org
.asamk
.signal
.manager
.groups
.GroupUtils
;
36 import org
.asamk
.signal
.manager
.groups
.LastGroupAdminException
;
37 import org
.asamk
.signal
.manager
.groups
.NotAGroupMemberException
;
38 import org
.asamk
.signal
.manager
.helper
.AttachmentHelper
;
39 import org
.asamk
.signal
.manager
.helper
.GroupHelper
;
40 import org
.asamk
.signal
.manager
.helper
.GroupV2Helper
;
41 import org
.asamk
.signal
.manager
.helper
.PinHelper
;
42 import org
.asamk
.signal
.manager
.helper
.ProfileHelper
;
43 import org
.asamk
.signal
.manager
.helper
.SendHelper
;
44 import org
.asamk
.signal
.manager
.helper
.SyncHelper
;
45 import org
.asamk
.signal
.manager
.helper
.UnidentifiedAccessHelper
;
46 import org
.asamk
.signal
.manager
.jobs
.Context
;
47 import org
.asamk
.signal
.manager
.jobs
.Job
;
48 import org
.asamk
.signal
.manager
.jobs
.RetrieveStickerPackJob
;
49 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
50 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfo
;
51 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfoV1
;
52 import org
.asamk
.signal
.manager
.storage
.identities
.IdentityInfo
;
53 import org
.asamk
.signal
.manager
.storage
.identities
.TrustNewIdentity
;
54 import org
.asamk
.signal
.manager
.storage
.messageCache
.CachedMessage
;
55 import org
.asamk
.signal
.manager
.storage
.recipients
.Contact
;
56 import org
.asamk
.signal
.manager
.storage
.recipients
.Profile
;
57 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
58 import org
.asamk
.signal
.manager
.storage
.stickers
.Sticker
;
59 import org
.asamk
.signal
.manager
.storage
.stickers
.StickerPackId
;
60 import org
.asamk
.signal
.manager
.util
.KeyUtils
;
61 import org
.asamk
.signal
.manager
.util
.StickerUtils
;
62 import org
.asamk
.signal
.manager
.util
.Utils
;
63 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
64 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
65 import org
.signal
.zkgroup
.InvalidInputException
;
66 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
67 import org
.slf4j
.Logger
;
68 import org
.slf4j
.LoggerFactory
;
69 import org
.whispersystems
.libsignal
.IdentityKey
;
70 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
71 import org
.whispersystems
.libsignal
.InvalidKeyException
;
72 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
73 import org
.whispersystems
.libsignal
.fingerprint
.Fingerprint
;
74 import org
.whispersystems
.libsignal
.fingerprint
.FingerprintParsingException
;
75 import org
.whispersystems
.libsignal
.fingerprint
.FingerprintVersionMismatchException
;
76 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
77 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
78 import org
.whispersystems
.libsignal
.util
.Pair
;
79 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
80 import org
.whispersystems
.signalservice
.api
.SignalSessionLock
;
81 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
82 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
83 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
84 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
85 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
86 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
87 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
88 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
89 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
90 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceTypingMessage
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.StickerPackOperationMessage
;
92 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
93 import org
.whispersystems
.signalservice
.api
.util
.DeviceNameUtil
;
94 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
95 import org
.whispersystems
.signalservice
.api
.util
.PhoneNumberFormatter
;
96 import org
.whispersystems
.signalservice
.api
.websocket
.WebSocketUnavailableException
;
97 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.Quote
;
98 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedQuoteException
;
99 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedResponseException
;
100 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
101 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
102 import org
.whispersystems
.signalservice
.internal
.util
.Util
;
104 import java
.io
.Closeable
;
106 import java
.io
.IOException
;
108 import java
.net
.URISyntaxException
;
109 import java
.net
.URLEncoder
;
110 import java
.nio
.charset
.StandardCharsets
;
111 import java
.security
.SignatureException
;
112 import java
.util
.ArrayList
;
113 import java
.util
.Arrays
;
114 import java
.util
.Collection
;
115 import java
.util
.Date
;
116 import java
.util
.HashMap
;
117 import java
.util
.HashSet
;
118 import java
.util
.List
;
119 import java
.util
.Map
;
120 import java
.util
.Set
;
121 import java
.util
.UUID
;
122 import java
.util
.concurrent
.ExecutorService
;
123 import java
.util
.concurrent
.Executors
;
124 import java
.util
.concurrent
.TimeUnit
;
125 import java
.util
.concurrent
.TimeoutException
;
126 import java
.util
.concurrent
.locks
.ReentrantLock
;
127 import java
.util
.function
.Function
;
128 import java
.util
.stream
.Collectors
;
130 import static org
.asamk
.signal
.manager
.config
.ServiceConfig
.capabilities
;
132 public class Manager
implements Closeable
{
134 private final static Logger logger
= LoggerFactory
.getLogger(Manager
.class);
136 private final ServiceEnvironmentConfig serviceEnvironmentConfig
;
137 private final SignalDependencies dependencies
;
139 private SignalAccount account
;
141 private final ExecutorService executor
= Executors
.newCachedThreadPool();
143 private final ProfileHelper profileHelper
;
144 private final PinHelper pinHelper
;
145 private final SendHelper sendHelper
;
146 private final SyncHelper syncHelper
;
147 private final AttachmentHelper attachmentHelper
;
148 private final GroupHelper groupHelper
;
150 private final AvatarStore avatarStore
;
151 private final AttachmentStore attachmentStore
;
152 private final StickerPackStore stickerPackStore
;
153 private final SignalSessionLock sessionLock
= new SignalSessionLock() {
154 private final ReentrantLock LEGACY_LOCK
= new ReentrantLock();
157 public Lock
acquire() {
159 return LEGACY_LOCK
::unlock
;
164 SignalAccount account
,
165 PathConfig pathConfig
,
166 ServiceEnvironmentConfig serviceEnvironmentConfig
,
169 this.account
= account
;
170 this.serviceEnvironmentConfig
= serviceEnvironmentConfig
;
172 final var credentialsProvider
= new DynamicCredentialsProvider(account
.getUuid(),
173 account
.getUsername(),
174 account
.getPassword(),
175 account
.getDeviceId());
176 this.dependencies
= new SignalDependencies(account
.getSelfAddress(),
177 serviceEnvironmentConfig
,
180 account
.getSignalProtocolStore(),
183 this.avatarStore
= new AvatarStore(pathConfig
.getAvatarsPath());
184 this.attachmentStore
= new AttachmentStore(pathConfig
.getAttachmentsPath());
185 this.stickerPackStore
= new StickerPackStore(pathConfig
.getStickerPacksPath());
187 this.attachmentHelper
= new AttachmentHelper(dependencies
, attachmentStore
);
188 this.pinHelper
= new PinHelper(dependencies
.getKeyBackupService());
189 final var unidentifiedAccessHelper
= new UnidentifiedAccessHelper(account
::getProfileKey
,
190 account
.getProfileStore()::getProfileKey
,
191 this::getRecipientProfile
,
192 this::getSenderCertificate
);
193 this.profileHelper
= new ProfileHelper(account
,
196 account
.getProfileStore()::getProfileKey
,
197 unidentifiedAccessHelper
::getAccessFor
,
198 dependencies
::getProfileService
,
199 dependencies
::getMessageReceiver
,
200 this::resolveSignalServiceAddress
);
201 final GroupV2Helper groupV2Helper
= new GroupV2Helper(profileHelper
::getRecipientProfileKeyCredential
,
202 this::getRecipientProfile
,
203 account
::getSelfRecipientId
,
204 dependencies
.getGroupsV2Operations(),
205 dependencies
.getGroupsV2Api(),
206 this::resolveSignalServiceAddress
);
207 this.sendHelper
= new SendHelper(account
,
209 unidentifiedAccessHelper
,
210 this::resolveSignalServiceAddress
,
211 this::resolveRecipient
,
212 this::handleIdentityFailure
,
214 this::refreshRegisteredUser
);
215 this.groupHelper
= new GroupHelper(account
,
221 this::resolveSignalServiceAddress
,
222 this::resolveRecipient
);
223 this.syncHelper
= new SyncHelper(account
,
228 this::resolveSignalServiceAddress
,
229 this::resolveRecipient
);
232 public String
getUsername() {
233 return account
.getUsername();
236 private SignalServiceAddress
getSelfAddress() {
237 return account
.getSelfAddress();
240 public RecipientId
getSelfRecipientId() {
241 return account
.getSelfRecipientId();
244 private IdentityKeyPair
getIdentityKeyPair() {
245 return account
.getIdentityKeyPair();
248 public int getDeviceId() {
249 return account
.getDeviceId();
252 public static Manager
init(
255 ServiceEnvironment serviceEnvironment
,
257 final TrustNewIdentity trustNewIdentity
258 ) throws IOException
, NotRegisteredException
{
259 var pathConfig
= PathConfig
.createDefault(settingsPath
);
261 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
262 throw new NotRegisteredException();
265 var account
= SignalAccount
.load(pathConfig
.getDataPath(), username
, true, trustNewIdentity
);
267 if (!account
.isRegistered()) {
268 throw new NotRegisteredException();
271 final var serviceEnvironmentConfig
= ServiceConfig
.getServiceEnvironmentConfig(serviceEnvironment
, userAgent
);
273 return new Manager(account
, pathConfig
, serviceEnvironmentConfig
, userAgent
);
276 public static List
<String
> getAllLocalUsernames(File settingsPath
) {
277 var pathConfig
= PathConfig
.createDefault(settingsPath
);
278 final var dataPath
= pathConfig
.getDataPath();
279 final var files
= dataPath
.listFiles();
285 return Arrays
.stream(files
)
286 .filter(File
::isFile
)
288 .filter(file
-> PhoneNumberFormatter
.isValidNumber(file
, null))
289 .collect(Collectors
.toList());
292 public void checkAccountState() throws IOException
{
293 if (account
.getLastReceiveTimestamp() == 0) {
294 logger
.info("The Signal protocol expects that incoming messages are regularly received.");
296 var diffInMilliseconds
= System
.currentTimeMillis() - account
.getLastReceiveTimestamp();
297 long days
= TimeUnit
.DAYS
.convert(diffInMilliseconds
, TimeUnit
.MILLISECONDS
);
300 "Messages have been last received {} days ago. The Signal protocol expects that incoming messages are regularly received.",
304 if (dependencies
.getAccountManager().getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
307 if (account
.getUuid() == null) {
308 account
.setUuid(dependencies
.getAccountManager().getOwnUuid());
310 updateAccountAttributes();
314 * This is used for checking a set of phone numbers for registration on Signal
316 * @param numbers The set of phone number in question
317 * @return A map of numbers to canonicalized number and uuid. If a number is not registered the uuid is null.
318 * @throws IOException if its unable to get the contacts to check if they're registered
320 public Map
<String
, Pair
<String
, UUID
>> areUsersRegistered(Set
<String
> numbers
) throws IOException
{
321 Map
<String
, String
> canonicalizedNumbers
= numbers
.stream().collect(Collectors
.toMap(n
-> n
, n
-> {
323 return canonicalizePhoneNumber(n
);
324 } catch (InvalidNumberException e
) {
329 // Note "contactDetails" has no optionals. It only gives us info on users who are registered
330 var contactDetails
= getRegisteredUsers(canonicalizedNumbers
.values()
332 .filter(s
-> !s
.isEmpty())
333 .collect(Collectors
.toSet()));
335 return numbers
.stream().collect(Collectors
.toMap(n
-> n
, n
-> {
336 final var number
= canonicalizedNumbers
.get(n
);
337 final var uuid
= contactDetails
.get(number
);
338 return new Pair
<>(number
.isEmpty() ?
null : number
, uuid
);
342 public void updateAccountAttributes() throws IOException
{
343 dependencies
.getAccountManager()
344 .setAccountAttributes(account
.getEncryptedDeviceName(),
346 account
.getLocalRegistrationId(),
348 // set legacy pin only if no KBS master key is set
349 account
.getPinMasterKey() == null ? account
.getRegistrationLockPin() : null,
350 account
.getPinMasterKey() == null ?
null : account
.getPinMasterKey().deriveRegistrationLock(),
351 account
.getSelfUnidentifiedAccessKey(),
352 account
.isUnrestrictedUnidentifiedAccess(),
354 account
.isDiscoverableByPhoneNumber());
358 * @param givenName if null, the previous givenName will be kept
359 * @param familyName if null, the previous familyName will be kept
360 * @param about if null, the previous about text will be kept
361 * @param aboutEmoji if null, the previous about emoji will be kept
362 * @param avatar if avatar is null the image from the local avatar store is used (if present),
364 public void setProfile(
365 String givenName
, final String familyName
, String about
, String aboutEmoji
, Optional
<File
> avatar
366 ) throws IOException
{
367 profileHelper
.setProfile(givenName
, familyName
, about
, aboutEmoji
, avatar
);
368 syncHelper
.sendSyncFetchProfileMessage();
371 public void unregister() throws IOException
{
372 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
373 // If this is the master device, other users can't send messages to this number anymore.
374 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
375 dependencies
.getAccountManager().setGcmId(Optional
.absent());
377 account
.setRegistered(false);
380 public void deleteAccount() throws IOException
{
381 dependencies
.getAccountManager().deleteAccount();
383 account
.setRegistered(false);
386 public List
<Device
> getLinkedDevices() throws IOException
{
387 var devices
= dependencies
.getAccountManager().getDevices();
388 account
.setMultiDevice(devices
.size() > 1);
389 var identityKey
= account
.getIdentityKeyPair().getPrivateKey();
390 return devices
.stream().map(d
-> {
391 String deviceName
= d
.getName();
392 if (deviceName
!= null) {
394 deviceName
= DeviceNameUtil
.decryptDeviceName(deviceName
, identityKey
);
395 } catch (IOException e
) {
396 logger
.debug("Failed to decrypt device name, maybe plain text?", e
);
399 return new Device(d
.getId(), deviceName
, d
.getCreated(), d
.getLastSeen());
400 }).collect(Collectors
.toList());
403 public void removeLinkedDevices(int deviceId
) throws IOException
{
404 dependencies
.getAccountManager().removeDevice(deviceId
);
405 var devices
= dependencies
.getAccountManager().getDevices();
406 account
.setMultiDevice(devices
.size() > 1);
409 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
410 var info
= DeviceLinkInfo
.parseDeviceLinkUri(linkUri
);
412 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
415 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
416 var identityKeyPair
= getIdentityKeyPair();
417 var verificationCode
= dependencies
.getAccountManager().getNewDeviceVerificationCode();
419 dependencies
.getAccountManager()
420 .addDevice(deviceIdentifier
,
423 Optional
.of(account
.getProfileKey().serialize()),
425 account
.setMultiDevice(true);
428 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
, UnauthenticatedResponseException
{
429 if (!account
.isMasterDevice()) {
430 throw new RuntimeException("Only master device can set a PIN");
432 if (pin
.isPresent()) {
433 final var masterKey
= account
.getPinMasterKey() != null
434 ? account
.getPinMasterKey()
435 : KeyUtils
.createMasterKey();
437 pinHelper
.setRegistrationLockPin(pin
.get(), masterKey
);
439 account
.setRegistrationLockPin(pin
.get(), masterKey
);
442 pinHelper
.removeRegistrationLockPin();
444 account
.setRegistrationLockPin(null, null);
448 void refreshPreKeys() throws IOException
{
449 var oneTimePreKeys
= generatePreKeys();
450 final var identityKeyPair
= getIdentityKeyPair();
451 var signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
453 dependencies
.getAccountManager().setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
456 private List
<PreKeyRecord
> generatePreKeys() {
457 final var offset
= account
.getPreKeyIdOffset();
459 var records
= KeyUtils
.generatePreKeyRecords(offset
, ServiceConfig
.PREKEY_BATCH_SIZE
);
460 account
.addPreKeys(records
);
465 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
466 final var signedPreKeyId
= account
.getNextSignedPreKeyId();
468 var record = KeyUtils
.generateSignedPreKeyRecord(identityKeyPair
, signedPreKeyId
);
469 account
.addSignedPreKey(record);
474 public Profile
getRecipientProfile(RecipientId recipientId
) {
475 return profileHelper
.getRecipientProfile(recipientId
);
478 public void refreshRecipientProfile(RecipientId recipientId
) {
479 profileHelper
.refreshRecipientProfile(recipientId
);
482 public List
<GroupInfo
> getGroups() {
483 return account
.getGroupStore().getGroups();
486 public SendGroupMessageResults
quitGroup(
487 GroupId groupId
, Set
<RecipientIdentifier
.Single
> groupAdmins
488 ) throws GroupNotFoundException
, IOException
, NotAGroupMemberException
, LastGroupAdminException
{
489 final var newAdmins
= getRecipientIds(groupAdmins
);
490 return groupHelper
.quitGroup(groupId
, newAdmins
);
493 public void deleteGroup(GroupId groupId
) throws IOException
{
494 groupHelper
.deleteGroup(groupId
);
497 public Pair
<GroupId
, SendGroupMessageResults
> createGroup(
498 String name
, Set
<RecipientIdentifier
.Single
> members
, File avatarFile
499 ) throws IOException
, AttachmentInvalidException
{
500 return groupHelper
.createGroup(name
, members
== null ?
null : getRecipientIds(members
), avatarFile
);
503 public SendGroupMessageResults
updateGroup(
507 Set
<RecipientIdentifier
.Single
> members
,
508 Set
<RecipientIdentifier
.Single
> removeMembers
,
509 Set
<RecipientIdentifier
.Single
> admins
,
510 Set
<RecipientIdentifier
.Single
> removeAdmins
,
511 boolean resetGroupLink
,
512 GroupLinkState groupLinkState
,
513 GroupPermission addMemberPermission
,
514 GroupPermission editDetailsPermission
,
516 Integer expirationTimer
,
517 Boolean isAnnouncementGroup
518 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
, GroupSendingNotAllowedException
{
519 return groupHelper
.updateGroup(groupId
,
522 members
== null ?
null : getRecipientIds(members
),
523 removeMembers
== null ?
null : getRecipientIds(removeMembers
),
524 admins
== null ?
null : getRecipientIds(admins
),
525 removeAdmins
== null ?
null : getRecipientIds(removeAdmins
),
529 editDetailsPermission
,
532 isAnnouncementGroup
);
535 public Pair
<GroupId
, SendGroupMessageResults
> joinGroup(
536 GroupInviteLinkUrl inviteLinkUrl
537 ) throws IOException
, GroupLinkNotActiveException
{
538 return groupHelper
.joinGroup(inviteLinkUrl
);
541 public SendMessageResults
sendMessage(
542 SignalServiceDataMessage
.Builder messageBuilder
, Set
<RecipientIdentifier
> recipients
543 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, GroupSendingNotAllowedException
{
544 var results
= new HashMap
<RecipientIdentifier
, List
<SendMessageResult
>>();
545 long timestamp
= System
.currentTimeMillis();
546 messageBuilder
.withTimestamp(timestamp
);
547 for (final var recipient
: recipients
) {
548 if (recipient
instanceof RecipientIdentifier
.Single
) {
549 final var recipientId
= resolveRecipient((RecipientIdentifier
.Single
) recipient
);
550 final var result
= sendHelper
.sendMessage(messageBuilder
, recipientId
);
551 results
.put(recipient
, List
.of(result
));
552 } else if (recipient
instanceof RecipientIdentifier
.NoteToSelf
) {
553 final var result
= sendHelper
.sendSelfMessage(messageBuilder
);
554 results
.put(recipient
, List
.of(result
));
555 } else if (recipient
instanceof RecipientIdentifier
.Group
) {
556 final var groupId
= ((RecipientIdentifier
.Group
) recipient
).groupId
;
557 final var result
= sendHelper
.sendAsGroupMessage(messageBuilder
, groupId
);
558 results
.put(recipient
, result
);
561 return new SendMessageResults(timestamp
, results
);
564 public void sendTypingMessage(
565 SignalServiceTypingMessage
.Action action
, Set
<RecipientIdentifier
> recipients
566 ) throws IOException
, UntrustedIdentityException
, NotAGroupMemberException
, GroupNotFoundException
, GroupSendingNotAllowedException
{
567 final var timestamp
= System
.currentTimeMillis();
568 for (var recipient
: recipients
) {
569 if (recipient
instanceof RecipientIdentifier
.Single
) {
570 final var message
= new SignalServiceTypingMessage(action
, timestamp
, Optional
.absent());
571 final var recipientId
= resolveRecipient((RecipientIdentifier
.Single
) recipient
);
572 sendHelper
.sendTypingMessage(message
, recipientId
);
573 } else if (recipient
instanceof RecipientIdentifier
.Group
) {
574 final var groupId
= ((RecipientIdentifier
.Group
) recipient
).groupId
;
575 final var message
= new SignalServiceTypingMessage(action
, timestamp
, Optional
.of(groupId
.serialize()));
576 sendHelper
.sendGroupTypingMessage(message
, groupId
);
581 SendGroupMessageResults
sendGroupInfoMessage(
582 GroupIdV1 groupId
, SignalServiceAddress recipient
583 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
584 final var recipientId
= resolveRecipient(recipient
);
585 return groupHelper
.sendGroupInfoMessage(groupId
, recipientId
);
588 SendGroupMessageResults
sendGroupInfoRequest(
589 GroupIdV1 groupId
, SignalServiceAddress recipient
590 ) throws IOException
{
591 final var recipientId
= resolveRecipient(recipient
);
592 return groupHelper
.sendGroupInfoRequest(groupId
, recipientId
);
595 public void sendReadReceipt(
596 RecipientIdentifier
.Single sender
, List
<Long
> messageIds
597 ) throws IOException
, UntrustedIdentityException
{
598 var receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.READ
,
600 System
.currentTimeMillis());
602 sendHelper
.sendReceiptMessage(receiptMessage
, resolveRecipient(sender
));
605 public void sendViewedReceipt(
606 RecipientIdentifier
.Single sender
, List
<Long
> messageIds
607 ) throws IOException
, UntrustedIdentityException
{
608 var receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.VIEWED
,
610 System
.currentTimeMillis());
612 sendHelper
.sendReceiptMessage(receiptMessage
, resolveRecipient(sender
));
615 void sendDeliveryReceipt(
616 SignalServiceAddress remoteAddress
, List
<Long
> messageIds
617 ) throws IOException
, UntrustedIdentityException
{
618 var receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
620 System
.currentTimeMillis());
622 sendHelper
.sendReceiptMessage(receiptMessage
, resolveRecipient(remoteAddress
));
625 public SendMessageResults
sendMessage(
626 Message message
, Set
<RecipientIdentifier
> recipients
627 ) throws IOException
, AttachmentInvalidException
, NotAGroupMemberException
, GroupNotFoundException
, GroupSendingNotAllowedException
{
628 final var messageBuilder
= SignalServiceDataMessage
.newBuilder();
629 applyMessage(messageBuilder
, message
);
630 return sendMessage(messageBuilder
, recipients
);
633 private void applyMessage(
634 final SignalServiceDataMessage
.Builder messageBuilder
, final Message message
635 ) throws AttachmentInvalidException
, IOException
{
636 messageBuilder
.withBody(message
.getMessageText());
637 final var attachments
= message
.getAttachments();
638 if (attachments
!= null) {
639 messageBuilder
.withAttachments(attachmentHelper
.uploadAttachments(attachments
));
643 public SendMessageResults
sendRemoteDeleteMessage(
644 long targetSentTimestamp
, Set
<RecipientIdentifier
> recipients
645 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, GroupSendingNotAllowedException
{
646 var delete
= new SignalServiceDataMessage
.RemoteDelete(targetSentTimestamp
);
647 final var messageBuilder
= SignalServiceDataMessage
.newBuilder().withRemoteDelete(delete
);
648 return sendMessage(messageBuilder
, recipients
);
651 public SendMessageResults
sendMessageReaction(
654 RecipientIdentifier
.Single targetAuthor
,
655 long targetSentTimestamp
,
656 Set
<RecipientIdentifier
> recipients
657 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, GroupSendingNotAllowedException
{
658 var targetAuthorRecipientId
= resolveRecipient(targetAuthor
);
659 var reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
661 resolveSignalServiceAddress(targetAuthorRecipientId
),
662 targetSentTimestamp
);
663 final var messageBuilder
= SignalServiceDataMessage
.newBuilder().withReaction(reaction
);
664 return sendMessage(messageBuilder
, recipients
);
667 public SendMessageResults
sendEndSessionMessage(Set
<RecipientIdentifier
.Single
> recipients
) throws IOException
{
668 var messageBuilder
= SignalServiceDataMessage
.newBuilder().asEndSessionMessage();
671 return sendMessage(messageBuilder
,
672 recipients
.stream().map(RecipientIdentifier
.class::cast
).collect(Collectors
.toSet()));
673 } catch (GroupNotFoundException
| NotAGroupMemberException
| GroupSendingNotAllowedException e
) {
674 throw new AssertionError(e
);
676 for (var recipient
: recipients
) {
677 final var recipientId
= resolveRecipient((RecipientIdentifier
.Single
) recipient
);
678 handleEndSession(recipientId
);
683 void renewSession(RecipientId recipientId
) throws IOException
{
684 account
.getSessionStore().archiveSessions(recipientId
);
685 if (!recipientId
.equals(getSelfRecipientId())) {
686 sendHelper
.sendNullMessage(recipientId
);
690 public void setContactName(
691 RecipientIdentifier
.Single recipient
, String name
692 ) throws NotMasterDeviceException
{
693 if (!account
.isMasterDevice()) {
694 throw new NotMasterDeviceException();
696 final var recipientId
= resolveRecipient(recipient
);
697 var contact
= account
.getContactStore().getContact(recipientId
);
698 final var builder
= contact
== null ? Contact
.newBuilder() : Contact
.newBuilder(contact
);
699 account
.getContactStore().storeContact(recipientId
, builder
.withName(name
).build());
702 public void setContactBlocked(
703 RecipientIdentifier
.Single recipient
, boolean blocked
704 ) throws NotMasterDeviceException
{
705 if (!account
.isMasterDevice()) {
706 throw new NotMasterDeviceException();
708 setContactBlocked(resolveRecipient(recipient
), blocked
);
711 private void setContactBlocked(RecipientId recipientId
, boolean blocked
) {
712 var contact
= account
.getContactStore().getContact(recipientId
);
713 final var builder
= contact
== null ? Contact
.newBuilder() : Contact
.newBuilder(contact
);
714 // TODO cycle our profile key
715 account
.getContactStore().storeContact(recipientId
, builder
.withBlocked(blocked
).build());
718 public void setGroupBlocked(final GroupId groupId
, final boolean blocked
) throws GroupNotFoundException
{
719 var group
= getGroup(groupId
);
721 throw new GroupNotFoundException(groupId
);
724 group
.setBlocked(blocked
);
725 // TODO cycle our profile key
726 account
.getGroupStore().updateGroup(group
);
730 * Change the expiration timer for a contact
732 public void setExpirationTimer(
733 RecipientIdentifier
.Single recipient
, int messageExpirationTimer
734 ) throws IOException
{
735 var recipientId
= resolveRecipient(recipient
);
736 setExpirationTimer(recipientId
, messageExpirationTimer
);
737 final var messageBuilder
= SignalServiceDataMessage
.newBuilder().asExpirationUpdate();
739 sendMessage(messageBuilder
, Set
.of(recipient
));
740 } catch (NotAGroupMemberException
| GroupNotFoundException
| GroupSendingNotAllowedException e
) {
741 throw new AssertionError(e
);
745 private void setExpirationTimer(RecipientId recipientId
, int messageExpirationTimer
) {
746 var contact
= account
.getContactStore().getContact(recipientId
);
747 if (contact
!= null && contact
.getMessageExpirationTime() == messageExpirationTimer
) {
750 final var builder
= contact
== null ? Contact
.newBuilder() : Contact
.newBuilder(contact
);
751 account
.getContactStore()
752 .storeContact(recipientId
, builder
.withMessageExpirationTime(messageExpirationTimer
).build());
756 * Upload the sticker pack from path.
758 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
759 * @return if successful, returns the URL to install the sticker pack in the signal app
761 public URI
uploadStickerPack(File path
) throws IOException
, StickerPackInvalidException
{
762 var manifest
= StickerUtils
.getSignalServiceStickerManifestUpload(path
);
764 var messageSender
= dependencies
.getMessageSender();
766 var packKey
= KeyUtils
.createStickerUploadKey();
767 var packIdString
= messageSender
.uploadStickerManifest(manifest
, packKey
);
768 var packId
= StickerPackId
.deserialize(Hex
.fromStringCondensed(packIdString
));
770 var sticker
= new Sticker(packId
, packKey
);
771 account
.getStickerStore().updateSticker(sticker
);
774 return new URI("https",
778 + URLEncoder
.encode(Hex
.toStringCondensed(packId
.serialize()), StandardCharsets
.UTF_8
)
780 + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), StandardCharsets
.UTF_8
));
781 } catch (URISyntaxException e
) {
782 throw new AssertionError(e
);
786 public void requestAllSyncData() throws IOException
{
787 syncHelper
.requestAllSyncData();
790 private byte[] getSenderCertificate() {
793 if (account
.isPhoneNumberShared()) {
794 certificate
= dependencies
.getAccountManager().getSenderCertificate();
796 certificate
= dependencies
.getAccountManager().getSenderCertificateForPhoneNumberPrivacy();
798 } catch (IOException e
) {
799 logger
.warn("Failed to get sender certificate, ignoring: {}", e
.getMessage());
802 // TODO cache for a day
806 private Set
<RecipientId
> getRecipientIds(Collection
<RecipientIdentifier
.Single
> recipients
) {
807 final var signalServiceAddresses
= new HashSet
<SignalServiceAddress
>(recipients
.size());
808 final var addressesMissingUuid
= new HashSet
<SignalServiceAddress
>();
810 for (var number
: recipients
) {
811 final var resolvedAddress
= resolveSignalServiceAddress(resolveRecipient(number
));
812 if (resolvedAddress
.getUuid().isPresent()) {
813 signalServiceAddresses
.add(resolvedAddress
);
815 addressesMissingUuid
.add(resolvedAddress
);
819 final var numbersMissingUuid
= addressesMissingUuid
.stream()
820 .map(a
-> a
.getNumber().get())
821 .collect(Collectors
.toSet());
822 Map
<String
, UUID
> registeredUsers
;
824 registeredUsers
= getRegisteredUsers(numbersMissingUuid
);
825 } catch (IOException e
) {
826 logger
.warn("Failed to resolve uuids from server, ignoring: {}", e
.getMessage());
827 registeredUsers
= Map
.of();
830 for (var address
: addressesMissingUuid
) {
831 final var number
= address
.getNumber().get();
832 if (registeredUsers
.containsKey(number
)) {
833 final var newAddress
= resolveSignalServiceAddress(resolveRecipientTrusted(new SignalServiceAddress(
834 registeredUsers
.get(number
),
836 signalServiceAddresses
.add(newAddress
);
838 signalServiceAddresses
.add(address
);
842 return signalServiceAddresses
.stream().map(this::resolveRecipient
).collect(Collectors
.toSet());
845 private RecipientId
refreshRegisteredUser(RecipientId recipientId
) throws IOException
{
846 final var address
= resolveSignalServiceAddress(recipientId
);
847 if (!address
.getNumber().isPresent()) {
850 final var number
= address
.getNumber().get();
851 final var uuidMap
= getRegisteredUsers(Set
.of(number
));
852 return resolveRecipientTrusted(new SignalServiceAddress(uuidMap
.getOrDefault(number
, null), number
));
855 private Map
<String
, UUID
> getRegisteredUsers(final Set
<String
> numbers
) throws IOException
{
856 final Map
<String
, UUID
> registeredUsers
;
858 registeredUsers
= dependencies
.getAccountManager()
859 .getRegisteredUsers(ServiceConfig
.getIasKeyStore(),
861 serviceEnvironmentConfig
.getCdsMrenclave());
862 } catch (Quote
.InvalidQuoteFormatException
| UnauthenticatedQuoteException
| SignatureException
| UnauthenticatedResponseException
| InvalidKeyException e
) {
863 throw new IOException(e
);
866 // Store numbers as recipients so we have the number/uuid association
867 registeredUsers
.forEach((number
, uuid
) -> resolveRecipientTrusted(new SignalServiceAddress(uuid
, number
)));
869 return registeredUsers
;
872 public void sendTypingMessage(
873 TypingAction action
, Set
<RecipientIdentifier
> recipients
874 ) throws IOException
, UntrustedIdentityException
, NotAGroupMemberException
, GroupNotFoundException
, GroupSendingNotAllowedException
{
875 sendTypingMessage(action
.toSignalService(), recipients
);
878 private void handleEndSession(RecipientId recipientId
) {
879 account
.getSessionStore().deleteAllSessions(recipientId
);
882 private List
<HandleAction
> handleSignalServiceDataMessage(
883 SignalServiceDataMessage message
,
885 SignalServiceAddress source
,
886 SignalServiceAddress destination
,
887 boolean ignoreAttachments
889 var actions
= new ArrayList
<HandleAction
>();
890 if (message
.getGroupContext().isPresent()) {
891 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
892 var groupInfo
= message
.getGroupContext().get().getGroupV1().get();
893 var groupId
= GroupId
.v1(groupInfo
.getGroupId());
894 var group
= getGroup(groupId
);
895 if (group
== null || group
instanceof GroupInfoV1
) {
896 var groupV1
= (GroupInfoV1
) group
;
897 switch (groupInfo
.getType()) {
899 if (groupV1
== null) {
900 groupV1
= new GroupInfoV1(groupId
);
903 if (groupInfo
.getAvatar().isPresent()) {
904 var avatar
= groupInfo
.getAvatar().get();
905 groupHelper
.downloadGroupAvatar(groupV1
.getGroupId(), avatar
);
908 if (groupInfo
.getName().isPresent()) {
909 groupV1
.name
= groupInfo
.getName().get();
912 if (groupInfo
.getMembers().isPresent()) {
913 groupV1
.addMembers(groupInfo
.getMembers()
916 .map(this::resolveRecipient
)
917 .collect(Collectors
.toSet()));
920 account
.getGroupStore().updateGroup(groupV1
);
924 if (groupV1
== null && !isSync
) {
925 actions
.add(new SendGroupInfoRequestAction(source
, groupId
));
929 if (groupV1
!= null) {
930 groupV1
.removeMember(resolveRecipient(source
));
931 account
.getGroupStore().updateGroup(groupV1
);
936 if (groupV1
!= null && !isSync
) {
937 actions
.add(new SendGroupInfoAction(source
, groupV1
.getGroupId()));
942 // Received a group v1 message for a v2 group
945 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
946 final var groupContext
= message
.getGroupContext().get().getGroupV2().get();
947 final var groupMasterKey
= groupContext
.getMasterKey();
949 groupHelper
.getOrMigrateGroup(groupMasterKey
,
950 groupContext
.getRevision(),
951 groupContext
.hasSignedGroupChange() ? groupContext
.getSignedGroupChange() : null);
955 final var conversationPartnerAddress
= isSync ? destination
: source
;
956 if (conversationPartnerAddress
!= null && message
.isEndSession()) {
957 handleEndSession(resolveRecipient(conversationPartnerAddress
));
959 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
960 if (message
.getGroupContext().isPresent()) {
961 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
962 var groupInfo
= message
.getGroupContext().get().getGroupV1().get();
963 var group
= account
.getGroupStore().getOrCreateGroupV1(GroupId
.v1(groupInfo
.getGroupId()));
965 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
966 group
.messageExpirationTime
= message
.getExpiresInSeconds();
967 account
.getGroupStore().updateGroup(group
);
970 } else if (message
.getGroupContext().get().getGroupV2().isPresent()) {
971 // disappearing message timer already stored in the DecryptedGroup
973 } else if (conversationPartnerAddress
!= null) {
974 setExpirationTimer(resolveRecipient(conversationPartnerAddress
), message
.getExpiresInSeconds());
977 if (!ignoreAttachments
) {
978 if (message
.getAttachments().isPresent()) {
979 for (var attachment
: message
.getAttachments().get()) {
980 attachmentHelper
.downloadAttachment(attachment
);
983 if (message
.getSharedContacts().isPresent()) {
984 for (var contact
: message
.getSharedContacts().get()) {
985 if (contact
.getAvatar().isPresent()) {
986 attachmentHelper
.downloadAttachment(contact
.getAvatar().get().getAttachment());
990 if (message
.getPreviews().isPresent()) {
991 final var previews
= message
.getPreviews().get();
992 for (var preview
: previews
) {
993 if (preview
.getImage().isPresent()) {
994 attachmentHelper
.downloadAttachment(preview
.getImage().get());
998 if (message
.getQuote().isPresent()) {
999 final var quote
= message
.getQuote().get();
1001 for (var quotedAttachment
: quote
.getAttachments()) {
1002 final var thumbnail
= quotedAttachment
.getThumbnail();
1003 if (thumbnail
!= null) {
1004 attachmentHelper
.downloadAttachment(thumbnail
);
1009 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1010 final ProfileKey profileKey
;
1012 profileKey
= new ProfileKey(message
.getProfileKey().get());
1013 } catch (InvalidInputException e
) {
1014 throw new AssertionError(e
);
1016 if (source
.matches(account
.getSelfAddress())) {
1017 this.account
.setProfileKey(profileKey
);
1019 this.account
.getProfileStore().storeProfileKey(resolveRecipient(source
), profileKey
);
1021 if (message
.getSticker().isPresent()) {
1022 final var messageSticker
= message
.getSticker().get();
1023 final var stickerPackId
= StickerPackId
.deserialize(messageSticker
.getPackId());
1024 var sticker
= account
.getStickerStore().getSticker(stickerPackId
);
1025 if (sticker
== null) {
1026 sticker
= new Sticker(stickerPackId
, messageSticker
.getPackKey());
1027 account
.getStickerStore().updateSticker(sticker
);
1029 enqueueJob(new RetrieveStickerPackJob(stickerPackId
, messageSticker
.getPackKey()));
1034 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1035 Set
<HandleAction
> queuedActions
= new HashSet
<>();
1036 for (var cachedMessage
: account
.getMessageCache().getCachedMessages()) {
1037 var actions
= retryFailedReceivedMessage(handler
, ignoreAttachments
, cachedMessage
);
1038 if (actions
!= null) {
1039 queuedActions
.addAll(actions
);
1042 handleQueuedActions(queuedActions
);
1045 private List
<HandleAction
> retryFailedReceivedMessage(
1046 final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final CachedMessage cachedMessage
1048 var envelope
= cachedMessage
.loadEnvelope();
1049 if (envelope
== null) {
1052 SignalServiceContent content
= null;
1053 List
<HandleAction
> actions
= null;
1054 if (!envelope
.isReceipt()) {
1056 content
= dependencies
.getCipher().decrypt(envelope
);
1057 } catch (ProtocolUntrustedIdentityException e
) {
1058 if (!envelope
.hasSource()) {
1059 final var identifier
= e
.getSender();
1060 final var recipientId
= resolveRecipient(identifier
);
1062 account
.getMessageCache().replaceSender(cachedMessage
, recipientId
);
1063 } catch (IOException ioException
) {
1064 logger
.warn("Failed to move cached message to recipient folder: {}", ioException
.getMessage());
1068 } catch (Exception er
) {
1069 // All other errors are not recoverable, so delete the cached message
1070 cachedMessage
.delete();
1073 actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1075 handler
.handleMessage(envelope
, content
, null);
1076 cachedMessage
.delete();
1080 public void receiveMessages(
1083 boolean returnOnTimeout
,
1084 boolean ignoreAttachments
,
1085 ReceiveMessageHandler handler
1086 ) throws IOException
{
1087 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1089 Set
<HandleAction
> queuedActions
= new HashSet
<>();
1091 final var signalWebSocket
= dependencies
.getSignalWebSocket();
1092 signalWebSocket
.connect();
1094 var hasCaughtUpWithOldMessages
= false;
1096 while (!Thread
.interrupted()) {
1097 SignalServiceEnvelope envelope
;
1098 SignalServiceContent content
= null;
1099 Exception exception
= null;
1100 final CachedMessage
[] cachedMessage
= {null};
1101 account
.setLastReceiveTimestamp(System
.currentTimeMillis());
1102 logger
.debug("Checking for new message from server");
1104 var result
= signalWebSocket
.readOrEmpty(unit
.toMillis(timeout
), envelope1
-> {
1105 final var recipientId
= envelope1
.hasSource()
1106 ?
resolveRecipient(envelope1
.getSourceIdentifier())
1108 // store message on disk, before acknowledging receipt to the server
1109 cachedMessage
[0] = account
.getMessageCache().cacheMessage(envelope1
, recipientId
);
1111 logger
.debug("New message received from server");
1112 if (result
.isPresent()) {
1113 envelope
= result
.get();
1115 // Received indicator that server queue is empty
1116 hasCaughtUpWithOldMessages
= true;
1118 handleQueuedActions(queuedActions
);
1119 queuedActions
.clear();
1121 // Continue to wait another timeout for new messages
1124 } catch (AssertionError e
) {
1125 if (e
.getCause() instanceof InterruptedException
) {
1126 Thread
.currentThread().interrupt();
1131 } catch (WebSocketUnavailableException e
) {
1132 logger
.debug("Pipe unexpectedly unavailable, connecting");
1133 signalWebSocket
.connect();
1135 } catch (TimeoutException e
) {
1136 if (returnOnTimeout
) return;
1140 if (envelope
.hasSource()) {
1141 // Store uuid if we don't have it already
1142 // address/uuid in envelope is sent by server
1143 resolveRecipientTrusted(envelope
.getSourceAddress());
1145 if (!envelope
.isReceipt()) {
1147 content
= dependencies
.getCipher().decrypt(envelope
);
1148 } catch (Exception e
) {
1151 if (!envelope
.hasSource() && content
!= null) {
1152 // Store uuid if we don't have it already
1153 // address/uuid is validated by unidentified sender certificate
1154 resolveRecipientTrusted(content
.getSender());
1156 var actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1157 if (exception
instanceof ProtocolInvalidMessageException
) {
1158 final var sender
= resolveRecipient(((ProtocolInvalidMessageException
) exception
).getSender());
1159 logger
.debug("Received invalid message, queuing renew session action.");
1160 actions
.add(new RenewSessionAction(sender
));
1162 if (hasCaughtUpWithOldMessages
) {
1163 for (var action
: actions
) {
1165 action
.execute(this);
1166 } catch (Throwable e
) {
1167 if (e
instanceof AssertionError
&& e
.getCause() instanceof InterruptedException
) {
1168 Thread
.currentThread().interrupt();
1170 logger
.warn("Message action failed.", e
);
1174 queuedActions
.addAll(actions
);
1177 final var notAllowedToSendToGroup
= isNotAllowedToSendToGroup(envelope
, content
);
1178 if (isMessageBlocked(envelope
, content
)) {
1179 logger
.info("Ignoring a message from blocked user/group: {}", envelope
.getTimestamp());
1180 } else if (notAllowedToSendToGroup
) {
1181 logger
.info("Ignoring a group message from an unauthorized sender (no member or admin): {} {}",
1182 (envelope
.hasSource() ? envelope
.getSourceAddress() : content
.getSender()).getIdentifier(),
1183 envelope
.getTimestamp());
1185 handler
.handleMessage(envelope
, content
, exception
);
1187 if (cachedMessage
[0] != null) {
1188 if (exception
instanceof ProtocolUntrustedIdentityException
) {
1189 final var identifier
= ((ProtocolUntrustedIdentityException
) exception
).getSender();
1190 final var recipientId
= resolveRecipient(identifier
);
1191 queuedActions
.add(new RetrieveProfileAction(recipientId
));
1192 if (!envelope
.hasSource()) {
1194 cachedMessage
[0] = account
.getMessageCache().replaceSender(cachedMessage
[0], recipientId
);
1195 } catch (IOException ioException
) {
1196 logger
.warn("Failed to move cached message to recipient folder: {}",
1197 ioException
.getMessage());
1201 cachedMessage
[0].delete();
1205 handleQueuedActions(queuedActions
);
1208 private void handleQueuedActions(final Set
<HandleAction
> queuedActions
) {
1209 for (var action
: queuedActions
) {
1211 action
.execute(this);
1212 } catch (Throwable e
) {
1213 if (e
instanceof AssertionError
&& e
.getCause() instanceof InterruptedException
) {
1214 Thread
.currentThread().interrupt();
1216 logger
.warn("Message action failed.", e
);
1221 private boolean isMessageBlocked(
1222 SignalServiceEnvelope envelope
, SignalServiceContent content
1224 SignalServiceAddress source
;
1225 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1226 source
= envelope
.getSourceAddress();
1227 } else if (content
!= null) {
1228 source
= content
.getSender();
1232 final var recipientId
= resolveRecipient(source
);
1233 if (isContactBlocked(recipientId
)) {
1237 if (content
!= null && content
.getDataMessage().isPresent()) {
1238 var message
= content
.getDataMessage().get();
1239 if (message
.getGroupContext().isPresent()) {
1240 var groupId
= GroupUtils
.getGroupId(message
.getGroupContext().get());
1241 var group
= getGroup(groupId
);
1242 if (group
!= null && group
.isBlocked()) {
1250 public boolean isContactBlocked(final RecipientIdentifier
.Single recipient
) {
1251 final var recipientId
= resolveRecipient(recipient
);
1252 return isContactBlocked(recipientId
);
1255 private boolean isContactBlocked(final RecipientId recipientId
) {
1256 var sourceContact
= account
.getContactStore().getContact(recipientId
);
1257 return sourceContact
!= null && sourceContact
.isBlocked();
1260 private boolean isNotAllowedToSendToGroup(
1261 SignalServiceEnvelope envelope
, SignalServiceContent content
1263 SignalServiceAddress source
;
1264 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1265 source
= envelope
.getSourceAddress();
1266 } else if (content
!= null) {
1267 source
= content
.getSender();
1272 if (content
== null || !content
.getDataMessage().isPresent()) {
1276 var message
= content
.getDataMessage().get();
1277 if (!message
.getGroupContext().isPresent()) {
1281 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1282 var groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1283 if (groupInfo
.getType() == SignalServiceGroup
.Type
.QUIT
) {
1288 var groupId
= GroupUtils
.getGroupId(message
.getGroupContext().get());
1289 var group
= getGroup(groupId
);
1290 if (group
== null) {
1294 final var recipientId
= resolveRecipient(source
);
1295 if (!group
.isMember(recipientId
)) {
1299 if (group
.isAnnouncementGroup() && !group
.isAdmin(recipientId
)) {
1300 return message
.getBody().isPresent()
1301 || message
.getAttachments().isPresent()
1302 || message
.getQuote()
1304 || message
.getPreviews().isPresent()
1305 || message
.getMentions().isPresent()
1306 || message
.getSticker().isPresent();
1311 private List
<HandleAction
> handleMessage(
1312 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
1314 var actions
= new ArrayList
<HandleAction
>();
1315 if (content
!= null) {
1316 final SignalServiceAddress sender
;
1317 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1318 sender
= envelope
.getSourceAddress();
1320 sender
= content
.getSender();
1323 if (content
.getDataMessage().isPresent()) {
1324 var message
= content
.getDataMessage().get();
1326 if (content
.isNeedsReceipt()) {
1327 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1330 actions
.addAll(handleSignalServiceDataMessage(message
,
1333 account
.getSelfAddress(),
1334 ignoreAttachments
));
1336 if (content
.getSyncMessage().isPresent()) {
1337 account
.setMultiDevice(true);
1338 var syncMessage
= content
.getSyncMessage().get();
1339 if (syncMessage
.getSent().isPresent()) {
1340 var message
= syncMessage
.getSent().get();
1341 final var destination
= message
.getDestination().orNull();
1342 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
1346 ignoreAttachments
));
1348 if (syncMessage
.getRequest().isPresent() && account
.isMasterDevice()) {
1349 var rm
= syncMessage
.getRequest().get();
1350 if (rm
.isContactsRequest()) {
1351 actions
.add(SendSyncContactsAction
.create());
1353 if (rm
.isGroupsRequest()) {
1354 actions
.add(SendSyncGroupsAction
.create());
1356 if (rm
.isBlockedListRequest()) {
1357 actions
.add(SendSyncBlockedListAction
.create());
1359 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
1361 if (syncMessage
.getGroups().isPresent()) {
1363 final var groupsMessage
= syncMessage
.getGroups().get();
1364 attachmentHelper
.retrieveAttachment(groupsMessage
, syncHelper
::handleSyncDeviceGroups
);
1365 } catch (Exception e
) {
1366 logger
.warn("Failed to handle received sync groups, ignoring: {}", e
.getMessage());
1369 if (syncMessage
.getBlockedList().isPresent()) {
1370 final var blockedListMessage
= syncMessage
.getBlockedList().get();
1371 for (var address
: blockedListMessage
.getAddresses()) {
1372 setContactBlocked(resolveRecipient(address
), true);
1374 for (var groupId
: blockedListMessage
.getGroupIds()
1376 .map(GroupId
::unknownVersion
)
1377 .collect(Collectors
.toSet())) {
1379 setGroupBlocked(groupId
, true);
1380 } catch (GroupNotFoundException e
) {
1381 logger
.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
1382 groupId
.toBase64());
1386 if (syncMessage
.getContacts().isPresent()) {
1388 final var contactsMessage
= syncMessage
.getContacts().get();
1389 attachmentHelper
.retrieveAttachment(contactsMessage
.getContactsStream(),
1390 syncHelper
::handleSyncDeviceContacts
);
1391 } catch (Exception e
) {
1392 logger
.warn("Failed to handle received sync contacts, ignoring: {}", e
.getMessage());
1395 if (syncMessage
.getVerified().isPresent()) {
1396 final var verifiedMessage
= syncMessage
.getVerified().get();
1397 account
.getIdentityKeyStore()
1398 .setIdentityTrustLevel(resolveRecipientTrusted(verifiedMessage
.getDestination()),
1399 verifiedMessage
.getIdentityKey(),
1400 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1402 if (syncMessage
.getStickerPackOperations().isPresent()) {
1403 final var stickerPackOperationMessages
= syncMessage
.getStickerPackOperations().get();
1404 for (var m
: stickerPackOperationMessages
) {
1405 if (!m
.getPackId().isPresent()) {
1408 final var stickerPackId
= StickerPackId
.deserialize(m
.getPackId().get());
1409 final var installed
= !m
.getType().isPresent()
1410 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
;
1412 var sticker
= account
.getStickerStore().getSticker(stickerPackId
);
1413 if (m
.getPackKey().isPresent()) {
1414 if (sticker
== null) {
1415 sticker
= new Sticker(stickerPackId
, m
.getPackKey().get());
1418 enqueueJob(new RetrieveStickerPackJob(stickerPackId
, m
.getPackKey().get()));
1422 if (sticker
!= null) {
1423 sticker
.setInstalled(installed
);
1424 account
.getStickerStore().updateSticker(sticker
);
1428 if (syncMessage
.getFetchType().isPresent()) {
1429 switch (syncMessage
.getFetchType().get()) {
1431 actions
.add(new RetrieveProfileAction(account
.getSelfRecipientId()));
1432 case STORAGE_MANIFEST
:
1436 if (syncMessage
.getKeys().isPresent()) {
1437 final var keysMessage
= syncMessage
.getKeys().get();
1438 if (keysMessage
.getStorageService().isPresent()) {
1439 final var storageKey
= keysMessage
.getStorageService().get();
1440 account
.setStorageKey(storageKey
);
1443 if (syncMessage
.getConfiguration().isPresent()) {
1451 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1452 return attachmentStore
.getAttachmentFile(attachmentId
);
1455 void sendGroups() throws IOException
{
1456 syncHelper
.sendGroups();
1459 public void sendContacts() throws IOException
{
1460 syncHelper
.sendContacts();
1463 void sendBlockedList() throws IOException
{
1464 syncHelper
.sendBlockedList();
1467 public List
<Pair
<RecipientId
, Contact
>> getContacts() {
1468 return account
.getContactStore().getContacts();
1471 public String
getContactOrProfileName(RecipientIdentifier
.Single recipientIdentifier
) {
1472 final var recipientId
= resolveRecipient(recipientIdentifier
);
1474 final var contact
= account
.getRecipientStore().getContact(recipientId
);
1475 if (contact
!= null && !Util
.isEmpty(contact
.getName())) {
1476 return contact
.getName();
1479 final var profile
= getRecipientProfile(recipientId
);
1480 if (profile
!= null) {
1481 return profile
.getDisplayName();
1487 public GroupInfo
getGroup(GroupId groupId
) {
1488 return groupHelper
.getGroup(groupId
);
1491 public List
<IdentityInfo
> getIdentities() {
1492 return account
.getIdentityKeyStore().getIdentities();
1495 public List
<IdentityInfo
> getIdentities(RecipientIdentifier
.Single recipient
) {
1496 final var identity
= account
.getIdentityKeyStore().getIdentity(resolveRecipient(recipient
));
1497 return identity
== null ? List
.of() : List
.of(identity
);
1501 * Trust this the identity with this fingerprint
1503 * @param recipient username of the identity
1504 * @param fingerprint Fingerprint
1506 public boolean trustIdentityVerified(RecipientIdentifier
.Single recipient
, byte[] fingerprint
) {
1507 var recipientId
= resolveRecipient(recipient
);
1508 return trustIdentity(recipientId
,
1509 identityKey
-> Arrays
.equals(identityKey
.serialize(), fingerprint
),
1510 TrustLevel
.TRUSTED_VERIFIED
);
1514 * Trust this the identity with this safety number
1516 * @param recipient username of the identity
1517 * @param safetyNumber Safety number
1519 public boolean trustIdentityVerifiedSafetyNumber(RecipientIdentifier
.Single recipient
, String safetyNumber
) {
1520 var recipientId
= resolveRecipient(recipient
);
1521 var address
= account
.getRecipientStore().resolveServiceAddress(recipientId
);
1522 return trustIdentity(recipientId
,
1523 identityKey
-> safetyNumber
.equals(computeSafetyNumber(address
, identityKey
)),
1524 TrustLevel
.TRUSTED_VERIFIED
);
1528 * Trust this the identity with this scannable safety number
1530 * @param recipient username of the identity
1531 * @param safetyNumber Scannable safety number
1533 public boolean trustIdentityVerifiedSafetyNumber(RecipientIdentifier
.Single recipient
, byte[] safetyNumber
) {
1534 var recipientId
= resolveRecipient(recipient
);
1535 var address
= account
.getRecipientStore().resolveServiceAddress(recipientId
);
1536 return trustIdentity(recipientId
, identityKey
-> {
1537 final var fingerprint
= computeSafetyNumberFingerprint(address
, identityKey
);
1539 return fingerprint
!= null && fingerprint
.getScannableFingerprint().compareTo(safetyNumber
);
1540 } catch (FingerprintVersionMismatchException
| FingerprintParsingException e
) {
1543 }, TrustLevel
.TRUSTED_VERIFIED
);
1547 * Trust all keys of this identity without verification
1549 * @param recipient username of the identity
1551 public boolean trustIdentityAllKeys(RecipientIdentifier
.Single recipient
) {
1552 var recipientId
= resolveRecipient(recipient
);
1553 return trustIdentity(recipientId
, identityKey
-> true, TrustLevel
.TRUSTED_UNVERIFIED
);
1556 private boolean trustIdentity(
1557 RecipientId recipientId
, Function
<IdentityKey
, Boolean
> verifier
, TrustLevel trustLevel
1559 var identity
= account
.getIdentityKeyStore().getIdentity(recipientId
);
1560 if (identity
== null) {
1564 if (!verifier
.apply(identity
.getIdentityKey())) {
1568 account
.getIdentityKeyStore().setIdentityTrustLevel(recipientId
, identity
.getIdentityKey(), trustLevel
);
1570 var address
= account
.getRecipientStore().resolveServiceAddress(recipientId
);
1571 syncHelper
.sendVerifiedMessage(address
, identity
.getIdentityKey(), trustLevel
);
1572 } catch (IOException e
) {
1573 logger
.warn("Failed to send verification sync message: {}", e
.getMessage());
1579 private void handleIdentityFailure(
1580 final RecipientId recipientId
, final SendMessageResult
.IdentityFailure identityFailure
1582 final var identityKey
= identityFailure
.getIdentityKey();
1583 if (identityKey
!= null) {
1584 final var newIdentity
= account
.getIdentityKeyStore().saveIdentity(recipientId
, identityKey
, new Date());
1586 account
.getSessionStore().archiveSessions(recipientId
);
1589 // Retrieve profile to get the current identity key from the server
1590 refreshRecipientProfile(recipientId
);
1594 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
1595 final Fingerprint fingerprint
= computeSafetyNumberFingerprint(theirAddress
, theirIdentityKey
);
1596 return fingerprint
== null ?
null : fingerprint
.getDisplayableFingerprint().getDisplayText();
1599 public byte[] computeSafetyNumberForScanning(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
1600 final Fingerprint fingerprint
= computeSafetyNumberFingerprint(theirAddress
, theirIdentityKey
);
1601 return fingerprint
== null ?
null : fingerprint
.getScannableFingerprint().getSerialized();
1604 private Fingerprint
computeSafetyNumberFingerprint(
1605 final SignalServiceAddress theirAddress
, final IdentityKey theirIdentityKey
1607 return Utils
.computeSafetyNumber(capabilities
.isUuid(),
1608 account
.getSelfAddress(),
1609 getIdentityKeyPair().getPublicKey(),
1615 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
1616 var address
= Utils
.getSignalServiceAddressFromIdentifier(identifier
);
1618 return resolveSignalServiceAddress(address
);
1622 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
1623 if (address
.matches(account
.getSelfAddress())) {
1624 return account
.getSelfAddress();
1627 return account
.getRecipientStore().resolveServiceAddress(address
);
1630 public SignalServiceAddress
resolveSignalServiceAddress(RecipientId recipientId
) {
1631 return account
.getRecipientStore().resolveServiceAddress(recipientId
);
1634 private String
canonicalizePhoneNumber(final String number
) throws InvalidNumberException
{
1635 return PhoneNumberFormatter
.formatNumber(number
, account
.getUsername());
1638 private RecipientId
resolveRecipient(final String identifier
) {
1639 var address
= Utils
.getSignalServiceAddressFromIdentifier(identifier
);
1641 return resolveRecipient(address
);
1644 private RecipientId
resolveRecipient(final RecipientIdentifier
.Single recipient
) {
1645 final SignalServiceAddress address
;
1646 if (recipient
instanceof RecipientIdentifier
.Uuid
) {
1647 address
= new SignalServiceAddress(((RecipientIdentifier
.Uuid
) recipient
).uuid
, null);
1649 address
= new SignalServiceAddress(null, ((RecipientIdentifier
.Number
) recipient
).number
);
1652 return resolveRecipient(address
);
1655 public RecipientId
resolveRecipient(SignalServiceAddress address
) {
1656 return account
.getRecipientStore().resolveRecipient(address
);
1659 private RecipientId
resolveRecipientTrusted(SignalServiceAddress address
) {
1660 return account
.getRecipientStore().resolveRecipientTrusted(address
);
1663 private void enqueueJob(Job job
) {
1664 var context
= new Context(account
,
1665 dependencies
.getAccountManager(),
1666 dependencies
.getMessageReceiver(),
1672 public void close() throws IOException
{
1676 void close(boolean closeAccount
) throws IOException
{
1677 executor
.shutdown();
1679 dependencies
.getSignalWebSocket().disconnect();
1681 if (closeAccount
&& account
!= null) {
1687 public interface ReceiveMessageHandler
{
1689 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);