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 com
.fasterxml
.jackson
.databind
.ObjectMapper
;
21 import org
.asamk
.signal
.manager
.groups
.GroupId
;
22 import org
.asamk
.signal
.manager
.groups
.GroupIdV1
;
23 import org
.asamk
.signal
.manager
.groups
.GroupIdV2
;
24 import org
.asamk
.signal
.manager
.groups
.GroupInviteLinkUrl
;
25 import org
.asamk
.signal
.manager
.groups
.GroupNotFoundException
;
26 import org
.asamk
.signal
.manager
.groups
.GroupUtils
;
27 import org
.asamk
.signal
.manager
.groups
.NotAGroupMemberException
;
28 import org
.asamk
.signal
.manager
.helper
.GroupHelper
;
29 import org
.asamk
.signal
.manager
.helper
.PinHelper
;
30 import org
.asamk
.signal
.manager
.helper
.ProfileHelper
;
31 import org
.asamk
.signal
.manager
.helper
.UnidentifiedAccessHelper
;
32 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
33 import org
.asamk
.signal
.manager
.storage
.contacts
.ContactInfo
;
34 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfo
;
35 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfoV1
;
36 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfoV2
;
37 import org
.asamk
.signal
.manager
.storage
.profiles
.SignalProfile
;
38 import org
.asamk
.signal
.manager
.storage
.profiles
.SignalProfileEntry
;
39 import org
.asamk
.signal
.manager
.storage
.protocol
.IdentityInfo
;
40 import org
.asamk
.signal
.manager
.storage
.stickers
.Sticker
;
41 import org
.asamk
.signal
.manager
.util
.AttachmentUtils
;
42 import org
.asamk
.signal
.manager
.util
.IOUtils
;
43 import org
.asamk
.signal
.manager
.util
.KeyUtils
;
44 import org
.asamk
.signal
.manager
.util
.MessageCacheUtils
;
45 import org
.asamk
.signal
.manager
.util
.Utils
;
46 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
47 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
48 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
49 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
50 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
51 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
52 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
53 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
54 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
55 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
56 import org
.signal
.libsignal
.metadata
.SelfSendException
;
57 import org
.signal
.libsignal
.metadata
.certificate
.CertificateValidator
;
58 import org
.signal
.storageservice
.protos
.groups
.GroupChange
;
59 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroup
;
60 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroupJoinInfo
;
61 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedMember
;
62 import org
.signal
.zkgroup
.InvalidInputException
;
63 import org
.signal
.zkgroup
.VerificationFailedException
;
64 import org
.signal
.zkgroup
.auth
.AuthCredentialResponse
;
65 import org
.signal
.zkgroup
.groups
.GroupMasterKey
;
66 import org
.signal
.zkgroup
.groups
.GroupSecretParams
;
67 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
68 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
69 import org
.signal
.zkgroup
.profiles
.ProfileKeyCredential
;
70 import org
.slf4j
.Logger
;
71 import org
.slf4j
.LoggerFactory
;
72 import org
.whispersystems
.libsignal
.IdentityKey
;
73 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
74 import org
.whispersystems
.libsignal
.InvalidKeyException
;
75 import org
.whispersystems
.libsignal
.InvalidMessageException
;
76 import org
.whispersystems
.libsignal
.InvalidVersionException
;
77 import org
.whispersystems
.libsignal
.ecc
.Curve
;
78 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
79 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
80 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
81 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
82 import org
.whispersystems
.libsignal
.util
.Medium
;
83 import org
.whispersystems
.libsignal
.util
.Pair
;
84 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
85 import org
.whispersystems
.signalservice
.api
.KeyBackupService
;
86 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
87 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
88 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
89 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
90 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
91 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
92 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
93 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
94 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
95 import org
.whispersystems
.signalservice
.api
.groupsv2
.ClientZkOperations
;
96 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
97 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Api
;
98 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2AuthorizationString
;
99 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
100 import org
.whispersystems
.signalservice
.api
.kbs
.MasterKey
;
101 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
102 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
103 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
104 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
105 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
106 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
107 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
108 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
109 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
110 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroupV2
;
111 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
112 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
113 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
114 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
115 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
116 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
117 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
118 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
119 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
120 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
121 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
122 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
123 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
124 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
125 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
126 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.StickerPackOperationMessage
;
127 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
128 import org
.whispersystems
.signalservice
.api
.profiles
.ProfileAndCredential
;
129 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
130 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
131 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
132 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
133 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
134 import org
.whispersystems
.signalservice
.api
.util
.PhoneNumberFormatter
;
135 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
136 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
137 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
138 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
139 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
140 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.Quote
;
141 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedQuoteException
;
142 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedResponseException
;
143 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
144 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
145 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
146 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
147 import org
.whispersystems
.util
.Base64
;
149 import java
.io
.Closeable
;
151 import java
.io
.FileInputStream
;
152 import java
.io
.FileNotFoundException
;
153 import java
.io
.FileOutputStream
;
154 import java
.io
.IOException
;
155 import java
.io
.InputStream
;
156 import java
.io
.OutputStream
;
158 import java
.net
.URISyntaxException
;
159 import java
.net
.URLEncoder
;
160 import java
.nio
.charset
.StandardCharsets
;
161 import java
.nio
.file
.Files
;
162 import java
.nio
.file
.Paths
;
163 import java
.nio
.file
.StandardCopyOption
;
164 import java
.security
.SignatureException
;
165 import java
.util
.ArrayList
;
166 import java
.util
.Arrays
;
167 import java
.util
.Collection
;
168 import java
.util
.Date
;
169 import java
.util
.HashMap
;
170 import java
.util
.HashSet
;
171 import java
.util
.List
;
172 import java
.util
.Map
;
173 import java
.util
.Objects
;
174 import java
.util
.Set
;
175 import java
.util
.UUID
;
176 import java
.util
.concurrent
.ExecutorService
;
177 import java
.util
.concurrent
.TimeUnit
;
178 import java
.util
.concurrent
.TimeoutException
;
179 import java
.util
.stream
.Collectors
;
180 import java
.util
.zip
.ZipEntry
;
181 import java
.util
.zip
.ZipFile
;
183 import static org
.asamk
.signal
.manager
.ServiceConfig
.CDS_MRENCLAVE
;
184 import static org
.asamk
.signal
.manager
.ServiceConfig
.capabilities
;
185 import static org
.asamk
.signal
.manager
.ServiceConfig
.getIasKeyStore
;
187 public class Manager
implements Closeable
{
189 final static Logger logger
= LoggerFactory
.getLogger(Manager
.class);
191 private final SleepTimer timer
= new UptimeSleepTimer();
192 private final CertificateValidator certificateValidator
= new CertificateValidator(ServiceConfig
.getUnidentifiedSenderTrustRoot());
194 private final SignalServiceConfiguration serviceConfiguration
;
195 private final String userAgent
;
197 private SignalAccount account
;
198 private final PathConfig pathConfig
;
199 private final SignalServiceAccountManager accountManager
;
200 private final GroupsV2Api groupsV2Api
;
201 private final GroupsV2Operations groupsV2Operations
;
202 private final SignalServiceMessageReceiver messageReceiver
;
203 private final ClientZkProfileOperations clientZkProfileOperations
;
205 private SignalServiceMessagePipe messagePipe
= null;
206 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
208 private final UnidentifiedAccessHelper unidentifiedAccessHelper
;
209 private final ProfileHelper profileHelper
;
210 private final GroupHelper groupHelper
;
211 private final PinHelper pinHelper
;
214 SignalAccount account
,
215 PathConfig pathConfig
,
216 SignalServiceConfiguration serviceConfiguration
,
219 this.account
= account
;
220 this.pathConfig
= pathConfig
;
221 this.serviceConfiguration
= serviceConfiguration
;
222 this.userAgent
= userAgent
;
223 this.groupsV2Operations
= capabilities
.isGv2() ?
new GroupsV2Operations(ClientZkOperations
.create(
224 serviceConfiguration
)) : null;
225 this.accountManager
= new SignalServiceAccountManager(serviceConfiguration
,
226 new DynamicCredentialsProvider(account
.getUuid(),
227 account
.getUsername(),
228 account
.getPassword(),
229 account
.getSignalingKey(),
230 account
.getDeviceId()),
234 this.groupsV2Api
= accountManager
.getGroupsV2Api();
235 final KeyBackupService keyBackupService
= ServiceConfig
.createKeyBackupService(accountManager
);
236 this.pinHelper
= new PinHelper(keyBackupService
);
237 this.clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(serviceConfiguration
)
238 .getProfileOperations() : null;
239 this.messageReceiver
= new SignalServiceMessageReceiver(serviceConfiguration
,
241 account
.getUsername(),
242 account
.getPassword(),
243 account
.getDeviceId(),
244 account
.getSignalingKey(),
248 clientZkProfileOperations
);
250 this.account
.setResolver(this::resolveSignalServiceAddress
);
252 this.unidentifiedAccessHelper
= new UnidentifiedAccessHelper(account
::getProfileKey
,
253 account
.getProfileStore()::getProfileKey
,
254 this::getRecipientProfile
,
255 this::getSenderCertificate
);
256 this.profileHelper
= new ProfileHelper(account
.getProfileStore()::getProfileKey
,
257 unidentifiedAccessHelper
::getAccessFor
,
258 unidentified
-> unidentified ?
getOrCreateUnidentifiedMessagePipe() : getOrCreateMessagePipe(),
259 () -> messageReceiver
);
260 this.groupHelper
= new GroupHelper(this::getRecipientProfileKeyCredential
,
261 this::getRecipientProfile
,
262 account
::getSelfAddress
,
265 this::getGroupAuthForToday
);
268 public String
getUsername() {
269 return account
.getUsername();
272 public SignalServiceAddress
getSelfAddress() {
273 return account
.getSelfAddress();
276 private IdentityKeyPair
getIdentityKeyPair() {
277 return account
.getSignalProtocolStore().getIdentityKeyPair();
280 public int getDeviceId() {
281 return account
.getDeviceId();
284 private File
getMessageCachePath() {
285 return SignalAccount
.getMessageCachePath(pathConfig
.getDataPath(), account
.getUsername());
288 private File
getMessageCachePath(String sender
) {
289 if (sender
== null || sender
.isEmpty()) {
290 return getMessageCachePath();
293 return new File(getMessageCachePath(), sender
.replace("/", "_"));
296 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
297 File cachePath
= getMessageCachePath(sender
);
298 IOUtils
.createPrivateDirectories(cachePath
);
299 return new File(cachePath
, now
+ "_" + timestamp
);
302 public static Manager
init(
303 String username
, File settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
304 ) throws IOException
, NotRegisteredException
{
305 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
307 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
308 throw new NotRegisteredException();
311 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
313 if (!account
.isRegistered()) {
314 throw new NotRegisteredException();
317 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
320 public void checkAccountState() throws IOException
{
321 if (account
.isRegistered()) {
322 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
326 if (account
.getUuid() == null) {
327 account
.setUuid(accountManager
.getOwnUuid());
330 updateAccountAttributes();
334 public boolean isRegistered() {
335 return account
.isRegistered();
339 * This is used for checking a set of phone numbers for registration on Signal
341 * @param numbers The set of phone number in question
342 * @return A map of numbers to booleans. True if registered, false otherwise. Should never be null
343 * @throws IOException if its unable to check if the users are registered
345 public Map
<String
, Boolean
> areUsersRegistered(Set
<String
> numbers
) throws IOException
{
346 // Note "contactDetails" has no optionals. It only gives us info on users who are registered
347 List
<ContactTokenDetails
> contactDetails
= this.accountManager
.getContacts(numbers
);
349 Set
<String
> registeredUsers
= contactDetails
.stream()
350 .map(ContactTokenDetails
::getNumber
)
351 .collect(Collectors
.toSet());
353 return numbers
.stream().collect(Collectors
.toMap(x
-> x
, registeredUsers
::contains
));
356 public void updateAccountAttributes() throws IOException
{
357 accountManager
.setAccountAttributes(account
.getSignalingKey(),
358 account
.getSignalProtocolStore().getLocalRegistrationId(),
360 // set legacy pin only if no KBS master key is set
361 account
.getPinMasterKey() == null ? account
.getRegistrationLockPin() : null,
362 account
.getPinMasterKey() == null ?
null : account
.getPinMasterKey().deriveRegistrationLock(),
363 account
.getSelfUnidentifiedAccessKey(),
364 account
.isUnrestrictedUnidentifiedAccess(),
366 account
.isDiscoverableByPhoneNumber());
369 public void setProfile(String name
, File avatar
) throws IOException
{
370 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
371 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
375 public void unregister() throws IOException
{
376 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
377 // If this is the master device, other users can't send messages to this number anymore.
378 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
379 accountManager
.setGcmId(Optional
.absent());
381 account
.setRegistered(false);
385 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
386 List
<DeviceInfo
> devices
= accountManager
.getDevices();
387 account
.setMultiDevice(devices
.size() > 1);
392 public void removeLinkedDevices(int deviceId
) throws IOException
{
393 accountManager
.removeDevice(deviceId
);
394 List
<DeviceInfo
> devices
= accountManager
.getDevices();
395 account
.setMultiDevice(devices
.size() > 1);
399 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
400 DeviceLinkInfo info
= DeviceLinkInfo
.parseDeviceLinkUri(linkUri
);
402 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
405 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
406 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
407 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
409 accountManager
.addDevice(deviceIdentifier
,
412 Optional
.of(account
.getProfileKey().serialize()),
414 account
.setMultiDevice(true);
418 private List
<PreKeyRecord
> generatePreKeys() {
419 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
421 final int offset
= account
.getPreKeyIdOffset();
422 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
423 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
424 ECKeyPair keyPair
= Curve
.generateKeyPair();
425 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
430 account
.addPreKeys(records
);
436 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
438 ECKeyPair keyPair
= Curve
.generateKeyPair();
439 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(),
440 keyPair
.getPublicKey().serialize());
441 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(),
442 System
.currentTimeMillis(),
446 account
.addSignedPreKey(record);
450 } catch (InvalidKeyException e
) {
451 throw new AssertionError(e
);
455 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
, UnauthenticatedResponseException
{
456 if (pin
.isPresent()) {
457 final MasterKey masterKey
= account
.getPinMasterKey() != null
458 ? account
.getPinMasterKey()
459 : KeyUtils
.createMasterKey();
461 pinHelper
.setRegistrationLockPin(pin
.get(), masterKey
);
463 account
.setRegistrationLockPin(pin
.get());
464 account
.setPinMasterKey(masterKey
);
466 // Remove legacy registration lock
467 accountManager
.removeRegistrationLockV1();
470 pinHelper
.removeRegistrationLockPin();
472 account
.setRegistrationLockPin(null);
473 account
.setPinMasterKey(null);
478 void refreshPreKeys() throws IOException
{
479 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
480 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
481 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
483 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
486 private SignalServiceMessagePipe
getOrCreateMessagePipe() {
487 if (messagePipe
== null) {
488 messagePipe
= messageReceiver
.createMessagePipe();
493 private SignalServiceMessagePipe
getOrCreateUnidentifiedMessagePipe() {
494 if (unidentifiedMessagePipe
== null) {
495 unidentifiedMessagePipe
= messageReceiver
.createUnidentifiedMessagePipe();
497 return unidentifiedMessagePipe
;
500 private SignalServiceMessageSender
createMessageSender() {
501 final ExecutorService executor
= null;
502 return new SignalServiceMessageSender(serviceConfiguration
,
504 account
.getUsername(),
505 account
.getPassword(),
506 account
.getDeviceId(),
507 account
.getSignalProtocolStore(),
509 account
.isMultiDevice(),
510 Optional
.fromNullable(messagePipe
),
511 Optional
.fromNullable(unidentifiedMessagePipe
),
513 clientZkProfileOperations
,
515 ServiceConfig
.MAX_ENVELOPE_SIZE
);
518 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
) throws IOException
{
519 return profileHelper
.retrieveProfileSync(address
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
522 private SignalProfile
getRecipientProfile(
523 SignalServiceAddress address
525 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
526 if (profileEntry
== null) {
529 long now
= new Date().getTime();
530 // Profiles are cache for 24h before retrieving them again
531 if (!profileEntry
.isRequestPending() && (
532 profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000
534 ProfileKey profileKey
= profileEntry
.getProfileKey();
535 profileEntry
.setRequestPending(true);
536 SignalProfile profile
;
538 profile
= retrieveRecipientProfile(address
, profileKey
);
539 } catch (IOException e
) {
540 logger
.warn("Failed to retrieve profile, ignoring: {}", e
.getMessage());
541 profileEntry
.setRequestPending(false);
544 profileEntry
.setRequestPending(false);
545 account
.getProfileStore()
546 .updateProfile(address
, profileKey
, now
, profile
, profileEntry
.getProfileKeyCredential());
549 return profileEntry
.getProfile();
552 private ProfileKeyCredential
getRecipientProfileKeyCredential(SignalServiceAddress address
) {
553 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
554 if (profileEntry
== null) {
557 if (profileEntry
.getProfileKeyCredential() == null) {
558 ProfileAndCredential profileAndCredential
;
560 profileAndCredential
= profileHelper
.retrieveProfileSync(address
,
561 SignalServiceProfile
.RequestType
.PROFILE_AND_CREDENTIAL
);
562 } catch (IOException e
) {
563 logger
.warn("Failed to retrieve profile key credential, ignoring: {}", e
.getMessage());
567 long now
= new Date().getTime();
568 final ProfileKeyCredential profileKeyCredential
= profileAndCredential
.getProfileKeyCredential().orNull();
569 final SignalProfile profile
= decryptProfile(address
,
570 profileEntry
.getProfileKey(),
571 profileAndCredential
.getProfile());
572 account
.getProfileStore()
573 .updateProfile(address
, profileEntry
.getProfileKey(), now
, profile
, profileKeyCredential
);
574 return profileKeyCredential
;
576 return profileEntry
.getProfileKeyCredential();
579 private SignalProfile
retrieveRecipientProfile(
580 SignalServiceAddress address
, ProfileKey profileKey
581 ) throws IOException
{
582 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
);
584 return decryptProfile(address
, profileKey
, encryptedProfile
);
587 private SignalProfile
decryptProfile(
588 final SignalServiceAddress address
, final ProfileKey profileKey
, final SignalServiceProfile encryptedProfile
590 File avatarFile
= null;
592 avatarFile
= encryptedProfile
.getAvatar() == null
594 : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
595 } catch (Throwable e
) {
596 logger
.warn("Failed to retrieve profile avatar, ignoring: {}", e
.getMessage());
599 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
603 name
= encryptedProfile
.getName() == null
605 : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName())));
606 } catch (IOException e
) {
609 String unidentifiedAccess
;
611 unidentifiedAccess
= encryptedProfile
.getUnidentifiedAccess() == null
612 || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess()))
614 : encryptedProfile
.getUnidentifiedAccess();
615 } catch (IOException e
) {
616 unidentifiedAccess
= null;
618 return new SignalProfile(encryptedProfile
.getIdentityKey(),
622 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
623 encryptedProfile
.getCapabilities());
624 } catch (InvalidCiphertextException e
) {
629 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(GroupId groupId
) throws IOException
{
630 File file
= getGroupAvatarFile(groupId
);
631 if (!file
.exists()) {
632 return Optional
.absent();
635 return Optional
.of(AttachmentUtils
.createAttachment(file
));
638 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
639 File file
= getContactAvatarFile(number
);
640 if (!file
.exists()) {
641 return Optional
.absent();
644 return Optional
.of(AttachmentUtils
.createAttachment(file
));
647 private GroupInfo
getGroupForSending(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
648 GroupInfo g
= getGroup(groupId
);
650 throw new GroupNotFoundException(groupId
);
652 if (!g
.isMember(account
.getSelfAddress())) {
653 throw new NotAGroupMemberException(groupId
, g
.getTitle());
658 private GroupInfo
getGroupForUpdating(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
659 GroupInfo g
= getGroup(groupId
);
661 throw new GroupNotFoundException(groupId
);
663 if (!g
.isMember(account
.getSelfAddress()) && !g
.isPendingMember(account
.getSelfAddress())) {
664 throw new NotAGroupMemberException(groupId
, g
.getTitle());
669 public List
<GroupInfo
> getGroups() {
670 return account
.getGroupStore().getGroups();
673 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
674 SignalServiceDataMessage
.Builder messageBuilder
, GroupId groupId
675 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
{
676 final GroupInfo g
= getGroupForSending(groupId
);
678 GroupUtils
.setGroupContext(messageBuilder
, g
);
679 messageBuilder
.withExpiration(g
.getMessageExpirationTime());
681 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
684 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
685 String messageText
, List
<String
> attachments
, GroupId groupId
686 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
687 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
688 .withBody(messageText
);
689 if (attachments
!= null) {
690 messageBuilder
.withAttachments(AttachmentUtils
.getSignalServiceAttachments(attachments
));
693 return sendGroupMessage(messageBuilder
, groupId
);
696 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessageReaction(
697 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, GroupId groupId
698 ) throws IOException
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
699 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
701 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
702 targetSentTimestamp
);
703 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
704 .withReaction(reaction
);
706 return sendGroupMessage(messageBuilder
, groupId
);
709 public Pair
<Long
, List
<SendMessageResult
>> sendQuitGroupMessage(GroupId groupId
) throws GroupNotFoundException
, IOException
, NotAGroupMemberException
{
711 SignalServiceDataMessage
.Builder messageBuilder
;
713 final GroupInfo g
= getGroupForUpdating(groupId
);
714 if (g
instanceof GroupInfoV1
) {
715 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
716 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
717 .withId(groupId
.serialize())
719 messageBuilder
= SignalServiceDataMessage
.newBuilder().asGroupMessage(group
);
720 groupInfoV1
.removeMember(account
.getSelfAddress());
721 account
.getGroupStore().updateGroup(groupInfoV1
);
723 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) g
;
724 final Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.leaveGroup(groupInfoV2
);
725 groupInfoV2
.setGroup(groupGroupChangePair
.first());
726 messageBuilder
= getGroupUpdateMessageBuilder(groupInfoV2
, groupGroupChangePair
.second().toByteArray());
727 account
.getGroupStore().updateGroup(groupInfoV2
);
730 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
733 private Pair
<GroupId
, List
<SendMessageResult
>> sendUpdateGroupMessage(
734 GroupId groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
735 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
737 SignalServiceDataMessage
.Builder messageBuilder
;
738 if (groupId
== null) {
740 GroupInfoV2 gv2
= groupHelper
.createGroupV2(name
, members
, avatarFile
);
742 GroupInfoV1 gv1
= new GroupInfoV1(GroupIdV1
.createRandom());
743 gv1
.addMembers(List
.of(account
.getSelfAddress()));
744 updateGroupV1(gv1
, name
, members
, avatarFile
);
745 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
748 messageBuilder
= getGroupUpdateMessageBuilder(gv2
, null);
752 GroupInfo group
= getGroupForUpdating(groupId
);
753 if (group
instanceof GroupInfoV2
) {
754 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) group
;
756 Pair
<Long
, List
<SendMessageResult
>> result
= null;
757 if (groupInfoV2
.isPendingMember(getSelfAddress())) {
758 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.acceptInvite(groupInfoV2
);
759 result
= sendUpdateGroupMessage(groupInfoV2
,
760 groupGroupChangePair
.first(),
761 groupGroupChangePair
.second());
764 if (members
!= null) {
765 final Set
<SignalServiceAddress
> newMembers
= new HashSet
<>(members
);
766 newMembers
.removeAll(group
.getMembers()
768 .map(this::resolveSignalServiceAddress
)
769 .collect(Collectors
.toSet()));
770 if (newMembers
.size() > 0) {
771 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
773 result
= sendUpdateGroupMessage(groupInfoV2
,
774 groupGroupChangePair
.first(),
775 groupGroupChangePair
.second());
778 if (result
== null || name
!= null || avatarFile
!= null) {
779 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
782 result
= sendUpdateGroupMessage(groupInfoV2
,
783 groupGroupChangePair
.first(),
784 groupGroupChangePair
.second());
787 return new Pair
<>(group
.getGroupId(), result
.second());
789 GroupInfoV1 gv1
= (GroupInfoV1
) group
;
790 updateGroupV1(gv1
, name
, members
, avatarFile
);
791 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
796 account
.getGroupStore().updateGroup(g
);
798 final Pair
<Long
, List
<SendMessageResult
>> result
= sendMessage(messageBuilder
,
799 g
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
800 return new Pair
<>(g
.getGroupId(), result
.second());
803 public Pair
<GroupId
, List
<SendMessageResult
>> joinGroup(
804 GroupInviteLinkUrl inviteLinkUrl
805 ) throws IOException
, GroupLinkNotActiveException
{
806 return sendJoinGroupMessage(inviteLinkUrl
);
809 private Pair
<GroupId
, List
<SendMessageResult
>> sendJoinGroupMessage(
810 GroupInviteLinkUrl inviteLinkUrl
811 ) throws IOException
, GroupLinkNotActiveException
{
812 final DecryptedGroupJoinInfo groupJoinInfo
= groupHelper
.getDecryptedGroupJoinInfo(inviteLinkUrl
.getGroupMasterKey(),
813 inviteLinkUrl
.getPassword());
814 final GroupChange groupChange
= groupHelper
.joinGroup(inviteLinkUrl
.getGroupMasterKey(),
815 inviteLinkUrl
.getPassword(),
817 final GroupInfoV2 group
= getOrMigrateGroup(inviteLinkUrl
.getGroupMasterKey(),
818 groupJoinInfo
.getRevision() + 1,
819 groupChange
.toByteArray());
821 if (group
.getGroup() == null) {
822 // Only requested member, can't send update to group members
823 return new Pair
<>(group
.getGroupId(), List
.of());
826 final Pair
<Long
, List
<SendMessageResult
>> result
= sendUpdateGroupMessage(group
, group
.getGroup(), groupChange
);
828 return new Pair
<>(group
.getGroupId(), result
.second());
831 private Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
832 GroupInfoV2 group
, DecryptedGroup newDecryptedGroup
, GroupChange groupChange
833 ) throws IOException
{
834 group
.setGroup(newDecryptedGroup
);
835 final SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(group
,
836 groupChange
.toByteArray());
837 account
.getGroupStore().updateGroup(group
);
838 return sendMessage(messageBuilder
, group
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
841 private void updateGroupV1(
844 final Collection
<SignalServiceAddress
> members
,
845 final String avatarFile
846 ) throws IOException
{
851 if (members
!= null) {
852 final Set
<String
> newE164Members
= new HashSet
<>();
853 for (SignalServiceAddress member
: members
) {
854 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
857 newE164Members
.add(member
.getNumber().get());
860 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
861 if (contacts
.size() != newE164Members
.size()) {
862 // Some of the new members are not registered on Signal
863 for (ContactTokenDetails contact
: contacts
) {
864 newE164Members
.remove(contact
.getNumber());
866 throw new IOException("Failed to add members "
867 + String
.join(", ", newE164Members
)
868 + " to group: Not registered on Signal");
871 g
.addMembers(members
);
874 if (avatarFile
!= null) {
875 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
876 File aFile
= getGroupAvatarFile(g
.getGroupId());
877 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
881 Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
882 GroupIdV1 groupId
, SignalServiceAddress recipient
883 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
885 GroupInfo group
= getGroupForSending(groupId
);
886 if (!(group
instanceof GroupInfoV1
)) {
887 throw new RuntimeException("Received an invalid group request for a v2 group!");
889 g
= (GroupInfoV1
) group
;
891 if (!g
.isMember(recipient
)) {
892 throw new NotAGroupMemberException(groupId
, g
.name
);
895 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
897 // Send group message only to the recipient who requested it
898 return sendMessage(messageBuilder
, List
.of(recipient
));
901 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV1 g
) throws AttachmentInvalidException
{
902 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
903 .withId(g
.getGroupId().serialize())
905 .withMembers(new ArrayList
<>(g
.getMembers()));
907 File aFile
= getGroupAvatarFile(g
.getGroupId());
908 if (aFile
.exists()) {
910 group
.withAvatar(AttachmentUtils
.createAttachment(aFile
));
911 } catch (IOException e
) {
912 throw new AttachmentInvalidException(aFile
.toString(), e
);
916 return SignalServiceDataMessage
.newBuilder()
917 .asGroupMessage(group
.build())
918 .withExpiration(g
.getMessageExpirationTime());
921 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV2 g
, byte[] signedGroupChange
) {
922 SignalServiceGroupV2
.Builder group
= SignalServiceGroupV2
.newBuilder(g
.getMasterKey())
923 .withRevision(g
.getGroup().getRevision())
924 .withSignedGroupChange(signedGroupChange
);
925 return SignalServiceDataMessage
.newBuilder()
926 .asGroupMessage(group
.build())
927 .withExpiration(g
.getMessageExpirationTime());
930 Pair
<Long
, List
<SendMessageResult
>> sendGroupInfoRequest(
931 GroupIdV1 groupId
, SignalServiceAddress recipient
932 ) throws IOException
{
933 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
934 .withId(groupId
.serialize());
936 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
937 .asGroupMessage(group
.build());
939 // Send group info request message to the recipient who sent us a message with this groupId
940 return sendMessage(messageBuilder
, List
.of(recipient
));
944 SignalServiceAddress remoteAddress
, long messageId
945 ) throws IOException
, UntrustedIdentityException
{
946 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
948 System
.currentTimeMillis());
950 createMessageSender().sendReceipt(remoteAddress
,
951 unidentifiedAccessHelper
.getAccessFor(remoteAddress
),
955 public Pair
<Long
, List
<SendMessageResult
>> sendMessage(
956 String messageText
, List
<String
> attachments
, List
<String
> recipients
957 ) throws IOException
, AttachmentInvalidException
, InvalidNumberException
{
958 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
959 .withBody(messageText
);
960 if (attachments
!= null) {
961 List
<SignalServiceAttachment
> attachmentStreams
= AttachmentUtils
.getSignalServiceAttachments(attachments
);
963 // Upload attachments here, so we only upload once even for multiple recipients
964 SignalServiceMessageSender messageSender
= createMessageSender();
965 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
966 for (SignalServiceAttachment attachment
: attachmentStreams
) {
967 if (attachment
.isStream()) {
968 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
969 } else if (attachment
.isPointer()) {
970 attachmentPointers
.add(attachment
.asPointer());
974 messageBuilder
.withAttachments(attachmentPointers
);
976 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
979 public Pair
<Long
, List
<SendMessageResult
>> sendMessageReaction(
980 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, List
<String
> recipients
981 ) throws IOException
, InvalidNumberException
{
982 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
984 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
985 targetSentTimestamp
);
986 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
987 .withReaction(reaction
);
988 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
991 public Pair
<Long
, List
<SendMessageResult
>> sendEndSessionMessage(List
<String
> recipients
) throws IOException
, InvalidNumberException
{
992 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().asEndSessionMessage();
994 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
996 return sendMessage(messageBuilder
, signalServiceAddresses
);
997 } catch (Exception e
) {
998 for (SignalServiceAddress address
: signalServiceAddresses
) {
999 handleEndSession(address
);
1006 public String
getContactName(String number
) throws InvalidNumberException
{
1007 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
1008 if (contact
== null) {
1011 return contact
.name
;
1015 public void setContactName(String number
, String name
) throws InvalidNumberException
{
1016 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1017 ContactInfo contact
= account
.getContactStore().getContact(address
);
1018 if (contact
== null) {
1019 contact
= new ContactInfo(address
);
1021 contact
.name
= name
;
1022 account
.getContactStore().updateContact(contact
);
1026 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
1027 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
1030 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
1031 ContactInfo contact
= account
.getContactStore().getContact(address
);
1032 if (contact
== null) {
1033 contact
= new ContactInfo(address
);
1035 contact
.blocked
= blocked
;
1036 account
.getContactStore().updateContact(contact
);
1040 public void setGroupBlocked(final GroupId groupId
, final boolean blocked
) throws GroupNotFoundException
{
1041 GroupInfo group
= getGroup(groupId
);
1042 if (group
== null) {
1043 throw new GroupNotFoundException(groupId
);
1046 group
.setBlocked(blocked
);
1047 account
.getGroupStore().updateGroup(group
);
1051 public Pair
<GroupId
, List
<SendMessageResult
>> updateGroup(
1052 GroupId groupId
, String name
, List
<String
> members
, String avatar
1053 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
1054 return sendUpdateGroupMessage(groupId
,
1056 members
== null ?
null : getSignalServiceAddresses(members
),
1061 * Change the expiration timer for a contact
1063 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
1064 ContactInfo contact
= account
.getContactStore().getContact(address
);
1065 contact
.messageExpirationTime
= messageExpirationTimer
;
1066 account
.getContactStore().updateContact(contact
);
1067 sendExpirationTimerUpdate(address
);
1071 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
1072 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1073 .asExpirationUpdate();
1074 sendMessage(messageBuilder
, List
.of(address
));
1078 * Change the expiration timer for a contact
1080 public void setExpirationTimer(
1081 String number
, int messageExpirationTimer
1082 ) throws IOException
, InvalidNumberException
{
1083 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1084 setExpirationTimer(address
, messageExpirationTimer
);
1088 * Change the expiration timer for a group
1090 public void setExpirationTimer(GroupId groupId
, int messageExpirationTimer
) {
1091 GroupInfo g
= getGroup(groupId
);
1092 if (g
instanceof GroupInfoV1
) {
1093 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
1094 groupInfoV1
.messageExpirationTime
= messageExpirationTimer
;
1095 account
.getGroupStore().updateGroup(groupInfoV1
);
1097 throw new RuntimeException("TODO Not implemented!");
1102 * Upload the sticker pack from path.
1104 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
1105 * @return if successful, returns the URL to install the sticker pack in the signal app
1107 public String
uploadStickerPack(File path
) throws IOException
, StickerPackInvalidException
{
1108 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
1110 SignalServiceMessageSender messageSender
= createMessageSender();
1112 byte[] packKey
= KeyUtils
.createStickerUploadKey();
1113 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
1115 Sticker sticker
= new Sticker(Hex
.fromStringCondensed(packId
), packKey
);
1116 account
.getStickerStore().updateSticker(sticker
);
1120 return new URI("https",
1123 "pack_id=" + URLEncoder
.encode(packId
, StandardCharsets
.UTF_8
) + "&pack_key=" + URLEncoder
.encode(
1124 Hex
.toStringCondensed(packKey
),
1125 StandardCharsets
.UTF_8
)).toString();
1126 } catch (URISyntaxException e
) {
1127 throw new AssertionError(e
);
1131 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(
1133 ) throws IOException
, StickerPackInvalidException
{
1135 String rootPath
= null;
1137 if (file
.getName().endsWith(".zip")) {
1138 zip
= new ZipFile(file
);
1139 } else if (file
.getName().equals("manifest.json")) {
1140 rootPath
= file
.getParent();
1142 throw new StickerPackInvalidException("Could not find manifest.json");
1145 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
1147 if (pack
.stickers
== null) {
1148 throw new StickerPackInvalidException("Must set a 'stickers' field.");
1151 if (pack
.stickers
.isEmpty()) {
1152 throw new StickerPackInvalidException("Must include stickers.");
1155 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
1156 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
1157 if (sticker
.file
== null) {
1158 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
1161 Pair
<InputStream
, Long
> data
;
1163 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
1164 } catch (IOException ignored
) {
1165 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
1168 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
1169 StickerInfo stickerInfo
= new StickerInfo(data
.first(),
1171 Optional
.fromNullable(sticker
.emoji
).or(""),
1173 stickers
.add(stickerInfo
);
1176 StickerInfo cover
= null;
1177 if (pack
.cover
!= null) {
1178 if (pack
.cover
.file
== null) {
1179 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
1182 Pair
<InputStream
, Long
> data
;
1184 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
1185 } catch (IOException ignored
) {
1186 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
1189 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
1190 cover
= new StickerInfo(data
.first(),
1192 Optional
.fromNullable(pack
.cover
.emoji
).or(""),
1196 return new SignalServiceStickerManifestUpload(pack
.title
, pack
.author
, cover
, stickers
);
1199 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
1200 InputStream inputStream
;
1202 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
1204 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
1206 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
1209 private static Pair
<InputStream
, Long
> getInputStreamAndLength(
1210 final String rootPath
, final ZipFile zip
, final String subfile
1211 ) throws IOException
{
1213 final ZipEntry entry
= zip
.getEntry(subfile
);
1214 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
1216 final File file
= new File(rootPath
, subfile
);
1217 return new Pair
<>(new FileInputStream(file
), file
.length());
1221 void requestSyncGroups() throws IOException
{
1222 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1223 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
)
1225 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1227 sendSyncMessage(message
);
1228 } catch (UntrustedIdentityException e
) {
1229 e
.printStackTrace();
1233 void requestSyncContacts() throws IOException
{
1234 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1235 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
)
1237 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1239 sendSyncMessage(message
);
1240 } catch (UntrustedIdentityException e
) {
1241 e
.printStackTrace();
1245 void requestSyncBlocked() throws IOException
{
1246 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1247 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
)
1249 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1251 sendSyncMessage(message
);
1252 } catch (UntrustedIdentityException e
) {
1253 e
.printStackTrace();
1257 void requestSyncConfiguration() throws IOException
{
1258 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1259 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
)
1261 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1263 sendSyncMessage(message
);
1264 } catch (UntrustedIdentityException e
) {
1265 e
.printStackTrace();
1269 private byte[] getSenderCertificate() {
1270 // TODO support UUID capable sender certificates
1271 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
1274 certificate
= accountManager
.getSenderCertificate();
1275 } catch (IOException e
) {
1276 logger
.warn("Failed to get sender certificate, ignoring: {}", e
.getMessage());
1279 // TODO cache for a day
1283 private void sendSyncMessage(SignalServiceSyncMessage message
) throws IOException
, UntrustedIdentityException
{
1284 SignalServiceMessageSender messageSender
= createMessageSender();
1286 messageSender
.sendMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
1287 } catch (UntrustedIdentityException e
) {
1288 account
.getSignalProtocolStore()
1289 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1291 TrustLevel
.UNTRUSTED
);
1296 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1297 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1298 final Set
<SignalServiceAddress
> missingUuids
= new HashSet
<>();
1300 for (String number
: numbers
) {
1301 final SignalServiceAddress resolvedAddress
= canonicalizeAndResolveSignalServiceAddress(number
);
1302 if (resolvedAddress
.getUuid().isPresent()) {
1303 signalServiceAddresses
.add(resolvedAddress
);
1305 missingUuids
.add(resolvedAddress
);
1309 Map
<String
, UUID
> registeredUsers
;
1311 registeredUsers
= accountManager
.getRegisteredUsers(getIasKeyStore(),
1312 missingUuids
.stream().map(a
-> a
.getNumber().get()).collect(Collectors
.toSet()),
1314 } catch (IOException
| Quote
.InvalidQuoteFormatException
| UnauthenticatedQuoteException
| SignatureException
| UnauthenticatedResponseException e
) {
1315 logger
.warn("Failed to resolve uuids from server, ignoring: {}", e
.getMessage());
1316 registeredUsers
= new HashMap
<>();
1319 for (SignalServiceAddress address
: missingUuids
) {
1320 final String number
= address
.getNumber().get();
1321 if (registeredUsers
.containsKey(number
)) {
1322 final SignalServiceAddress newAddress
= resolveSignalServiceAddress(new SignalServiceAddress(
1323 registeredUsers
.get(number
),
1325 signalServiceAddresses
.add(newAddress
);
1327 signalServiceAddresses
.add(address
);
1331 return signalServiceAddresses
;
1334 private Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1335 SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
1336 ) throws IOException
{
1337 recipients
= recipients
.stream().map(this::resolveSignalServiceAddress
).collect(Collectors
.toSet());
1338 final long timestamp
= System
.currentTimeMillis();
1339 messageBuilder
.withTimestamp(timestamp
);
1340 getOrCreateMessagePipe();
1341 getOrCreateUnidentifiedMessagePipe();
1342 SignalServiceDataMessage message
= null;
1344 message
= messageBuilder
.build();
1345 if (message
.getGroupContext().isPresent()) {
1347 SignalServiceMessageSender messageSender
= createMessageSender();
1348 final boolean isRecipientUpdate
= false;
1349 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
),
1350 unidentifiedAccessHelper
.getAccessFor(recipients
),
1353 for (SendMessageResult r
: result
) {
1354 if (r
.getIdentityFailure() != null) {
1355 account
.getSignalProtocolStore()
1356 .saveIdentity(r
.getAddress(),
1357 r
.getIdentityFailure().getIdentityKey(),
1358 TrustLevel
.UNTRUSTED
);
1361 return new Pair
<>(timestamp
, result
);
1362 } catch (UntrustedIdentityException e
) {
1363 account
.getSignalProtocolStore()
1364 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1366 TrustLevel
.UNTRUSTED
);
1367 return new Pair
<>(timestamp
, List
.of());
1370 // Send to all individually, so sync messages are sent correctly
1371 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1372 for (SignalServiceAddress address
: recipients
) {
1373 ContactInfo contact
= account
.getContactStore().getContact(address
);
1374 if (contact
!= null) {
1375 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1376 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1378 messageBuilder
.withExpiration(0);
1379 messageBuilder
.withProfileKey(null);
1381 message
= messageBuilder
.build();
1382 if (address
.matches(account
.getSelfAddress())) {
1383 results
.add(sendSelfMessage(message
));
1385 results
.add(sendMessage(address
, message
));
1388 return new Pair
<>(timestamp
, results
);
1391 if (message
!= null && message
.isEndSession()) {
1392 for (SignalServiceAddress recipient
: recipients
) {
1393 handleEndSession(recipient
);
1400 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1401 SignalServiceMessageSender messageSender
= createMessageSender();
1403 SignalServiceAddress recipient
= account
.getSelfAddress();
1405 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= unidentifiedAccessHelper
.getAccessFor(recipient
);
1406 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1407 message
.getTimestamp(),
1409 message
.getExpiresInSeconds(),
1410 Map
.of(recipient
, unidentifiedAccess
.isPresent()),
1412 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1415 long startTime
= System
.currentTimeMillis();
1416 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1417 return SendMessageResult
.success(recipient
,
1418 unidentifiedAccess
.isPresent(),
1420 System
.currentTimeMillis() - startTime
);
1421 } catch (UntrustedIdentityException e
) {
1422 account
.getSignalProtocolStore()
1423 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1425 TrustLevel
.UNTRUSTED
);
1426 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1430 private SendMessageResult
sendMessage(
1431 SignalServiceAddress address
, SignalServiceDataMessage message
1432 ) throws IOException
{
1433 SignalServiceMessageSender messageSender
= createMessageSender();
1436 return messageSender
.sendMessage(address
, unidentifiedAccessHelper
.getAccessFor(address
), message
);
1437 } catch (UntrustedIdentityException e
) {
1438 account
.getSignalProtocolStore()
1439 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1441 TrustLevel
.UNTRUSTED
);
1442 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1446 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1447 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(),
1448 account
.getSignalProtocolStore(),
1449 certificateValidator
);
1451 return cipher
.decrypt(envelope
);
1452 } catch (ProtocolUntrustedIdentityException e
) {
1453 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1454 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
1456 account
.getSignalProtocolStore()
1457 .saveIdentity(resolveSignalServiceAddress(identityException
.getName()),
1458 identityException
.getUntrustedIdentity(),
1459 TrustLevel
.UNTRUSTED
);
1460 throw identityException
;
1462 throw new AssertionError(e
);
1466 private void handleEndSession(SignalServiceAddress source
) {
1467 account
.getSignalProtocolStore().deleteAllSessions(source
);
1470 private static int currentTimeDays() {
1471 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
1474 private GroupsV2AuthorizationString
getGroupAuthForToday(
1475 final GroupSecretParams groupSecretParams
1476 ) throws IOException
{
1477 final int today
= currentTimeDays();
1478 // Returns credentials for the next 7 days
1479 final HashMap
<Integer
, AuthCredentialResponse
> credentials
= groupsV2Api
.getCredentials(today
);
1480 // TODO cache credentials until they expire
1481 AuthCredentialResponse authCredentialResponse
= credentials
.get(today
);
1483 return groupsV2Api
.getGroupsV2AuthorizationString(account
.getUuid(),
1486 authCredentialResponse
);
1487 } catch (VerificationFailedException e
) {
1488 throw new IOException(e
);
1492 private List
<HandleAction
> handleSignalServiceDataMessage(
1493 SignalServiceDataMessage message
,
1495 SignalServiceAddress source
,
1496 SignalServiceAddress destination
,
1497 boolean ignoreAttachments
1499 List
<HandleAction
> actions
= new ArrayList
<>();
1500 if (message
.getGroupContext().isPresent()) {
1501 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1502 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1503 GroupIdV1 groupId
= GroupId
.v1(groupInfo
.getGroupId());
1504 GroupInfo group
= getGroup(groupId
);
1505 if (group
== null || group
instanceof GroupInfoV1
) {
1506 GroupInfoV1 groupV1
= (GroupInfoV1
) group
;
1507 switch (groupInfo
.getType()) {
1509 if (groupV1
== null) {
1510 groupV1
= new GroupInfoV1(groupId
);
1513 if (groupInfo
.getAvatar().isPresent()) {
1514 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1515 if (avatar
.isPointer()) {
1517 retrieveGroupAvatarAttachment(avatar
.asPointer(), groupV1
.getGroupId());
1518 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1519 logger
.warn("Failed to retrieve avatar for group {}, ignoring: {}",
1526 if (groupInfo
.getName().isPresent()) {
1527 groupV1
.name
= groupInfo
.getName().get();
1530 if (groupInfo
.getMembers().isPresent()) {
1531 groupV1
.addMembers(groupInfo
.getMembers()
1534 .map(this::resolveSignalServiceAddress
)
1535 .collect(Collectors
.toSet()));
1538 account
.getGroupStore().updateGroup(groupV1
);
1542 if (groupV1
== null && !isSync
) {
1543 actions
.add(new SendGroupInfoRequestAction(source
, groupId
));
1547 if (groupV1
!= null) {
1548 groupV1
.removeMember(source
);
1549 account
.getGroupStore().updateGroup(groupV1
);
1554 if (groupV1
!= null && !isSync
) {
1555 actions
.add(new SendGroupUpdateAction(source
, groupV1
.getGroupId()));
1560 // Received a group v1 message for a v2 group
1563 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1564 final SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1565 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1567 getOrMigrateGroup(groupMasterKey
,
1568 groupContext
.getRevision(),
1569 groupContext
.hasSignedGroupChange() ? groupContext
.getSignedGroupChange() : null);
1573 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1574 if (conversationPartnerAddress
!= null && message
.isEndSession()) {
1575 handleEndSession(conversationPartnerAddress
);
1577 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1578 if (message
.getGroupContext().isPresent()) {
1579 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1580 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1581 GroupInfoV1 group
= account
.getGroupStore().getOrCreateGroupV1(GroupId
.v1(groupInfo
.getGroupId()));
1582 if (group
!= null) {
1583 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1584 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1585 account
.getGroupStore().updateGroup(group
);
1588 } else if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1589 // disappearing message timer already stored in the DecryptedGroup
1591 } else if (conversationPartnerAddress
!= null) {
1592 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1593 if (contact
== null) {
1594 contact
= new ContactInfo(conversationPartnerAddress
);
1596 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1597 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1598 account
.getContactStore().updateContact(contact
);
1602 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1603 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1604 if (attachment
.isPointer()) {
1606 retrieveAttachment(attachment
.asPointer());
1607 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1608 logger
.warn("Failed to retrieve attachment ({}), ignoring: {}",
1609 attachment
.asPointer().getRemoteId(),
1615 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1616 final ProfileKey profileKey
;
1618 profileKey
= new ProfileKey(message
.getProfileKey().get());
1619 } catch (InvalidInputException e
) {
1620 throw new AssertionError(e
);
1622 if (source
.matches(account
.getSelfAddress())) {
1623 this.account
.setProfileKey(profileKey
);
1625 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1627 if (message
.getPreviews().isPresent()) {
1628 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1629 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1630 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1631 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1633 retrieveAttachment(attachment
);
1634 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1635 logger
.warn("Failed to retrieve preview image ({}), ignoring: {}",
1636 attachment
.getRemoteId(),
1642 if (message
.getQuote().isPresent()) {
1643 final SignalServiceDataMessage
.Quote quote
= message
.getQuote().get();
1645 for (SignalServiceDataMessage
.Quote
.QuotedAttachment quotedAttachment
: quote
.getAttachments()) {
1646 final SignalServiceAttachment attachment
= quotedAttachment
.getThumbnail();
1647 if (attachment
!= null && attachment
.isPointer()) {
1649 retrieveAttachment(attachment
.asPointer());
1650 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1651 logger
.warn("Failed to retrieve quote attachment thumbnail ({}), ignoring: {}",
1652 attachment
.asPointer().getRemoteId(),
1658 if (message
.getSticker().isPresent()) {
1659 final SignalServiceDataMessage
.Sticker messageSticker
= message
.getSticker().get();
1660 Sticker sticker
= account
.getStickerStore().getSticker(messageSticker
.getPackId());
1661 if (sticker
== null) {
1662 sticker
= new Sticker(messageSticker
.getPackId(), messageSticker
.getPackKey());
1663 account
.getStickerStore().updateSticker(sticker
);
1669 private GroupInfoV2
getOrMigrateGroup(
1670 final GroupMasterKey groupMasterKey
, final int revision
, final byte[] signedGroupChange
1672 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
1674 GroupIdV2 groupId
= GroupUtils
.getGroupIdV2(groupSecretParams
);
1675 GroupInfo groupInfo
= getGroup(groupId
);
1676 final GroupInfoV2 groupInfoV2
;
1677 if (groupInfo
instanceof GroupInfoV1
) {
1678 // Received a v2 group message for a v1 group, we need to locally migrate the group
1679 account
.getGroupStore().deleteGroup(groupInfo
.getGroupId());
1680 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1681 logger
.info("Locally migrated group {} to group v2, id: {}",
1682 groupInfo
.getGroupId().toBase64(),
1683 groupInfoV2
.getGroupId().toBase64());
1684 } else if (groupInfo
instanceof GroupInfoV2
) {
1685 groupInfoV2
= (GroupInfoV2
) groupInfo
;
1687 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1690 if (groupInfoV2
.getGroup() == null || groupInfoV2
.getGroup().getRevision() < revision
) {
1691 DecryptedGroup group
= null;
1692 if (signedGroupChange
!= null
1693 && groupInfoV2
.getGroup() != null
1694 && groupInfoV2
.getGroup().getRevision() + 1 == revision
) {
1695 group
= groupHelper
.getUpdatedDecryptedGroup(groupInfoV2
.getGroup(), signedGroupChange
, groupMasterKey
);
1697 if (group
== null) {
1698 group
= groupHelper
.getDecryptedGroup(groupSecretParams
);
1700 if (group
!= null) {
1701 storeProfileKeysFromMembers(group
);
1702 final String avatar
= group
.getAvatar();
1703 if (avatar
!= null && !avatar
.isEmpty()) {
1705 retrieveGroupAvatar(groupId
, groupSecretParams
, avatar
);
1706 } catch (IOException e
) {
1707 logger
.warn("Failed to download group avatar, ignoring: {}", e
.getMessage());
1711 groupInfoV2
.setGroup(group
);
1712 account
.getGroupStore().updateGroup(groupInfoV2
);
1718 private void storeProfileKeysFromMembers(final DecryptedGroup group
) {
1719 for (DecryptedMember member
: group
.getMembersList()) {
1720 final SignalServiceAddress address
= resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil
.parseOrThrow(
1721 member
.getUuid().toByteArray()), null));
1723 account
.getProfileStore()
1724 .storeProfileKey(address
, new ProfileKey(member
.getProfileKey().toByteArray()));
1725 } catch (InvalidInputException ignored
) {
1730 private void retryFailedReceivedMessages(
1731 ReceiveMessageHandler handler
, boolean ignoreAttachments
1733 final File cachePath
= getMessageCachePath();
1734 if (!cachePath
.exists()) {
1737 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1738 if (!dir
.isDirectory()) {
1739 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1743 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1744 if (!fileEntry
.isFile()) {
1747 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1749 // Try to delete directory if empty
1754 private void retryFailedReceivedMessage(
1755 final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
1757 SignalServiceEnvelope envelope
;
1759 envelope
= MessageCacheUtils
.loadEnvelope(fileEntry
);
1760 if (envelope
== null) {
1763 } catch (IOException e
) {
1764 e
.printStackTrace();
1767 SignalServiceContent content
= null;
1768 if (!envelope
.isReceipt()) {
1770 content
= decryptMessage(envelope
);
1771 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1773 } catch (Exception er
) {
1774 // All other errors are not recoverable, so delete the cached message
1776 Files
.delete(fileEntry
.toPath());
1777 } catch (IOException e
) {
1778 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry
, e
.getMessage());
1782 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1783 for (HandleAction action
: actions
) {
1785 action
.execute(this);
1786 } catch (Throwable e
) {
1787 e
.printStackTrace();
1792 handler
.handleMessage(envelope
, content
, null);
1794 Files
.delete(fileEntry
.toPath());
1795 } catch (IOException e
) {
1796 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry
, e
.getMessage());
1800 public void receiveMessages(
1803 boolean returnOnTimeout
,
1804 boolean ignoreAttachments
,
1805 ReceiveMessageHandler handler
1806 ) throws IOException
{
1807 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1809 Set
<HandleAction
> queuedActions
= null;
1811 getOrCreateMessagePipe();
1813 boolean hasCaughtUpWithOldMessages
= false;
1816 SignalServiceEnvelope envelope
;
1817 SignalServiceContent content
= null;
1818 Exception exception
= null;
1819 final long now
= new Date().getTime();
1821 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1822 // store message on disk, before acknowledging receipt to the server
1824 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1825 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1826 MessageCacheUtils
.storeEnvelope(envelope1
, cacheFile
);
1827 } catch (IOException e
) {
1828 logger
.warn("Failed to store encrypted message in disk cache, ignoring: {}", e
.getMessage());
1831 if (result
.isPresent()) {
1832 envelope
= result
.get();
1834 // Received indicator that server queue is empty
1835 hasCaughtUpWithOldMessages
= true;
1837 if (queuedActions
!= null) {
1838 for (HandleAction action
: queuedActions
) {
1840 action
.execute(this);
1841 } catch (Throwable e
) {
1842 e
.printStackTrace();
1846 queuedActions
.clear();
1847 queuedActions
= null;
1850 // Continue to wait another timeout for new messages
1853 } catch (TimeoutException e
) {
1854 if (returnOnTimeout
) return;
1856 } catch (InvalidVersionException e
) {
1857 logger
.warn("Error while receiving messages, ignoring: {}", e
.getMessage());
1861 if (envelope
.hasSource()) {
1862 // Store uuid if we don't have it already
1863 SignalServiceAddress source
= envelope
.getSourceAddress();
1864 resolveSignalServiceAddress(source
);
1866 if (!envelope
.isReceipt()) {
1868 content
= decryptMessage(envelope
);
1869 } catch (Exception e
) {
1872 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1873 if (hasCaughtUpWithOldMessages
) {
1874 for (HandleAction action
: actions
) {
1876 action
.execute(this);
1877 } catch (Throwable e
) {
1878 e
.printStackTrace();
1882 if (queuedActions
== null) {
1883 queuedActions
= new HashSet
<>();
1885 queuedActions
.addAll(actions
);
1889 if (!isMessageBlocked(envelope
, content
)) {
1890 handler
.handleMessage(envelope
, content
, exception
);
1892 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1893 File cacheFile
= null;
1895 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1896 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1897 Files
.delete(cacheFile
.toPath());
1898 // Try to delete directory if empty
1899 getMessageCachePath().delete();
1900 } catch (IOException e
) {
1901 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", cacheFile
, e
.getMessage());
1907 private boolean isMessageBlocked(
1908 SignalServiceEnvelope envelope
, SignalServiceContent content
1910 SignalServiceAddress source
;
1911 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1912 source
= envelope
.getSourceAddress();
1913 } else if (content
!= null) {
1914 source
= content
.getSender();
1918 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1919 if (sourceContact
!= null && sourceContact
.blocked
) {
1923 if (content
!= null && content
.getDataMessage().isPresent()) {
1924 SignalServiceDataMessage message
= content
.getDataMessage().get();
1925 if (message
.getGroupContext().isPresent()) {
1926 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1927 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1928 if (groupInfo
.getType() != SignalServiceGroup
.Type
.DELIVER
) {
1932 GroupId groupId
= GroupUtils
.getGroupId(message
.getGroupContext().get());
1933 GroupInfo group
= getGroup(groupId
);
1934 if (group
!= null && group
.isBlocked()) {
1942 private List
<HandleAction
> handleMessage(
1943 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
1945 List
<HandleAction
> actions
= new ArrayList
<>();
1946 if (content
!= null) {
1947 final SignalServiceAddress sender
;
1948 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1949 sender
= envelope
.getSourceAddress();
1951 sender
= content
.getSender();
1953 // Store uuid if we don't have it already
1954 resolveSignalServiceAddress(sender
);
1956 if (content
.getDataMessage().isPresent()) {
1957 SignalServiceDataMessage message
= content
.getDataMessage().get();
1959 if (content
.isNeedsReceipt()) {
1960 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1963 actions
.addAll(handleSignalServiceDataMessage(message
,
1966 account
.getSelfAddress(),
1967 ignoreAttachments
));
1969 if (content
.getSyncMessage().isPresent()) {
1970 account
.setMultiDevice(true);
1971 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1972 if (syncMessage
.getSent().isPresent()) {
1973 SentTranscriptMessage message
= syncMessage
.getSent().get();
1974 final SignalServiceAddress destination
= message
.getDestination().orNull();
1975 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
1979 ignoreAttachments
));
1981 if (syncMessage
.getRequest().isPresent()) {
1982 RequestMessage rm
= syncMessage
.getRequest().get();
1983 if (rm
.isContactsRequest()) {
1984 actions
.add(SendSyncContactsAction
.create());
1986 if (rm
.isGroupsRequest()) {
1987 actions
.add(SendSyncGroupsAction
.create());
1989 if (rm
.isBlockedListRequest()) {
1990 actions
.add(SendSyncBlockedListAction
.create());
1992 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
1994 if (syncMessage
.getGroups().isPresent()) {
1995 File tmpFile
= null;
1997 tmpFile
= IOUtils
.createTempFile();
1998 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups()
2000 .asPointer(), tmpFile
)) {
2001 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
2003 while ((g
= s
.read()) != null) {
2004 GroupInfoV1 syncGroup
= account
.getGroupStore()
2005 .getOrCreateGroupV1(GroupId
.v1(g
.getId()));
2006 if (syncGroup
!= null) {
2007 if (g
.getName().isPresent()) {
2008 syncGroup
.name
= g
.getName().get();
2010 syncGroup
.addMembers(g
.getMembers()
2012 .map(this::resolveSignalServiceAddress
)
2013 .collect(Collectors
.toSet()));
2014 if (!g
.isActive()) {
2015 syncGroup
.removeMember(account
.getSelfAddress());
2017 // Add ourself to the member set as it's marked as active
2018 syncGroup
.addMembers(List
.of(account
.getSelfAddress()));
2020 syncGroup
.blocked
= g
.isBlocked();
2021 if (g
.getColor().isPresent()) {
2022 syncGroup
.color
= g
.getColor().get();
2025 if (g
.getAvatar().isPresent()) {
2026 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.getGroupId());
2028 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
2029 syncGroup
.archived
= g
.isArchived();
2030 account
.getGroupStore().updateGroup(syncGroup
);
2034 } catch (Exception e
) {
2035 logger
.warn("Failed to handle received sync groups “{}”, ignoring: {}",
2038 e
.printStackTrace();
2040 if (tmpFile
!= null) {
2042 Files
.delete(tmpFile
.toPath());
2043 } catch (IOException e
) {
2044 logger
.warn("Failed to delete received groups temp file “{}”, ignoring: {}",
2051 if (syncMessage
.getBlockedList().isPresent()) {
2052 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
2053 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
2054 setContactBlocked(resolveSignalServiceAddress(address
), true);
2056 for (GroupId groupId
: blockedListMessage
.getGroupIds()
2058 .map(GroupId
::unknownVersion
)
2059 .collect(Collectors
.toSet())) {
2061 setGroupBlocked(groupId
, true);
2062 } catch (GroupNotFoundException e
) {
2063 logger
.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
2064 groupId
.toBase64());
2068 if (syncMessage
.getContacts().isPresent()) {
2069 File tmpFile
= null;
2071 tmpFile
= IOUtils
.createTempFile();
2072 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
2073 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream()
2074 .asPointer(), tmpFile
)) {
2075 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
2076 if (contactsMessage
.isComplete()) {
2077 account
.getContactStore().clear();
2080 while ((c
= s
.read()) != null) {
2081 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
2082 account
.setProfileKey(c
.getProfileKey().get());
2084 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
2085 ContactInfo contact
= account
.getContactStore().getContact(address
);
2086 if (contact
== null) {
2087 contact
= new ContactInfo(address
);
2089 if (c
.getName().isPresent()) {
2090 contact
.name
= c
.getName().get();
2092 if (c
.getColor().isPresent()) {
2093 contact
.color
= c
.getColor().get();
2095 if (c
.getProfileKey().isPresent()) {
2096 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
2098 if (c
.getVerified().isPresent()) {
2099 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
2100 account
.getSignalProtocolStore()
2101 .setIdentityTrustLevel(verifiedMessage
.getDestination(),
2102 verifiedMessage
.getIdentityKey(),
2103 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2105 if (c
.getExpirationTimer().isPresent()) {
2106 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
2108 contact
.blocked
= c
.isBlocked();
2109 contact
.inboxPosition
= c
.getInboxPosition().orNull();
2110 contact
.archived
= c
.isArchived();
2111 account
.getContactStore().updateContact(contact
);
2113 if (c
.getAvatar().isPresent()) {
2114 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
2118 } catch (Exception e
) {
2119 e
.printStackTrace();
2121 if (tmpFile
!= null) {
2123 Files
.delete(tmpFile
.toPath());
2124 } catch (IOException e
) {
2125 logger
.warn("Failed to delete received contacts temp file “{}”, ignoring: {}",
2132 if (syncMessage
.getVerified().isPresent()) {
2133 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
2134 account
.getSignalProtocolStore()
2135 .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()),
2136 verifiedMessage
.getIdentityKey(),
2137 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2139 if (syncMessage
.getStickerPackOperations().isPresent()) {
2140 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations()
2142 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
2143 if (!m
.getPackId().isPresent()) {
2146 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
2147 if (sticker
== null) {
2148 if (!m
.getPackKey().isPresent()) {
2151 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
2153 sticker
.setInstalled(!m
.getType().isPresent()
2154 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
2155 account
.getStickerStore().updateSticker(sticker
);
2158 if (syncMessage
.getConfiguration().isPresent()) {
2166 private File
getContactAvatarFile(String number
) {
2167 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
2170 private File
retrieveContactAvatarAttachment(
2171 SignalServiceAttachment attachment
, String number
2172 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2173 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2174 if (attachment
.isPointer()) {
2175 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2176 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
2178 SignalServiceAttachmentStream stream
= attachment
.asStream();
2179 return AttachmentUtils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
2183 private File
getGroupAvatarFile(GroupId groupId
) {
2184 return new File(pathConfig
.getAvatarsPath(), "group-" + groupId
.toBase64().replace("/", "_"));
2187 private File
retrieveGroupAvatarAttachment(
2188 SignalServiceAttachment attachment
, GroupId groupId
2189 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2190 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2191 if (attachment
.isPointer()) {
2192 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2193 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
2195 SignalServiceAttachmentStream stream
= attachment
.asStream();
2196 return AttachmentUtils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
2200 private File
retrieveGroupAvatar(
2201 GroupId groupId
, GroupSecretParams groupSecretParams
, String cdnKey
2202 ) throws IOException
{
2203 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2204 File outputFile
= getGroupAvatarFile(groupId
);
2205 GroupsV2Operations
.GroupOperations groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
2207 File tmpFile
= IOUtils
.createTempFile();
2208 tmpFile
.deleteOnExit();
2209 try (InputStream input
= messageReceiver
.retrieveGroupsV2ProfileAvatar(cdnKey
,
2211 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2212 byte[] encryptedData
= IOUtils
.readFully(input
);
2214 byte[] decryptedData
= groupOperations
.decryptAvatar(encryptedData
);
2215 try (OutputStream output
= new FileOutputStream(outputFile
)) {
2216 output
.write(decryptedData
);
2220 Files
.delete(tmpFile
.toPath());
2221 } catch (IOException e
) {
2222 logger
.warn("Failed to delete received group avatar temp file “{}”, ignoring: {}",
2230 private File
getProfileAvatarFile(SignalServiceAddress address
) {
2231 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
2234 private File
retrieveProfileAvatar(
2235 SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
2236 ) throws IOException
{
2237 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2238 File outputFile
= getProfileAvatarFile(address
);
2240 File tmpFile
= IOUtils
.createTempFile();
2241 try (InputStream input
= messageReceiver
.retrieveProfileAvatar(avatarPath
,
2244 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2245 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2246 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
2249 Files
.delete(tmpFile
.toPath());
2250 } catch (IOException e
) {
2251 logger
.warn("Failed to delete received profile avatar temp file “{}”, ignoring: {}",
2259 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
2260 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
2263 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2264 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
2265 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
2268 private File
retrieveAttachment(
2269 SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
2270 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2271 if (storePreview
&& pointer
.getPreview().isPresent()) {
2272 File previewFile
= new File(outputFile
+ ".preview");
2273 try (OutputStream output
= new FileOutputStream(previewFile
)) {
2274 byte[] preview
= pointer
.getPreview().get();
2275 output
.write(preview
, 0, preview
.length
);
2276 } catch (FileNotFoundException e
) {
2277 e
.printStackTrace();
2282 File tmpFile
= IOUtils
.createTempFile();
2283 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
,
2285 ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
2286 IOUtils
.copyStreamToFile(input
, outputFile
);
2289 Files
.delete(tmpFile
.toPath());
2290 } catch (IOException e
) {
2291 logger
.warn("Failed to delete received attachment temp file “{}”, ignoring: {}",
2299 private InputStream
retrieveAttachmentAsStream(
2300 SignalServiceAttachmentPointer pointer
, File tmpFile
2301 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2302 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
2305 void sendGroups() throws IOException
, UntrustedIdentityException
{
2306 File groupsFile
= IOUtils
.createTempFile();
2309 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
2310 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
2311 for (GroupInfo
record : getGroups()) {
2312 if (record instanceof GroupInfoV1
) {
2313 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
2314 out
.write(new DeviceGroup(groupInfo
.getGroupId().serialize(),
2315 Optional
.fromNullable(groupInfo
.name
),
2316 new ArrayList
<>(groupInfo
.getMembers()),
2317 createGroupAvatarAttachment(groupInfo
.getGroupId()),
2318 groupInfo
.isMember(account
.getSelfAddress()),
2319 Optional
.of(groupInfo
.messageExpirationTime
),
2320 Optional
.fromNullable(groupInfo
.color
),
2322 Optional
.fromNullable(groupInfo
.inboxPosition
),
2323 groupInfo
.archived
));
2328 if (groupsFile
.exists() && groupsFile
.length() > 0) {
2329 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
2330 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2331 .withStream(groupsFileStream
)
2332 .withContentType("application/octet-stream")
2333 .withLength(groupsFile
.length())
2336 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
2341 Files
.delete(groupsFile
.toPath());
2342 } catch (IOException e
) {
2343 logger
.warn("Failed to delete groups temp file “{}”, ignoring: {}", groupsFile
, e
.getMessage());
2348 public void sendContacts() throws IOException
, UntrustedIdentityException
{
2349 File contactsFile
= IOUtils
.createTempFile();
2352 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
2353 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
2354 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2355 VerifiedMessage verifiedMessage
= null;
2356 IdentityInfo currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
2357 if (currentIdentity
!= null) {
2358 verifiedMessage
= new VerifiedMessage(record.getAddress(),
2359 currentIdentity
.getIdentityKey(),
2360 currentIdentity
.getTrustLevel().toVerifiedState(),
2361 currentIdentity
.getDateAdded().getTime());
2364 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
2365 out
.write(new DeviceContact(record.getAddress(),
2366 Optional
.fromNullable(record.name
),
2367 createContactAvatarAttachment(record.number
),
2368 Optional
.fromNullable(record.color
),
2369 Optional
.fromNullable(verifiedMessage
),
2370 Optional
.fromNullable(profileKey
),
2372 Optional
.of(record.messageExpirationTime
),
2373 Optional
.fromNullable(record.inboxPosition
),
2377 if (account
.getProfileKey() != null) {
2378 // Send our own profile key as well
2379 out
.write(new DeviceContact(account
.getSelfAddress(),
2384 Optional
.of(account
.getProfileKey()),
2392 if (contactsFile
.exists() && contactsFile
.length() > 0) {
2393 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
2394 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2395 .withStream(contactsFileStream
)
2396 .withContentType("application/octet-stream")
2397 .withLength(contactsFile
.length())
2400 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
2405 Files
.delete(contactsFile
.toPath());
2406 } catch (IOException e
) {
2407 logger
.warn("Failed to delete contacts temp file “{}”, ignoring: {}", contactsFile
, e
.getMessage());
2412 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2413 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2414 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2415 if (record.blocked
) {
2416 addresses
.add(record.getAddress());
2419 List
<byte[]> groupIds
= new ArrayList
<>();
2420 for (GroupInfo
record : getGroups()) {
2421 if (record.isBlocked()) {
2422 groupIds
.add(record.getGroupId().serialize());
2425 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2428 private void sendVerifiedMessage(
2429 SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
2430 ) throws IOException
, UntrustedIdentityException
{
2431 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
,
2433 trustLevel
.toVerifiedState(),
2434 System
.currentTimeMillis());
2435 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2438 public List
<ContactInfo
> getContacts() {
2439 return account
.getContactStore().getContacts();
2442 public ContactInfo
getContact(String number
) {
2443 return account
.getContactStore().getContact(Utils
.getSignalServiceAddressFromIdentifier(number
));
2446 public GroupInfo
getGroup(GroupId groupId
) {
2447 final GroupInfo group
= account
.getGroupStore().getGroup(groupId
);
2448 if (group
instanceof GroupInfoV2
&& ((GroupInfoV2
) group
).getGroup() == null) {
2449 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(((GroupInfoV2
) group
).getMasterKey());
2450 ((GroupInfoV2
) group
).setGroup(groupHelper
.getDecryptedGroup(groupSecretParams
));
2451 account
.getGroupStore().updateGroup(group
);
2456 public List
<IdentityInfo
> getIdentities() {
2457 return account
.getSignalProtocolStore().getIdentities();
2460 public List
<IdentityInfo
> getIdentities(String number
) throws InvalidNumberException
{
2461 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2465 * Trust this the identity with this fingerprint
2467 * @param name username of the identity
2468 * @param fingerprint Fingerprint
2470 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2471 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2472 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2476 for (IdentityInfo id
: ids
) {
2477 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2481 account
.getSignalProtocolStore()
2482 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2484 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2485 } catch (IOException
| UntrustedIdentityException e
) {
2486 e
.printStackTrace();
2495 * Trust this the identity with this safety number
2497 * @param name username of the identity
2498 * @param safetyNumber Safety number
2500 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2501 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2502 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2506 for (IdentityInfo id
: ids
) {
2507 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2511 account
.getSignalProtocolStore()
2512 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2514 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2515 } catch (IOException
| UntrustedIdentityException e
) {
2516 e
.printStackTrace();
2525 * Trust all keys of this identity without verification
2527 * @param name username of the identity
2529 public boolean trustIdentityAllKeys(String name
) {
2530 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2531 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2535 for (IdentityInfo id
: ids
) {
2536 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2537 account
.getSignalProtocolStore()
2538 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2540 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2541 } catch (IOException
| UntrustedIdentityException e
) {
2542 e
.printStackTrace();
2550 public String
computeSafetyNumber(
2551 SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
2553 return Utils
.computeSafetyNumber(ServiceConfig
.capabilities
.isUuid(),
2554 account
.getSelfAddress(),
2555 getIdentityKeyPair().getPublicKey(),
2560 void saveAccount() {
2564 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2565 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
)
2567 : PhoneNumberFormatter
.formatNumber(identifier
, account
.getUsername());
2568 return resolveSignalServiceAddress(canonicalizedNumber
);
2571 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2572 SignalServiceAddress address
= Utils
.getSignalServiceAddressFromIdentifier(identifier
);
2574 return resolveSignalServiceAddress(address
);
2577 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2578 if (address
.matches(account
.getSelfAddress())) {
2579 return account
.getSelfAddress();
2582 return account
.getRecipientStore().resolveServiceAddress(address
);
2586 public void close() throws IOException
{
2590 void close(boolean closeAccount
) throws IOException
{
2591 if (messagePipe
!= null) {
2592 messagePipe
.shutdown();
2596 if (unidentifiedMessagePipe
!= null) {
2597 unidentifiedMessagePipe
.shutdown();
2598 unidentifiedMessagePipe
= null;
2601 if (closeAccount
&& account
!= null) {
2607 public interface ReceiveMessageHandler
{
2609 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);