2 Copyright (C) 2015-2020 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
.helper
.GroupHelper
;
22 import org
.asamk
.signal
.manager
.helper
.ProfileHelper
;
23 import org
.asamk
.signal
.manager
.helper
.UnidentifiedAccessHelper
;
24 import org
.asamk
.signal
.storage
.SignalAccount
;
25 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
26 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
27 import org
.asamk
.signal
.storage
.groups
.GroupInfoV1
;
28 import org
.asamk
.signal
.storage
.groups
.GroupInfoV2
;
29 import org
.asamk
.signal
.storage
.profiles
.SignalProfile
;
30 import org
.asamk
.signal
.storage
.profiles
.SignalProfileEntry
;
31 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
32 import org
.asamk
.signal
.storage
.stickers
.Sticker
;
33 import org
.asamk
.signal
.util
.IOUtils
;
34 import org
.asamk
.signal
.util
.Util
;
35 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
36 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
37 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
38 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
39 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
40 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
41 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
42 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
43 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
44 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
45 import org
.signal
.libsignal
.metadata
.SelfSendException
;
46 import org
.signal
.storageservice
.protos
.groups
.GroupChange
;
47 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroup
;
48 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroupJoinInfo
;
49 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedMember
;
50 import org
.signal
.zkgroup
.InvalidInputException
;
51 import org
.signal
.zkgroup
.VerificationFailedException
;
52 import org
.signal
.zkgroup
.auth
.AuthCredentialResponse
;
53 import org
.signal
.zkgroup
.groups
.GroupMasterKey
;
54 import org
.signal
.zkgroup
.groups
.GroupSecretParams
;
55 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
56 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
57 import org
.signal
.zkgroup
.profiles
.ProfileKeyCredential
;
58 import org
.slf4j
.Logger
;
59 import org
.slf4j
.LoggerFactory
;
60 import org
.whispersystems
.libsignal
.IdentityKey
;
61 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
62 import org
.whispersystems
.libsignal
.InvalidKeyException
;
63 import org
.whispersystems
.libsignal
.InvalidMessageException
;
64 import org
.whispersystems
.libsignal
.InvalidVersionException
;
65 import org
.whispersystems
.libsignal
.ecc
.Curve
;
66 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
67 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
68 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
69 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
70 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
71 import org
.whispersystems
.libsignal
.util
.Medium
;
72 import org
.whispersystems
.libsignal
.util
.Pair
;
73 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
74 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
75 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
76 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
77 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
78 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
79 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
80 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
81 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
82 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
83 import org
.whispersystems
.signalservice
.api
.groupsv2
.ClientZkOperations
;
84 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
85 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Api
;
86 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2AuthorizationString
;
87 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
88 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
89 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
90 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
91 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
92 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
93 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
94 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
95 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
96 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
97 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroupV2
;
98 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
99 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
100 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
101 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
102 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
103 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
104 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
105 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
106 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
107 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
108 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
109 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
110 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
111 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
112 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
113 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.StickerPackOperationMessage
;
114 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
115 import org
.whispersystems
.signalservice
.api
.profiles
.ProfileAndCredential
;
116 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
117 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
118 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
119 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
120 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
121 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
122 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
123 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
124 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
125 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
126 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.Quote
;
127 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedQuoteException
;
128 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedResponseException
;
129 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
130 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
131 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
132 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
133 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
134 import org
.whispersystems
.util
.Base64
;
136 import java
.io
.Closeable
;
138 import java
.io
.FileInputStream
;
139 import java
.io
.FileNotFoundException
;
140 import java
.io
.FileOutputStream
;
141 import java
.io
.IOException
;
142 import java
.io
.InputStream
;
143 import java
.io
.OutputStream
;
145 import java
.net
.URISyntaxException
;
146 import java
.net
.URLEncoder
;
147 import java
.nio
.charset
.StandardCharsets
;
148 import java
.nio
.file
.Files
;
149 import java
.nio
.file
.Paths
;
150 import java
.nio
.file
.StandardCopyOption
;
151 import java
.security
.SignatureException
;
152 import java
.util
.ArrayList
;
153 import java
.util
.Arrays
;
154 import java
.util
.Collection
;
155 import java
.util
.Collections
;
156 import java
.util
.Date
;
157 import java
.util
.HashMap
;
158 import java
.util
.HashSet
;
159 import java
.util
.List
;
160 import java
.util
.Locale
;
161 import java
.util
.Map
;
162 import java
.util
.Objects
;
163 import java
.util
.Set
;
164 import java
.util
.UUID
;
165 import java
.util
.concurrent
.ExecutorService
;
166 import java
.util
.concurrent
.TimeUnit
;
167 import java
.util
.concurrent
.TimeoutException
;
168 import java
.util
.stream
.Collectors
;
169 import java
.util
.zip
.ZipEntry
;
170 import java
.util
.zip
.ZipFile
;
172 import static org
.asamk
.signal
.manager
.ServiceConfig
.CDS_MRENCLAVE
;
173 import static org
.asamk
.signal
.manager
.ServiceConfig
.capabilities
;
174 import static org
.asamk
.signal
.manager
.ServiceConfig
.getIasKeyStore
;
176 public class Manager
implements Closeable
{
178 final static Logger logger
= LoggerFactory
.getLogger(Manager
.class);
180 private final SleepTimer timer
= new UptimeSleepTimer();
182 private final SignalServiceConfiguration serviceConfiguration
;
183 private final String userAgent
;
184 private final boolean discoverableByPhoneNumber
= true;
185 private final boolean unrestrictedUnidentifiedAccess
= false;
187 private final SignalAccount account
;
188 private final PathConfig pathConfig
;
189 private SignalServiceAccountManager accountManager
;
190 private GroupsV2Api groupsV2Api
;
191 private final GroupsV2Operations groupsV2Operations
;
193 private SignalServiceMessageReceiver messageReceiver
= null;
194 private SignalServiceMessagePipe messagePipe
= null;
195 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
197 private final UnidentifiedAccessHelper unidentifiedAccessHelper
;
198 private final ProfileHelper profileHelper
;
199 private final GroupHelper groupHelper
;
202 SignalAccount account
,
203 PathConfig pathConfig
,
204 SignalServiceConfiguration serviceConfiguration
,
207 this.account
= account
;
208 this.pathConfig
= pathConfig
;
209 this.serviceConfiguration
= serviceConfiguration
;
210 this.userAgent
= userAgent
;
211 this.groupsV2Operations
= capabilities
.isGv2() ?
new GroupsV2Operations(ClientZkOperations
.create(
212 serviceConfiguration
)) : null;
213 this.accountManager
= createSignalServiceAccountManager();
214 this.groupsV2Api
= accountManager
.getGroupsV2Api();
216 this.account
.setResolver(this::resolveSignalServiceAddress
);
218 this.unidentifiedAccessHelper
= new UnidentifiedAccessHelper(account
::getProfileKey
,
219 account
.getProfileStore()::getProfileKey
,
220 this::getRecipientProfile
,
221 this::getSenderCertificate
);
222 this.profileHelper
= new ProfileHelper(account
.getProfileStore()::getProfileKey
,
223 unidentifiedAccessHelper
::getAccessFor
,
224 unidentified
-> unidentified ?
getOrCreateUnidentifiedMessagePipe() : getOrCreateMessagePipe(),
225 this::getOrCreateMessageReceiver
);
226 this.groupHelper
= new GroupHelper(this::getRecipientProfileKeyCredential
,
227 this::getRecipientProfile
,
228 account
::getSelfAddress
,
231 this::getGroupAuthForToday
);
234 public String
getUsername() {
235 return account
.getUsername();
238 public SignalServiceAddress
getSelfAddress() {
239 return account
.getSelfAddress();
242 private SignalServiceAccountManager
createSignalServiceAccountManager() {
243 return new SignalServiceAccountManager(serviceConfiguration
,
244 new DynamicCredentialsProvider(account
.getUuid(),
245 account
.getUsername(),
246 account
.getPassword(),
248 account
.getDeviceId()),
254 private IdentityKeyPair
getIdentityKeyPair() {
255 return account
.getSignalProtocolStore().getIdentityKeyPair();
258 public int getDeviceId() {
259 return account
.getDeviceId();
262 private String
getMessageCachePath() {
263 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
266 private String
getMessageCachePath(String sender
) {
267 if (sender
== null || sender
.isEmpty()) {
268 return getMessageCachePath();
271 return getMessageCachePath() + "/" + sender
.replace("/", "_");
274 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
275 String cachePath
= getMessageCachePath(sender
);
276 IOUtils
.createPrivateDirectories(cachePath
);
277 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
280 public static Manager
init(
281 String username
, File settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
282 ) throws IOException
{
283 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
285 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
286 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
287 int registrationId
= KeyHelper
.generateRegistrationId(false);
289 ProfileKey profileKey
= KeyUtils
.createProfileKey();
290 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(),
297 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
300 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
302 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
304 m
.migrateLegacyConfigs();
309 private void migrateLegacyConfigs() {
310 if (account
.getProfileKey() == null && isRegistered()) {
311 // Old config file, creating new profile key
312 account
.setProfileKey(KeyUtils
.createProfileKey());
315 // Store profile keys only in profile store
316 for (ContactInfo contact
: account
.getContactStore().getContacts()) {
317 String profileKeyString
= contact
.profileKey
;
318 if (profileKeyString
== null) {
321 final ProfileKey profileKey
;
323 profileKey
= new ProfileKey(Base64
.decode(profileKeyString
));
324 } catch (InvalidInputException
| IOException e
) {
327 contact
.profileKey
= null;
328 account
.getProfileStore().storeProfileKey(contact
.getAddress(), profileKey
);
330 // Ensure our profile key is stored in profile store
331 account
.getProfileStore().storeProfileKey(getSelfAddress(), account
.getProfileKey());
334 public void checkAccountState() throws IOException
{
335 if (account
.isRegistered()) {
336 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
340 if (account
.getUuid() == null) {
341 account
.setUuid(accountManager
.getOwnUuid());
344 updateAccountAttributes();
348 public boolean isRegistered() {
349 return account
.isRegistered();
352 public void register(boolean voiceVerification
, String captcha
) throws IOException
{
353 account
.setPassword(KeyUtils
.createPassword());
355 // Resetting UUID, because registering doesn't work otherwise
356 account
.setUuid(null);
357 accountManager
= createSignalServiceAccountManager();
358 this.groupsV2Api
= accountManager
.getGroupsV2Api();
360 if (voiceVerification
) {
361 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(),
362 Optional
.fromNullable(captcha
),
365 accountManager
.requestSmsVerificationCode(false, Optional
.fromNullable(captcha
), Optional
.absent());
368 account
.setRegistered(false);
372 public void updateAccountAttributes() throws IOException
{
373 accountManager
.setAccountAttributes(account
.getSignalingKey(),
374 account
.getSignalProtocolStore().getLocalRegistrationId(),
376 account
.getRegistrationLockPin(),
377 account
.getRegistrationLock(),
378 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
379 unrestrictedUnidentifiedAccess
,
381 discoverableByPhoneNumber
);
384 public void setProfile(String name
, File avatar
) throws IOException
{
385 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
386 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
390 public void unregister() throws IOException
{
391 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
392 // If this is the master device, other users can't send messages to this number anymore.
393 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
394 accountManager
.setGcmId(Optional
.absent());
396 account
.setRegistered(false);
400 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
401 List
<DeviceInfo
> devices
= accountManager
.getDevices();
402 account
.setMultiDevice(devices
.size() > 1);
407 public void removeLinkedDevices(int deviceId
) throws IOException
{
408 accountManager
.removeDevice(deviceId
);
409 List
<DeviceInfo
> devices
= accountManager
.getDevices();
410 account
.setMultiDevice(devices
.size() > 1);
414 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
415 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
417 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
420 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
421 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
422 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
424 accountManager
.addDevice(deviceIdentifier
,
427 Optional
.of(account
.getProfileKey().serialize()),
429 account
.setMultiDevice(true);
433 private List
<PreKeyRecord
> generatePreKeys() {
434 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
436 final int offset
= account
.getPreKeyIdOffset();
437 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
438 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
439 ECKeyPair keyPair
= Curve
.generateKeyPair();
440 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
445 account
.addPreKeys(records
);
451 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
453 ECKeyPair keyPair
= Curve
.generateKeyPair();
454 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(),
455 keyPair
.getPublicKey().serialize());
456 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(),
457 System
.currentTimeMillis(),
461 account
.addSignedPreKey(record);
465 } catch (InvalidKeyException e
) {
466 throw new AssertionError(e
);
470 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
471 verificationCode
= verificationCode
.replace("-", "");
472 account
.setSignalingKey(KeyUtils
.createSignalingKey());
473 // TODO make unrestricted unidentified access configurable
474 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
,
475 account
.getSignalingKey(),
476 account
.getSignalProtocolStore().getLocalRegistrationId(),
480 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
481 unrestrictedUnidentifiedAccess
,
483 discoverableByPhoneNumber
);
485 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
486 // TODO response.isStorageCapable()
487 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
488 account
.setRegistered(true);
489 account
.setUuid(uuid
);
490 account
.setRegistrationLockPin(pin
);
491 account
.getSignalProtocolStore()
492 .saveIdentity(account
.getSelfAddress(),
493 getIdentityKeyPair().getPublicKey(),
494 TrustLevel
.TRUSTED_VERIFIED
);
500 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
501 if (pin
.isPresent()) {
502 account
.setRegistrationLockPin(pin
.get());
503 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
505 account
.setRegistrationLockPin(null);
506 accountManager
.removeRegistrationLockV1();
511 void refreshPreKeys() throws IOException
{
512 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
513 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
514 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
516 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
519 private SignalServiceMessageReceiver
createMessageReceiver() {
520 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
521 serviceConfiguration
).getProfileOperations() : null;
522 return new SignalServiceMessageReceiver(serviceConfiguration
,
524 account
.getUsername(),
525 account
.getPassword(),
526 account
.getDeviceId(),
527 account
.getSignalingKey(),
531 clientZkProfileOperations
);
534 private SignalServiceMessageReceiver
getOrCreateMessageReceiver() {
535 if (messageReceiver
== null) {
536 messageReceiver
= createMessageReceiver();
538 return messageReceiver
;
541 private SignalServiceMessagePipe
getOrCreateMessagePipe() {
542 if (messagePipe
== null) {
543 messagePipe
= getOrCreateMessageReceiver().createMessagePipe();
548 private SignalServiceMessagePipe
getOrCreateUnidentifiedMessagePipe() {
549 if (unidentifiedMessagePipe
== null) {
550 unidentifiedMessagePipe
= getOrCreateMessageReceiver().createUnidentifiedMessagePipe();
552 return unidentifiedMessagePipe
;
555 private SignalServiceMessageSender
createMessageSender() {
556 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
557 serviceConfiguration
).getProfileOperations() : null;
558 final ExecutorService executor
= null;
559 return new SignalServiceMessageSender(serviceConfiguration
,
561 account
.getUsername(),
562 account
.getPassword(),
563 account
.getDeviceId(),
564 account
.getSignalProtocolStore(),
566 account
.isMultiDevice(),
567 Optional
.fromNullable(messagePipe
),
568 Optional
.fromNullable(unidentifiedMessagePipe
),
570 clientZkProfileOperations
,
572 ServiceConfig
.MAX_ENVELOPE_SIZE
);
575 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
) throws IOException
{
576 return profileHelper
.retrieveProfileSync(address
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
579 private SignalProfile
getRecipientProfile(
580 SignalServiceAddress address
582 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
583 if (profileEntry
== null) {
586 long now
= new Date().getTime();
587 // Profiles are cache for 24h before retrieving them again
588 if (!profileEntry
.isRequestPending() && (
589 profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000
591 ProfileKey profileKey
= profileEntry
.getProfileKey();
592 profileEntry
.setRequestPending(true);
593 SignalProfile profile
;
595 profile
= retrieveRecipientProfile(address
, profileKey
);
596 } catch (IOException e
) {
597 logger
.warn("Failed to retrieve profile, ignoring: {}", e
.getMessage());
598 profileEntry
.setRequestPending(false);
601 profileEntry
.setRequestPending(false);
602 account
.getProfileStore()
603 .updateProfile(address
, profileKey
, now
, profile
, profileEntry
.getProfileKeyCredential());
606 return profileEntry
.getProfile();
609 private ProfileKeyCredential
getRecipientProfileKeyCredential(SignalServiceAddress address
) {
610 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
611 if (profileEntry
== null) {
614 if (profileEntry
.getProfileKeyCredential() == null) {
615 ProfileAndCredential profileAndCredential
;
617 profileAndCredential
= profileHelper
.retrieveProfileSync(address
,
618 SignalServiceProfile
.RequestType
.PROFILE_AND_CREDENTIAL
);
619 } catch (IOException e
) {
620 logger
.warn("Failed to retrieve profile key credential, ignoring: {}", e
.getMessage());
624 long now
= new Date().getTime();
625 final ProfileKeyCredential profileKeyCredential
= profileAndCredential
.getProfileKeyCredential().orNull();
626 final SignalProfile profile
= decryptProfile(address
,
627 profileEntry
.getProfileKey(),
628 profileAndCredential
.getProfile());
629 account
.getProfileStore()
630 .updateProfile(address
, profileEntry
.getProfileKey(), now
, profile
, profileKeyCredential
);
631 return profileKeyCredential
;
633 return profileEntry
.getProfileKeyCredential();
636 private SignalProfile
retrieveRecipientProfile(
637 SignalServiceAddress address
, ProfileKey profileKey
638 ) throws IOException
{
639 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
);
641 return decryptProfile(address
, profileKey
, encryptedProfile
);
644 private SignalProfile
decryptProfile(
645 final SignalServiceAddress address
, final ProfileKey profileKey
, final SignalServiceProfile encryptedProfile
647 File avatarFile
= null;
649 avatarFile
= encryptedProfile
.getAvatar() == null
651 : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
652 } catch (Throwable e
) {
653 logger
.warn("Failed to retrieve profile avatar, ignoring: {}", e
.getMessage());
656 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
660 name
= encryptedProfile
.getName() == null
662 : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName())));
663 } catch (IOException e
) {
666 String unidentifiedAccess
;
668 unidentifiedAccess
= encryptedProfile
.getUnidentifiedAccess() == null
669 || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess()))
671 : encryptedProfile
.getUnidentifiedAccess();
672 } catch (IOException e
) {
673 unidentifiedAccess
= null;
675 return new SignalProfile(encryptedProfile
.getIdentityKey(),
679 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
680 encryptedProfile
.getCapabilities());
681 } catch (InvalidCiphertextException e
) {
686 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(GroupId groupId
) throws IOException
{
687 File file
= getGroupAvatarFile(groupId
);
688 if (!file
.exists()) {
689 return Optional
.absent();
692 return Optional
.of(Utils
.createAttachment(file
));
695 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
696 File file
= getContactAvatarFile(number
);
697 if (!file
.exists()) {
698 return Optional
.absent();
701 return Optional
.of(Utils
.createAttachment(file
));
704 private GroupInfo
getGroupForSending(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
705 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
707 throw new GroupNotFoundException(groupId
);
709 if (!g
.isMember(account
.getSelfAddress())) {
710 throw new NotAGroupMemberException(groupId
, g
.getTitle());
715 private GroupInfo
getGroupForUpdating(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
716 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
718 throw new GroupNotFoundException(groupId
);
720 if (!g
.isMember(account
.getSelfAddress()) && !g
.isPendingMember(account
.getSelfAddress())) {
721 throw new NotAGroupMemberException(groupId
, g
.getTitle());
726 public List
<GroupInfo
> getGroups() {
727 return account
.getGroupStore().getGroups();
730 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
731 SignalServiceDataMessage
.Builder messageBuilder
, GroupId groupId
732 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
{
733 final GroupInfo g
= getGroupForSending(groupId
);
735 GroupUtils
.setGroupContext(messageBuilder
, g
);
736 messageBuilder
.withExpiration(g
.getMessageExpirationTime());
738 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
741 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
742 String messageText
, List
<String
> attachments
, GroupId groupId
743 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
744 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
745 .withBody(messageText
);
746 if (attachments
!= null) {
747 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
750 return sendGroupMessage(messageBuilder
, groupId
);
753 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessageReaction(
754 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, GroupId groupId
755 ) throws IOException
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
756 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
758 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
759 targetSentTimestamp
);
760 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
761 .withReaction(reaction
);
763 return sendGroupMessage(messageBuilder
, groupId
);
766 public Pair
<Long
, List
<SendMessageResult
>> sendQuitGroupMessage(GroupId groupId
) throws GroupNotFoundException
, IOException
, NotAGroupMemberException
{
768 SignalServiceDataMessage
.Builder messageBuilder
;
770 final GroupInfo g
= getGroupForUpdating(groupId
);
771 if (g
instanceof GroupInfoV1
) {
772 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
773 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
774 .withId(groupId
.serialize())
776 messageBuilder
= SignalServiceDataMessage
.newBuilder().asGroupMessage(group
);
777 groupInfoV1
.removeMember(account
.getSelfAddress());
778 account
.getGroupStore().updateGroup(groupInfoV1
);
780 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) g
;
781 final Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.leaveGroup(groupInfoV2
);
782 groupInfoV2
.setGroup(groupGroupChangePair
.first());
783 messageBuilder
= getGroupUpdateMessageBuilder(groupInfoV2
, groupGroupChangePair
.second().toByteArray());
784 account
.getGroupStore().updateGroup(groupInfoV2
);
787 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
790 private Pair
<GroupId
, List
<SendMessageResult
>> sendUpdateGroupMessage(
791 GroupId groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
792 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
794 SignalServiceDataMessage
.Builder messageBuilder
;
795 if (groupId
== null) {
797 GroupInfoV2 gv2
= groupHelper
.createGroupV2(name
, members
, avatarFile
);
799 GroupInfoV1 gv1
= new GroupInfoV1(GroupIdV1
.createRandom());
800 gv1
.addMembers(Collections
.singleton(account
.getSelfAddress()));
801 updateGroupV1(gv1
, name
, members
, avatarFile
);
802 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
805 messageBuilder
= getGroupUpdateMessageBuilder(gv2
, null);
809 GroupInfo group
= getGroupForUpdating(groupId
);
810 if (group
instanceof GroupInfoV2
) {
811 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) group
;
813 Pair
<Long
, List
<SendMessageResult
>> result
= null;
814 if (groupInfoV2
.isPendingMember(getSelfAddress())) {
815 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.acceptInvite(groupInfoV2
);
816 result
= sendUpdateGroupMessage(groupInfoV2
,
817 groupGroupChangePair
.first(),
818 groupGroupChangePair
.second());
821 if (members
!= null) {
822 final Set
<SignalServiceAddress
> newMembers
= new HashSet
<>(members
);
823 newMembers
.removeAll(group
.getMembers()
825 .map(this::resolveSignalServiceAddress
)
826 .collect(Collectors
.toSet()));
827 if (newMembers
.size() > 0) {
828 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
830 result
= sendUpdateGroupMessage(groupInfoV2
,
831 groupGroupChangePair
.first(),
832 groupGroupChangePair
.second());
835 if (result
== null || name
!= null || avatarFile
!= null) {
836 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
839 result
= sendUpdateGroupMessage(groupInfoV2
,
840 groupGroupChangePair
.first(),
841 groupGroupChangePair
.second());
844 return new Pair
<>(group
.getGroupId(), result
.second());
846 GroupInfoV1 gv1
= (GroupInfoV1
) group
;
847 updateGroupV1(gv1
, name
, members
, avatarFile
);
848 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
853 account
.getGroupStore().updateGroup(g
);
855 final Pair
<Long
, List
<SendMessageResult
>> result
= sendMessage(messageBuilder
,
856 g
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
857 return new Pair
<>(g
.getGroupId(), result
.second());
860 public Pair
<GroupId
, List
<SendMessageResult
>> joinGroup(
861 GroupInviteLinkUrl inviteLinkUrl
862 ) throws IOException
, GroupLinkNotActiveException
{
863 return sendJoinGroupMessage(inviteLinkUrl
);
866 private Pair
<GroupId
, List
<SendMessageResult
>> sendJoinGroupMessage(
867 GroupInviteLinkUrl inviteLinkUrl
868 ) throws IOException
, GroupLinkNotActiveException
{
869 final DecryptedGroupJoinInfo groupJoinInfo
= groupHelper
.getDecryptedGroupJoinInfo(inviteLinkUrl
.getGroupMasterKey(),
870 inviteLinkUrl
.getPassword());
871 final GroupChange groupChange
= groupHelper
.joinGroup(inviteLinkUrl
.getGroupMasterKey(),
872 inviteLinkUrl
.getPassword(),
874 final GroupInfoV2 group
= getOrMigrateGroup(inviteLinkUrl
.getGroupMasterKey(),
875 groupJoinInfo
.getRevision() + 1,
876 groupChange
.toByteArray());
878 if (group
.getGroup() == null) {
879 // Only requested member, can't send update to group members
880 return new Pair
<>(group
.getGroupId(), List
.of());
883 final Pair
<Long
, List
<SendMessageResult
>> result
= sendUpdateGroupMessage(group
, group
.getGroup(), groupChange
);
885 return new Pair
<>(group
.getGroupId(), result
.second());
888 private Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
889 GroupInfoV2 group
, DecryptedGroup newDecryptedGroup
, GroupChange groupChange
890 ) throws IOException
{
891 group
.setGroup(newDecryptedGroup
);
892 final SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(group
,
893 groupChange
.toByteArray());
894 account
.getGroupStore().updateGroup(group
);
895 return sendMessage(messageBuilder
, group
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
898 private void updateGroupV1(
901 final Collection
<SignalServiceAddress
> members
,
902 final String avatarFile
903 ) throws IOException
{
908 if (members
!= null) {
909 final Set
<String
> newE164Members
= new HashSet
<>();
910 for (SignalServiceAddress member
: members
) {
911 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
914 newE164Members
.add(member
.getNumber().get());
917 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
918 if (contacts
.size() != newE164Members
.size()) {
919 // Some of the new members are not registered on Signal
920 for (ContactTokenDetails contact
: contacts
) {
921 newE164Members
.remove(contact
.getNumber());
923 throw new IOException("Failed to add members "
924 + Util
.join(", ", newE164Members
)
925 + " to group: Not registered on Signal");
928 g
.addMembers(members
);
931 if (avatarFile
!= null) {
932 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
933 File aFile
= getGroupAvatarFile(g
.getGroupId());
934 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
938 Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
939 GroupIdV1 groupId
, SignalServiceAddress recipient
940 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
942 GroupInfo group
= getGroupForSending(groupId
);
943 if (!(group
instanceof GroupInfoV1
)) {
944 throw new RuntimeException("Received an invalid group request for a v2 group!");
946 g
= (GroupInfoV1
) group
;
948 if (!g
.isMember(recipient
)) {
949 throw new NotAGroupMemberException(groupId
, g
.name
);
952 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
954 // Send group message only to the recipient who requested it
955 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
958 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV1 g
) throws AttachmentInvalidException
{
959 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
960 .withId(g
.getGroupId().serialize())
962 .withMembers(new ArrayList
<>(g
.getMembers()));
964 File aFile
= getGroupAvatarFile(g
.getGroupId());
965 if (aFile
.exists()) {
967 group
.withAvatar(Utils
.createAttachment(aFile
));
968 } catch (IOException e
) {
969 throw new AttachmentInvalidException(aFile
.toString(), e
);
973 return SignalServiceDataMessage
.newBuilder()
974 .asGroupMessage(group
.build())
975 .withExpiration(g
.getMessageExpirationTime());
978 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV2 g
, byte[] signedGroupChange
) {
979 SignalServiceGroupV2
.Builder group
= SignalServiceGroupV2
.newBuilder(g
.getMasterKey())
980 .withRevision(g
.getGroup().getRevision())
981 .withSignedGroupChange(signedGroupChange
);
982 return SignalServiceDataMessage
.newBuilder()
983 .asGroupMessage(group
.build())
984 .withExpiration(g
.getMessageExpirationTime());
987 Pair
<Long
, List
<SendMessageResult
>> sendGroupInfoRequest(
988 GroupIdV1 groupId
, SignalServiceAddress recipient
989 ) throws IOException
{
990 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
991 .withId(groupId
.serialize());
993 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
994 .asGroupMessage(group
.build());
996 // Send group info request message to the recipient who sent us a message with this groupId
997 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
1001 SignalServiceAddress remoteAddress
, long messageId
1002 ) throws IOException
, UntrustedIdentityException
{
1003 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
1004 Collections
.singletonList(messageId
),
1005 System
.currentTimeMillis());
1007 createMessageSender().sendReceipt(remoteAddress
,
1008 unidentifiedAccessHelper
.getAccessFor(remoteAddress
),
1012 public Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1013 String messageText
, List
<String
> attachments
, List
<String
> recipients
1014 ) throws IOException
, AttachmentInvalidException
, InvalidNumberException
{
1015 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1016 .withBody(messageText
);
1017 if (attachments
!= null) {
1018 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
1020 // Upload attachments here, so we only upload once even for multiple recipients
1021 SignalServiceMessageSender messageSender
= createMessageSender();
1022 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
1023 for (SignalServiceAttachment attachment
: attachmentStreams
) {
1024 if (attachment
.isStream()) {
1025 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
1026 } else if (attachment
.isPointer()) {
1027 attachmentPointers
.add(attachment
.asPointer());
1031 messageBuilder
.withAttachments(attachmentPointers
);
1033 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
1036 public Pair
<Long
, List
<SendMessageResult
>> sendMessageReaction(
1037 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, List
<String
> recipients
1038 ) throws IOException
, InvalidNumberException
{
1039 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
1041 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
1042 targetSentTimestamp
);
1043 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1044 .withReaction(reaction
);
1045 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
1048 public Pair
<Long
, List
<SendMessageResult
>> sendEndSessionMessage(List
<String
> recipients
) throws IOException
, InvalidNumberException
{
1049 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().asEndSessionMessage();
1051 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
1053 return sendMessage(messageBuilder
, signalServiceAddresses
);
1054 } catch (Exception e
) {
1055 for (SignalServiceAddress address
: signalServiceAddresses
) {
1056 handleEndSession(address
);
1063 public String
getContactName(String number
) throws InvalidNumberException
{
1064 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
1065 if (contact
== null) {
1068 return contact
.name
;
1072 public void setContactName(String number
, String name
) throws InvalidNumberException
{
1073 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1074 ContactInfo contact
= account
.getContactStore().getContact(address
);
1075 if (contact
== null) {
1076 contact
= new ContactInfo(address
);
1078 contact
.name
= name
;
1079 account
.getContactStore().updateContact(contact
);
1083 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
1084 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
1087 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
1088 ContactInfo contact
= account
.getContactStore().getContact(address
);
1089 if (contact
== null) {
1090 contact
= new ContactInfo(address
);
1092 contact
.blocked
= blocked
;
1093 account
.getContactStore().updateContact(contact
);
1097 public void setGroupBlocked(final GroupId groupId
, final boolean blocked
) throws GroupNotFoundException
{
1098 GroupInfo group
= getGroup(groupId
);
1099 if (group
== null) {
1100 throw new GroupNotFoundException(groupId
);
1103 group
.setBlocked(blocked
);
1104 account
.getGroupStore().updateGroup(group
);
1108 public Pair
<GroupId
, List
<SendMessageResult
>> updateGroup(
1109 GroupId groupId
, String name
, List
<String
> members
, String avatar
1110 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
1111 return sendUpdateGroupMessage(groupId
,
1113 members
== null ?
null : getSignalServiceAddresses(members
),
1118 * Change the expiration timer for a contact
1120 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
1121 ContactInfo contact
= account
.getContactStore().getContact(address
);
1122 contact
.messageExpirationTime
= messageExpirationTimer
;
1123 account
.getContactStore().updateContact(contact
);
1124 sendExpirationTimerUpdate(address
);
1128 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
1129 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1130 .asExpirationUpdate();
1131 sendMessage(messageBuilder
, Collections
.singleton(address
));
1135 * Change the expiration timer for a contact
1137 public void setExpirationTimer(
1138 String number
, int messageExpirationTimer
1139 ) throws IOException
, InvalidNumberException
{
1140 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1141 setExpirationTimer(address
, messageExpirationTimer
);
1145 * Change the expiration timer for a group
1147 public void setExpirationTimer(GroupId groupId
, int messageExpirationTimer
) {
1148 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
1149 if (g
instanceof GroupInfoV1
) {
1150 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
1151 groupInfoV1
.messageExpirationTime
= messageExpirationTimer
;
1152 account
.getGroupStore().updateGroup(groupInfoV1
);
1154 throw new RuntimeException("TODO Not implemented!");
1159 * Upload the sticker pack from path.
1161 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
1162 * @return if successful, returns the URL to install the sticker pack in the signal app
1164 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
1165 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
1167 SignalServiceMessageSender messageSender
= createMessageSender();
1169 byte[] packKey
= KeyUtils
.createStickerUploadKey();
1170 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
1172 Sticker sticker
= new Sticker(Hex
.fromStringCondensed(packId
), packKey
);
1173 account
.getStickerStore().updateSticker(sticker
);
1177 return new URI("https",
1180 "pack_id=" + URLEncoder
.encode(packId
, StandardCharsets
.UTF_8
) + "&pack_key=" + URLEncoder
.encode(
1181 Hex
.toStringCondensed(packKey
),
1182 StandardCharsets
.UTF_8
)).toString();
1183 } catch (URISyntaxException e
) {
1184 throw new AssertionError(e
);
1188 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(
1190 ) throws IOException
, StickerPackInvalidException
{
1192 String rootPath
= null;
1194 final File file
= new File(path
);
1195 if (file
.getName().endsWith(".zip")) {
1196 zip
= new ZipFile(file
);
1197 } else if (file
.getName().equals("manifest.json")) {
1198 rootPath
= file
.getParent();
1200 throw new StickerPackInvalidException("Could not find manifest.json");
1203 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
1205 if (pack
.stickers
== null) {
1206 throw new StickerPackInvalidException("Must set a 'stickers' field.");
1209 if (pack
.stickers
.isEmpty()) {
1210 throw new StickerPackInvalidException("Must include stickers.");
1213 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
1214 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
1215 if (sticker
.file
== null) {
1216 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
1219 Pair
<InputStream
, Long
> data
;
1221 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
1222 } catch (IOException ignored
) {
1223 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
1226 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
1227 StickerInfo stickerInfo
= new StickerInfo(data
.first(),
1229 Optional
.fromNullable(sticker
.emoji
).or(""),
1231 stickers
.add(stickerInfo
);
1234 StickerInfo cover
= null;
1235 if (pack
.cover
!= null) {
1236 if (pack
.cover
.file
== null) {
1237 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
1240 Pair
<InputStream
, Long
> data
;
1242 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
1243 } catch (IOException ignored
) {
1244 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
1247 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
1248 cover
= new StickerInfo(data
.first(),
1250 Optional
.fromNullable(pack
.cover
.emoji
).or(""),
1254 return new SignalServiceStickerManifestUpload(pack
.title
, pack
.author
, cover
, stickers
);
1257 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
1258 InputStream inputStream
;
1260 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
1262 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
1264 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
1267 private static Pair
<InputStream
, Long
> getInputStreamAndLength(
1268 final String rootPath
, final ZipFile zip
, final String subfile
1269 ) throws IOException
{
1271 final ZipEntry entry
= zip
.getEntry(subfile
);
1272 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
1274 final File file
= new File(rootPath
, subfile
);
1275 return new Pair
<>(new FileInputStream(file
), file
.length());
1279 void requestSyncGroups() throws IOException
{
1280 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1281 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
)
1283 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1285 sendSyncMessage(message
);
1286 } catch (UntrustedIdentityException e
) {
1287 e
.printStackTrace();
1291 void requestSyncContacts() throws IOException
{
1292 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1293 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
)
1295 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1297 sendSyncMessage(message
);
1298 } catch (UntrustedIdentityException e
) {
1299 e
.printStackTrace();
1303 void requestSyncBlocked() throws IOException
{
1304 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1305 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
)
1307 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1309 sendSyncMessage(message
);
1310 } catch (UntrustedIdentityException e
) {
1311 e
.printStackTrace();
1315 void requestSyncConfiguration() throws IOException
{
1316 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1317 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
)
1319 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1321 sendSyncMessage(message
);
1322 } catch (UntrustedIdentityException e
) {
1323 e
.printStackTrace();
1327 private byte[] getSenderCertificate() {
1328 // TODO support UUID capable sender certificates
1329 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
1332 certificate
= accountManager
.getSenderCertificate();
1333 } catch (IOException e
) {
1334 logger
.warn("Failed to get sender certificate, ignoring: {}", e
.getMessage());
1337 // TODO cache for a day
1341 private void sendSyncMessage(SignalServiceSyncMessage message
) throws IOException
, UntrustedIdentityException
{
1342 SignalServiceMessageSender messageSender
= createMessageSender();
1344 messageSender
.sendMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
1345 } catch (UntrustedIdentityException e
) {
1346 account
.getSignalProtocolStore()
1347 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1349 TrustLevel
.UNTRUSTED
);
1354 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1355 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1356 final Set
<SignalServiceAddress
> missingUuids
= new HashSet
<>();
1358 for (String number
: numbers
) {
1359 final SignalServiceAddress resolvedAddress
= canonicalizeAndResolveSignalServiceAddress(number
);
1360 if (resolvedAddress
.getUuid().isPresent()) {
1361 signalServiceAddresses
.add(resolvedAddress
);
1363 missingUuids
.add(resolvedAddress
);
1367 Map
<String
, UUID
> registeredUsers
;
1369 registeredUsers
= accountManager
.getRegisteredUsers(getIasKeyStore(),
1370 missingUuids
.stream().map(a
-> a
.getNumber().get()).collect(Collectors
.toSet()),
1372 } catch (IOException
| Quote
.InvalidQuoteFormatException
| UnauthenticatedQuoteException
| SignatureException
| UnauthenticatedResponseException e
) {
1373 logger
.warn("Failed to resolve uuids from server, ignoring: {}", e
.getMessage());
1374 registeredUsers
= new HashMap
<>();
1377 for (SignalServiceAddress address
: missingUuids
) {
1378 final String number
= address
.getNumber().get();
1379 if (registeredUsers
.containsKey(number
)) {
1380 final SignalServiceAddress newAddress
= resolveSignalServiceAddress(new SignalServiceAddress(
1381 registeredUsers
.get(number
),
1383 signalServiceAddresses
.add(newAddress
);
1385 signalServiceAddresses
.add(address
);
1389 return signalServiceAddresses
;
1392 private Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1393 SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
1394 ) throws IOException
{
1395 recipients
= recipients
.stream().map(this::resolveSignalServiceAddress
).collect(Collectors
.toSet());
1396 final long timestamp
= System
.currentTimeMillis();
1397 messageBuilder
.withTimestamp(timestamp
);
1398 getOrCreateMessagePipe();
1399 getOrCreateUnidentifiedMessagePipe();
1400 SignalServiceDataMessage message
= null;
1402 message
= messageBuilder
.build();
1403 if (message
.getGroupContext().isPresent()) {
1405 SignalServiceMessageSender messageSender
= createMessageSender();
1406 final boolean isRecipientUpdate
= false;
1407 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
),
1408 unidentifiedAccessHelper
.getAccessFor(recipients
),
1411 for (SendMessageResult r
: result
) {
1412 if (r
.getIdentityFailure() != null) {
1413 account
.getSignalProtocolStore()
1414 .saveIdentity(r
.getAddress(),
1415 r
.getIdentityFailure().getIdentityKey(),
1416 TrustLevel
.UNTRUSTED
);
1419 return new Pair
<>(timestamp
, result
);
1420 } catch (UntrustedIdentityException e
) {
1421 account
.getSignalProtocolStore()
1422 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1424 TrustLevel
.UNTRUSTED
);
1425 return new Pair
<>(timestamp
, Collections
.emptyList());
1428 // Send to all individually, so sync messages are sent correctly
1429 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1430 for (SignalServiceAddress address
: recipients
) {
1431 ContactInfo contact
= account
.getContactStore().getContact(address
);
1432 if (contact
!= null) {
1433 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1434 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1436 messageBuilder
.withExpiration(0);
1437 messageBuilder
.withProfileKey(null);
1439 message
= messageBuilder
.build();
1440 if (address
.matches(account
.getSelfAddress())) {
1441 results
.add(sendSelfMessage(message
));
1443 results
.add(sendMessage(address
, message
));
1446 return new Pair
<>(timestamp
, results
);
1449 if (message
!= null && message
.isEndSession()) {
1450 for (SignalServiceAddress recipient
: recipients
) {
1451 handleEndSession(recipient
);
1458 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1459 SignalServiceMessageSender messageSender
= createMessageSender();
1461 SignalServiceAddress recipient
= account
.getSelfAddress();
1463 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= unidentifiedAccessHelper
.getAccessFor(recipient
);
1464 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1465 message
.getTimestamp(),
1467 message
.getExpiresInSeconds(),
1468 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1470 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1473 long startTime
= System
.currentTimeMillis();
1474 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1475 return SendMessageResult
.success(recipient
,
1476 unidentifiedAccess
.isPresent(),
1478 System
.currentTimeMillis() - startTime
);
1479 } catch (UntrustedIdentityException e
) {
1480 account
.getSignalProtocolStore()
1481 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1483 TrustLevel
.UNTRUSTED
);
1484 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1488 private SendMessageResult
sendMessage(
1489 SignalServiceAddress address
, SignalServiceDataMessage message
1490 ) throws IOException
{
1491 SignalServiceMessageSender messageSender
= createMessageSender();
1494 return messageSender
.sendMessage(address
, unidentifiedAccessHelper
.getAccessFor(address
), message
);
1495 } catch (UntrustedIdentityException e
) {
1496 account
.getSignalProtocolStore()
1497 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1499 TrustLevel
.UNTRUSTED
);
1500 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1504 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1505 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(),
1506 account
.getSignalProtocolStore(),
1507 Utils
.getCertificateValidator());
1509 return cipher
.decrypt(envelope
);
1510 } catch (ProtocolUntrustedIdentityException e
) {
1511 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1512 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
1514 account
.getSignalProtocolStore()
1515 .saveIdentity(resolveSignalServiceAddress(identityException
.getName()),
1516 identityException
.getUntrustedIdentity(),
1517 TrustLevel
.UNTRUSTED
);
1518 throw identityException
;
1520 throw new AssertionError(e
);
1524 private void handleEndSession(SignalServiceAddress source
) {
1525 account
.getSignalProtocolStore().deleteAllSessions(source
);
1528 private static int currentTimeDays() {
1529 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
1532 private GroupsV2AuthorizationString
getGroupAuthForToday(
1533 final GroupSecretParams groupSecretParams
1534 ) throws IOException
{
1535 final int today
= currentTimeDays();
1536 // Returns credentials for the next 7 days
1537 final HashMap
<Integer
, AuthCredentialResponse
> credentials
= groupsV2Api
.getCredentials(today
);
1538 // TODO cache credentials until they expire
1539 AuthCredentialResponse authCredentialResponse
= credentials
.get(today
);
1541 return groupsV2Api
.getGroupsV2AuthorizationString(account
.getUuid(),
1544 authCredentialResponse
);
1545 } catch (VerificationFailedException e
) {
1546 throw new IOException(e
);
1550 private List
<HandleAction
> handleSignalServiceDataMessage(
1551 SignalServiceDataMessage message
,
1553 SignalServiceAddress source
,
1554 SignalServiceAddress destination
,
1555 boolean ignoreAttachments
1557 List
<HandleAction
> actions
= new ArrayList
<>();
1558 if (message
.getGroupContext().isPresent()) {
1559 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1560 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1561 GroupIdV1 groupId
= GroupId
.v1(groupInfo
.getGroupId());
1562 GroupInfo group
= account
.getGroupStore().getGroup(groupId
);
1563 if (group
== null || group
instanceof GroupInfoV1
) {
1564 GroupInfoV1 groupV1
= (GroupInfoV1
) group
;
1565 switch (groupInfo
.getType()) {
1567 if (groupV1
== null) {
1568 groupV1
= new GroupInfoV1(groupId
);
1571 if (groupInfo
.getAvatar().isPresent()) {
1572 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1573 if (avatar
.isPointer()) {
1575 retrieveGroupAvatarAttachment(avatar
.asPointer(), groupV1
.getGroupId());
1576 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1577 logger
.warn("Failed to retrieve avatar for group {}, ignoring: {}",
1584 if (groupInfo
.getName().isPresent()) {
1585 groupV1
.name
= groupInfo
.getName().get();
1588 if (groupInfo
.getMembers().isPresent()) {
1589 groupV1
.addMembers(groupInfo
.getMembers()
1592 .map(this::resolveSignalServiceAddress
)
1593 .collect(Collectors
.toSet()));
1596 account
.getGroupStore().updateGroup(groupV1
);
1600 if (groupV1
== null && !isSync
) {
1601 actions
.add(new SendGroupInfoRequestAction(source
, groupId
));
1605 if (groupV1
!= null) {
1606 groupV1
.removeMember(source
);
1607 account
.getGroupStore().updateGroup(groupV1
);
1612 if (groupV1
!= null && !isSync
) {
1613 actions
.add(new SendGroupUpdateAction(source
, groupV1
.getGroupId()));
1618 // Received a group v1 message for a v2 group
1621 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1622 final SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1623 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1625 getOrMigrateGroup(groupMasterKey
,
1626 groupContext
.getRevision(),
1627 groupContext
.hasSignedGroupChange() ? groupContext
.getSignedGroupChange() : null);
1631 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1632 if (conversationPartnerAddress
!= null && message
.isEndSession()) {
1633 handleEndSession(conversationPartnerAddress
);
1635 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1636 if (message
.getGroupContext().isPresent()) {
1637 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1638 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1639 GroupInfoV1 group
= account
.getGroupStore().getOrCreateGroupV1(GroupId
.v1(groupInfo
.getGroupId()));
1640 if (group
!= null) {
1641 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1642 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1643 account
.getGroupStore().updateGroup(group
);
1646 } else if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1647 // disappearing message timer already stored in the DecryptedGroup
1649 } else if (conversationPartnerAddress
!= null) {
1650 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1651 if (contact
== null) {
1652 contact
= new ContactInfo(conversationPartnerAddress
);
1654 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1655 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1656 account
.getContactStore().updateContact(contact
);
1660 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1661 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1662 if (attachment
.isPointer()) {
1664 retrieveAttachment(attachment
.asPointer());
1665 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1666 logger
.warn("Failed to retrieve attachment ({}), ignoring: {}",
1667 attachment
.asPointer().getRemoteId(),
1673 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1674 final ProfileKey profileKey
;
1676 profileKey
= new ProfileKey(message
.getProfileKey().get());
1677 } catch (InvalidInputException e
) {
1678 throw new AssertionError(e
);
1680 if (source
.matches(account
.getSelfAddress())) {
1681 this.account
.setProfileKey(profileKey
);
1683 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1685 if (message
.getPreviews().isPresent()) {
1686 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1687 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1688 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1689 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1691 retrieveAttachment(attachment
);
1692 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1693 logger
.warn("Failed to retrieve preview image ({}), ignoring: {}",
1694 attachment
.getRemoteId(),
1700 if (message
.getQuote().isPresent()) {
1701 final SignalServiceDataMessage
.Quote quote
= message
.getQuote().get();
1703 for (SignalServiceDataMessage
.Quote
.QuotedAttachment quotedAttachment
: quote
.getAttachments()) {
1704 final SignalServiceAttachment attachment
= quotedAttachment
.getThumbnail();
1705 if (attachment
!= null && attachment
.isPointer()) {
1707 retrieveAttachment(attachment
.asPointer());
1708 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1709 logger
.warn("Failed to retrieve quote attachment thumbnail ({}), ignoring: {}",
1710 attachment
.asPointer().getRemoteId(),
1716 if (message
.getSticker().isPresent()) {
1717 final SignalServiceDataMessage
.Sticker messageSticker
= message
.getSticker().get();
1718 Sticker sticker
= account
.getStickerStore().getSticker(messageSticker
.getPackId());
1719 if (sticker
== null) {
1720 sticker
= new Sticker(messageSticker
.getPackId(), messageSticker
.getPackKey());
1721 account
.getStickerStore().updateSticker(sticker
);
1727 private GroupInfoV2
getOrMigrateGroup(
1728 final GroupMasterKey groupMasterKey
, final int revision
, final byte[] signedGroupChange
1730 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
1732 GroupIdV2 groupId
= GroupUtils
.getGroupIdV2(groupSecretParams
);
1733 GroupInfo groupInfo
= account
.getGroupStore().getGroup(groupId
);
1734 final GroupInfoV2 groupInfoV2
;
1735 if (groupInfo
instanceof GroupInfoV1
) {
1736 // Received a v2 group message for a v1 group, we need to locally migrate the group
1737 account
.getGroupStore().deleteGroup(groupInfo
.getGroupId());
1738 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1739 logger
.info("Locally migrated group {} to group v2, id: {}",
1740 groupInfo
.getGroupId().toBase64(),
1741 groupInfoV2
.getGroupId().toBase64());
1742 } else if (groupInfo
instanceof GroupInfoV2
) {
1743 groupInfoV2
= (GroupInfoV2
) groupInfo
;
1745 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1748 if (groupInfoV2
.getGroup() == null || groupInfoV2
.getGroup().getRevision() < revision
) {
1749 DecryptedGroup group
= null;
1750 if (signedGroupChange
!= null
1751 && groupInfoV2
.getGroup() != null
1752 && groupInfoV2
.getGroup().getRevision() + 1 == revision
) {
1753 group
= groupHelper
.getUpdatedDecryptedGroup(groupInfoV2
.getGroup(), signedGroupChange
, groupMasterKey
);
1755 if (group
== null) {
1756 group
= groupHelper
.getDecryptedGroup(groupSecretParams
);
1758 if (group
!= null) {
1759 storeProfileKeysFromMembers(group
);
1760 final String avatar
= group
.getAvatar();
1761 if (avatar
!= null && !avatar
.isEmpty()) {
1763 retrieveGroupAvatar(groupId
, groupSecretParams
, avatar
);
1764 } catch (IOException e
) {
1765 logger
.warn("Failed to download group avatar, ignoring: {}", e
.getMessage());
1769 groupInfoV2
.setGroup(group
);
1770 account
.getGroupStore().updateGroup(groupInfoV2
);
1776 private void storeProfileKeysFromMembers(final DecryptedGroup group
) {
1777 for (DecryptedMember member
: group
.getMembersList()) {
1778 final SignalServiceAddress address
= resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil
.parseOrThrow(
1779 member
.getUuid().toByteArray()), null));
1781 account
.getProfileStore()
1782 .storeProfileKey(address
, new ProfileKey(member
.getProfileKey().toByteArray()));
1783 } catch (InvalidInputException ignored
) {
1788 private void retryFailedReceivedMessages(
1789 ReceiveMessageHandler handler
, boolean ignoreAttachments
1791 final File cachePath
= new File(getMessageCachePath());
1792 if (!cachePath
.exists()) {
1795 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1796 if (!dir
.isDirectory()) {
1797 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1801 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1802 if (!fileEntry
.isFile()) {
1805 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1807 // Try to delete directory if empty
1812 private void retryFailedReceivedMessage(
1813 final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
1815 SignalServiceEnvelope envelope
;
1817 envelope
= Utils
.loadEnvelope(fileEntry
);
1818 if (envelope
== null) {
1821 } catch (IOException e
) {
1822 e
.printStackTrace();
1825 SignalServiceContent content
= null;
1826 if (!envelope
.isReceipt()) {
1828 content
= decryptMessage(envelope
);
1829 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1831 } catch (Exception er
) {
1832 // All other errors are not recoverable, so delete the cached message
1834 Files
.delete(fileEntry
.toPath());
1835 } catch (IOException e
) {
1836 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry
, e
.getMessage());
1840 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1841 for (HandleAction action
: actions
) {
1843 action
.execute(this);
1844 } catch (Throwable e
) {
1845 e
.printStackTrace();
1850 handler
.handleMessage(envelope
, content
, null);
1852 Files
.delete(fileEntry
.toPath());
1853 } catch (IOException e
) {
1854 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry
, e
.getMessage());
1858 public void receiveMessages(
1861 boolean returnOnTimeout
,
1862 boolean ignoreAttachments
,
1863 ReceiveMessageHandler handler
1864 ) throws IOException
{
1865 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1867 Set
<HandleAction
> queuedActions
= null;
1869 getOrCreateMessagePipe();
1871 boolean hasCaughtUpWithOldMessages
= false;
1874 SignalServiceEnvelope envelope
;
1875 SignalServiceContent content
= null;
1876 Exception exception
= null;
1877 final long now
= new Date().getTime();
1879 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1880 // store message on disk, before acknowledging receipt to the server
1882 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1883 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1884 Utils
.storeEnvelope(envelope1
, cacheFile
);
1885 } catch (IOException e
) {
1886 logger
.warn("Failed to store encrypted message in disk cache, ignoring: {}", e
.getMessage());
1889 if (result
.isPresent()) {
1890 envelope
= result
.get();
1892 // Received indicator that server queue is empty
1893 hasCaughtUpWithOldMessages
= true;
1895 if (queuedActions
!= null) {
1896 for (HandleAction action
: queuedActions
) {
1898 action
.execute(this);
1899 } catch (Throwable e
) {
1900 e
.printStackTrace();
1904 queuedActions
.clear();
1905 queuedActions
= null;
1908 // Continue to wait another timeout for new messages
1911 } catch (TimeoutException e
) {
1912 if (returnOnTimeout
) return;
1914 } catch (InvalidVersionException e
) {
1915 logger
.warn("Error while receiving messages, ignoring: {}", e
.getMessage());
1919 if (envelope
.hasSource()) {
1920 // Store uuid if we don't have it already
1921 SignalServiceAddress source
= envelope
.getSourceAddress();
1922 resolveSignalServiceAddress(source
);
1924 if (!envelope
.isReceipt()) {
1926 content
= decryptMessage(envelope
);
1927 } catch (Exception e
) {
1930 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1931 if (hasCaughtUpWithOldMessages
) {
1932 for (HandleAction action
: actions
) {
1934 action
.execute(this);
1935 } catch (Throwable e
) {
1936 e
.printStackTrace();
1940 if (queuedActions
== null) {
1941 queuedActions
= new HashSet
<>();
1943 queuedActions
.addAll(actions
);
1947 if (!isMessageBlocked(envelope
, content
)) {
1948 handler
.handleMessage(envelope
, content
, exception
);
1950 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1951 File cacheFile
= null;
1953 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1954 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1955 Files
.delete(cacheFile
.toPath());
1956 // Try to delete directory if empty
1957 new File(getMessageCachePath()).delete();
1958 } catch (IOException e
) {
1959 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", cacheFile
, e
.getMessage());
1965 private boolean isMessageBlocked(
1966 SignalServiceEnvelope envelope
, SignalServiceContent content
1968 SignalServiceAddress source
;
1969 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1970 source
= envelope
.getSourceAddress();
1971 } else if (content
!= null) {
1972 source
= content
.getSender();
1976 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1977 if (sourceContact
!= null && sourceContact
.blocked
) {
1981 if (content
!= null && content
.getDataMessage().isPresent()) {
1982 SignalServiceDataMessage message
= content
.getDataMessage().get();
1983 if (message
.getGroupContext().isPresent()) {
1984 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1985 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1986 if (groupInfo
.getType() != SignalServiceGroup
.Type
.DELIVER
) {
1990 GroupId groupId
= GroupUtils
.getGroupId(message
.getGroupContext().get());
1991 GroupInfo group
= account
.getGroupStore().getGroup(groupId
);
1992 if (group
!= null && group
.isBlocked()) {
2000 private List
<HandleAction
> handleMessage(
2001 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
2003 List
<HandleAction
> actions
= new ArrayList
<>();
2004 if (content
!= null) {
2005 final SignalServiceAddress sender
;
2006 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
2007 sender
= envelope
.getSourceAddress();
2009 sender
= content
.getSender();
2011 // Store uuid if we don't have it already
2012 resolveSignalServiceAddress(sender
);
2014 if (content
.getDataMessage().isPresent()) {
2015 SignalServiceDataMessage message
= content
.getDataMessage().get();
2017 if (content
.isNeedsReceipt()) {
2018 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
2021 actions
.addAll(handleSignalServiceDataMessage(message
,
2024 account
.getSelfAddress(),
2025 ignoreAttachments
));
2027 if (content
.getSyncMessage().isPresent()) {
2028 account
.setMultiDevice(true);
2029 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
2030 if (syncMessage
.getSent().isPresent()) {
2031 SentTranscriptMessage message
= syncMessage
.getSent().get();
2032 final SignalServiceAddress destination
= message
.getDestination().orNull();
2033 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
2037 ignoreAttachments
));
2039 if (syncMessage
.getRequest().isPresent()) {
2040 RequestMessage rm
= syncMessage
.getRequest().get();
2041 if (rm
.isContactsRequest()) {
2042 actions
.add(SendSyncContactsAction
.create());
2044 if (rm
.isGroupsRequest()) {
2045 actions
.add(SendSyncGroupsAction
.create());
2047 if (rm
.isBlockedListRequest()) {
2048 actions
.add(SendSyncBlockedListAction
.create());
2050 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
2052 if (syncMessage
.getGroups().isPresent()) {
2053 File tmpFile
= null;
2055 tmpFile
= IOUtils
.createTempFile();
2056 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups()
2058 .asPointer(), tmpFile
)) {
2059 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
2061 while ((g
= s
.read()) != null) {
2062 GroupInfoV1 syncGroup
= account
.getGroupStore()
2063 .getOrCreateGroupV1(GroupId
.v1(g
.getId()));
2064 if (syncGroup
!= null) {
2065 if (g
.getName().isPresent()) {
2066 syncGroup
.name
= g
.getName().get();
2068 syncGroup
.addMembers(g
.getMembers()
2070 .map(this::resolveSignalServiceAddress
)
2071 .collect(Collectors
.toSet()));
2072 if (!g
.isActive()) {
2073 syncGroup
.removeMember(account
.getSelfAddress());
2075 // Add ourself to the member set as it's marked as active
2076 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
2078 syncGroup
.blocked
= g
.isBlocked();
2079 if (g
.getColor().isPresent()) {
2080 syncGroup
.color
= g
.getColor().get();
2083 if (g
.getAvatar().isPresent()) {
2084 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.getGroupId());
2086 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
2087 syncGroup
.archived
= g
.isArchived();
2088 account
.getGroupStore().updateGroup(syncGroup
);
2092 } catch (Exception e
) {
2093 logger
.warn("Failed to handle received sync groups “{}”, ignoring: {}",
2096 e
.printStackTrace();
2098 if (tmpFile
!= null) {
2100 Files
.delete(tmpFile
.toPath());
2101 } catch (IOException e
) {
2102 logger
.warn("Failed to delete received groups temp file “{}”, ignoring: {}",
2109 if (syncMessage
.getBlockedList().isPresent()) {
2110 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
2111 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
2112 setContactBlocked(resolveSignalServiceAddress(address
), true);
2114 for (GroupId groupId
: blockedListMessage
.getGroupIds()
2116 .map(GroupId
::unknownVersion
)
2117 .collect(Collectors
.toSet())) {
2119 setGroupBlocked(groupId
, true);
2120 } catch (GroupNotFoundException e
) {
2121 logger
.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
2122 groupId
.toBase64());
2126 if (syncMessage
.getContacts().isPresent()) {
2127 File tmpFile
= null;
2129 tmpFile
= IOUtils
.createTempFile();
2130 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
2131 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream()
2132 .asPointer(), tmpFile
)) {
2133 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
2134 if (contactsMessage
.isComplete()) {
2135 account
.getContactStore().clear();
2138 while ((c
= s
.read()) != null) {
2139 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
2140 account
.setProfileKey(c
.getProfileKey().get());
2142 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
2143 ContactInfo contact
= account
.getContactStore().getContact(address
);
2144 if (contact
== null) {
2145 contact
= new ContactInfo(address
);
2147 if (c
.getName().isPresent()) {
2148 contact
.name
= c
.getName().get();
2150 if (c
.getColor().isPresent()) {
2151 contact
.color
= c
.getColor().get();
2153 if (c
.getProfileKey().isPresent()) {
2154 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
2156 if (c
.getVerified().isPresent()) {
2157 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
2158 account
.getSignalProtocolStore()
2159 .setIdentityTrustLevel(verifiedMessage
.getDestination(),
2160 verifiedMessage
.getIdentityKey(),
2161 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2163 if (c
.getExpirationTimer().isPresent()) {
2164 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
2166 contact
.blocked
= c
.isBlocked();
2167 contact
.inboxPosition
= c
.getInboxPosition().orNull();
2168 contact
.archived
= c
.isArchived();
2169 account
.getContactStore().updateContact(contact
);
2171 if (c
.getAvatar().isPresent()) {
2172 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
2176 } catch (Exception e
) {
2177 e
.printStackTrace();
2179 if (tmpFile
!= null) {
2181 Files
.delete(tmpFile
.toPath());
2182 } catch (IOException e
) {
2183 logger
.warn("Failed to delete received contacts temp file “{}”, ignoring: {}",
2190 if (syncMessage
.getVerified().isPresent()) {
2191 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
2192 account
.getSignalProtocolStore()
2193 .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()),
2194 verifiedMessage
.getIdentityKey(),
2195 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2197 if (syncMessage
.getStickerPackOperations().isPresent()) {
2198 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations()
2200 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
2201 if (!m
.getPackId().isPresent()) {
2204 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
2205 if (sticker
== null) {
2206 if (!m
.getPackKey().isPresent()) {
2209 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
2211 sticker
.setInstalled(!m
.getType().isPresent()
2212 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
2213 account
.getStickerStore().updateSticker(sticker
);
2216 if (syncMessage
.getConfiguration().isPresent()) {
2224 private File
getContactAvatarFile(String number
) {
2225 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
2228 private File
retrieveContactAvatarAttachment(
2229 SignalServiceAttachment attachment
, String number
2230 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2231 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2232 if (attachment
.isPointer()) {
2233 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2234 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
2236 SignalServiceAttachmentStream stream
= attachment
.asStream();
2237 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
2241 private File
getGroupAvatarFile(GroupId groupId
) {
2242 return new File(pathConfig
.getAvatarsPath(), "group-" + groupId
.toBase64().replace("/", "_"));
2245 private File
retrieveGroupAvatarAttachment(
2246 SignalServiceAttachment attachment
, GroupId groupId
2247 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2248 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2249 if (attachment
.isPointer()) {
2250 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2251 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
2253 SignalServiceAttachmentStream stream
= attachment
.asStream();
2254 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
2258 private File
retrieveGroupAvatar(
2259 GroupId groupId
, GroupSecretParams groupSecretParams
, String cdnKey
2260 ) throws IOException
{
2261 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2262 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2263 File outputFile
= getGroupAvatarFile(groupId
);
2264 GroupsV2Operations
.GroupOperations groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
2266 File tmpFile
= IOUtils
.createTempFile();
2267 tmpFile
.deleteOnExit();
2268 try (InputStream input
= receiver
.retrieveGroupsV2ProfileAvatar(cdnKey
,
2270 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2271 byte[] encryptedData
= IOUtils
.readFully(input
);
2273 byte[] decryptedData
= groupOperations
.decryptAvatar(encryptedData
);
2274 try (OutputStream output
= new FileOutputStream(outputFile
)) {
2275 output
.write(decryptedData
);
2279 Files
.delete(tmpFile
.toPath());
2280 } catch (IOException e
) {
2281 logger
.warn("Failed to delete received group avatar temp file “{}”, ignoring: {}",
2289 private File
getProfileAvatarFile(SignalServiceAddress address
) {
2290 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
2293 private File
retrieveProfileAvatar(
2294 SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
2295 ) throws IOException
{
2296 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2297 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2298 File outputFile
= getProfileAvatarFile(address
);
2300 File tmpFile
= IOUtils
.createTempFile();
2301 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
,
2304 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2305 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2306 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
2309 Files
.delete(tmpFile
.toPath());
2310 } catch (IOException e
) {
2311 logger
.warn("Failed to delete received profile avatar temp file “{}”, ignoring: {}",
2319 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
2320 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
2323 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2324 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
2325 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
2328 private File
retrieveAttachment(
2329 SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
2330 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2331 if (storePreview
&& pointer
.getPreview().isPresent()) {
2332 File previewFile
= new File(outputFile
+ ".preview");
2333 try (OutputStream output
= new FileOutputStream(previewFile
)) {
2334 byte[] preview
= pointer
.getPreview().get();
2335 output
.write(preview
, 0, preview
.length
);
2336 } catch (FileNotFoundException e
) {
2337 e
.printStackTrace();
2342 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2344 File tmpFile
= IOUtils
.createTempFile();
2345 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
,
2347 ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
2348 IOUtils
.copyStreamToFile(input
, outputFile
);
2351 Files
.delete(tmpFile
.toPath());
2352 } catch (IOException e
) {
2353 logger
.warn("Failed to delete received attachment temp file “{}”, ignoring: {}",
2361 private InputStream
retrieveAttachmentAsStream(
2362 SignalServiceAttachmentPointer pointer
, File tmpFile
2363 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2364 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2365 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
2368 void sendGroups() throws IOException
, UntrustedIdentityException
{
2369 File groupsFile
= IOUtils
.createTempFile();
2372 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
2373 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
2374 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2375 if (record instanceof GroupInfoV1
) {
2376 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
2377 out
.write(new DeviceGroup(groupInfo
.getGroupId().serialize(),
2378 Optional
.fromNullable(groupInfo
.name
),
2379 new ArrayList
<>(groupInfo
.getMembers()),
2380 createGroupAvatarAttachment(groupInfo
.getGroupId()),
2381 groupInfo
.isMember(account
.getSelfAddress()),
2382 Optional
.of(groupInfo
.messageExpirationTime
),
2383 Optional
.fromNullable(groupInfo
.color
),
2385 Optional
.fromNullable(groupInfo
.inboxPosition
),
2386 groupInfo
.archived
));
2391 if (groupsFile
.exists() && groupsFile
.length() > 0) {
2392 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
2393 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2394 .withStream(groupsFileStream
)
2395 .withContentType("application/octet-stream")
2396 .withLength(groupsFile
.length())
2399 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
2404 Files
.delete(groupsFile
.toPath());
2405 } catch (IOException e
) {
2406 logger
.warn("Failed to delete groups temp file “{}”, ignoring: {}", groupsFile
, e
.getMessage());
2411 public void sendContacts() throws IOException
, UntrustedIdentityException
{
2412 File contactsFile
= IOUtils
.createTempFile();
2415 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
2416 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
2417 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2418 VerifiedMessage verifiedMessage
= null;
2419 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore()
2420 .getIdentity(record.getAddress());
2421 if (currentIdentity
!= null) {
2422 verifiedMessage
= new VerifiedMessage(record.getAddress(),
2423 currentIdentity
.getIdentityKey(),
2424 currentIdentity
.getTrustLevel().toVerifiedState(),
2425 currentIdentity
.getDateAdded().getTime());
2428 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
2429 out
.write(new DeviceContact(record.getAddress(),
2430 Optional
.fromNullable(record.name
),
2431 createContactAvatarAttachment(record.number
),
2432 Optional
.fromNullable(record.color
),
2433 Optional
.fromNullable(verifiedMessage
),
2434 Optional
.fromNullable(profileKey
),
2436 Optional
.of(record.messageExpirationTime
),
2437 Optional
.fromNullable(record.inboxPosition
),
2441 if (account
.getProfileKey() != null) {
2442 // Send our own profile key as well
2443 out
.write(new DeviceContact(account
.getSelfAddress(),
2448 Optional
.of(account
.getProfileKey()),
2456 if (contactsFile
.exists() && contactsFile
.length() > 0) {
2457 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
2458 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2459 .withStream(contactsFileStream
)
2460 .withContentType("application/octet-stream")
2461 .withLength(contactsFile
.length())
2464 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
2469 Files
.delete(contactsFile
.toPath());
2470 } catch (IOException e
) {
2471 logger
.warn("Failed to delete contacts temp file “{}”, ignoring: {}", contactsFile
, e
.getMessage());
2476 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2477 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2478 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2479 if (record.blocked
) {
2480 addresses
.add(record.getAddress());
2483 List
<byte[]> groupIds
= new ArrayList
<>();
2484 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2485 if (record.isBlocked()) {
2486 groupIds
.add(record.getGroupId().serialize());
2489 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2492 private void sendVerifiedMessage(
2493 SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
2494 ) throws IOException
, UntrustedIdentityException
{
2495 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
,
2497 trustLevel
.toVerifiedState(),
2498 System
.currentTimeMillis());
2499 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2502 public List
<ContactInfo
> getContacts() {
2503 return account
.getContactStore().getContacts();
2506 public ContactInfo
getContact(String number
) {
2507 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
2510 public GroupInfo
getGroup(GroupId groupId
) {
2511 return account
.getGroupStore().getGroup(groupId
);
2514 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
2515 return account
.getSignalProtocolStore().getIdentities();
2518 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
2519 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2523 * Trust this the identity with this fingerprint
2525 * @param name username of the identity
2526 * @param fingerprint Fingerprint
2528 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2529 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2530 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2534 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2535 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2539 account
.getSignalProtocolStore()
2540 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2542 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2543 } catch (IOException
| UntrustedIdentityException e
) {
2544 e
.printStackTrace();
2553 * Trust this the identity with this safety number
2555 * @param name username of the identity
2556 * @param safetyNumber Safety number
2558 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2559 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2560 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2564 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2565 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2569 account
.getSignalProtocolStore()
2570 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2572 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2573 } catch (IOException
| UntrustedIdentityException e
) {
2574 e
.printStackTrace();
2583 * Trust all keys of this identity without verification
2585 * @param name username of the identity
2587 public boolean trustIdentityAllKeys(String name
) {
2588 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2589 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2593 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2594 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2595 account
.getSignalProtocolStore()
2596 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2598 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2599 } catch (IOException
| UntrustedIdentityException e
) {
2600 e
.printStackTrace();
2608 public String
computeSafetyNumber(
2609 SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
2611 return Utils
.computeSafetyNumber(account
.getSelfAddress(),
2612 getIdentityKeyPair().getPublicKey(),
2617 void saveAccount() {
2621 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2622 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
)
2624 : Util
.canonicalizeNumber(identifier
, account
.getUsername());
2625 return resolveSignalServiceAddress(canonicalizedNumber
);
2628 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2629 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2631 return resolveSignalServiceAddress(address
);
2634 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2635 if (address
.matches(account
.getSelfAddress())) {
2636 return account
.getSelfAddress();
2639 return account
.getRecipientStore().resolveServiceAddress(address
);
2643 public void close() throws IOException
{
2644 if (messagePipe
!= null) {
2645 messagePipe
.shutdown();
2649 if (unidentifiedMessagePipe
!= null) {
2650 unidentifiedMessagePipe
.shutdown();
2651 unidentifiedMessagePipe
= null;
2657 public interface ReceiveMessageHandler
{
2659 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);