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 File
getMessageCachePath() {
263 return SignalAccount
.getMessageCachePath(pathConfig
.getDataPath(), account
.getUsername());
266 private File
getMessageCachePath(String sender
) {
267 if (sender
== null || sender
.isEmpty()) {
268 return getMessageCachePath();
271 return new File(getMessageCachePath(), sender
.replace("/", "_"));
274 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
275 File 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(File 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 if (file
.getName().endsWith(".zip")) {
1195 zip
= new ZipFile(file
);
1196 } else if (file
.getName().equals("manifest.json")) {
1197 rootPath
= file
.getParent();
1199 throw new StickerPackInvalidException("Could not find manifest.json");
1202 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
1204 if (pack
.stickers
== null) {
1205 throw new StickerPackInvalidException("Must set a 'stickers' field.");
1208 if (pack
.stickers
.isEmpty()) {
1209 throw new StickerPackInvalidException("Must include stickers.");
1212 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
1213 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
1214 if (sticker
.file
== null) {
1215 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
1218 Pair
<InputStream
, Long
> data
;
1220 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
1221 } catch (IOException ignored
) {
1222 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
1225 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
1226 StickerInfo stickerInfo
= new StickerInfo(data
.first(),
1228 Optional
.fromNullable(sticker
.emoji
).or(""),
1230 stickers
.add(stickerInfo
);
1233 StickerInfo cover
= null;
1234 if (pack
.cover
!= null) {
1235 if (pack
.cover
.file
== null) {
1236 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
1239 Pair
<InputStream
, Long
> data
;
1241 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
1242 } catch (IOException ignored
) {
1243 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
1246 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
1247 cover
= new StickerInfo(data
.first(),
1249 Optional
.fromNullable(pack
.cover
.emoji
).or(""),
1253 return new SignalServiceStickerManifestUpload(pack
.title
, pack
.author
, cover
, stickers
);
1256 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
1257 InputStream inputStream
;
1259 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
1261 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
1263 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
1266 private static Pair
<InputStream
, Long
> getInputStreamAndLength(
1267 final String rootPath
, final ZipFile zip
, final String subfile
1268 ) throws IOException
{
1270 final ZipEntry entry
= zip
.getEntry(subfile
);
1271 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
1273 final File file
= new File(rootPath
, subfile
);
1274 return new Pair
<>(new FileInputStream(file
), file
.length());
1278 void requestSyncGroups() throws IOException
{
1279 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1280 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
)
1282 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1284 sendSyncMessage(message
);
1285 } catch (UntrustedIdentityException e
) {
1286 e
.printStackTrace();
1290 void requestSyncContacts() throws IOException
{
1291 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1292 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
)
1294 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1296 sendSyncMessage(message
);
1297 } catch (UntrustedIdentityException e
) {
1298 e
.printStackTrace();
1302 void requestSyncBlocked() throws IOException
{
1303 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1304 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
)
1306 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1308 sendSyncMessage(message
);
1309 } catch (UntrustedIdentityException e
) {
1310 e
.printStackTrace();
1314 void requestSyncConfiguration() throws IOException
{
1315 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1316 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
)
1318 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1320 sendSyncMessage(message
);
1321 } catch (UntrustedIdentityException e
) {
1322 e
.printStackTrace();
1326 private byte[] getSenderCertificate() {
1327 // TODO support UUID capable sender certificates
1328 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
1331 certificate
= accountManager
.getSenderCertificate();
1332 } catch (IOException e
) {
1333 logger
.warn("Failed to get sender certificate, ignoring: {}", e
.getMessage());
1336 // TODO cache for a day
1340 private void sendSyncMessage(SignalServiceSyncMessage message
) throws IOException
, UntrustedIdentityException
{
1341 SignalServiceMessageSender messageSender
= createMessageSender();
1343 messageSender
.sendMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
1344 } catch (UntrustedIdentityException e
) {
1345 account
.getSignalProtocolStore()
1346 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1348 TrustLevel
.UNTRUSTED
);
1353 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1354 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1355 final Set
<SignalServiceAddress
> missingUuids
= new HashSet
<>();
1357 for (String number
: numbers
) {
1358 final SignalServiceAddress resolvedAddress
= canonicalizeAndResolveSignalServiceAddress(number
);
1359 if (resolvedAddress
.getUuid().isPresent()) {
1360 signalServiceAddresses
.add(resolvedAddress
);
1362 missingUuids
.add(resolvedAddress
);
1366 Map
<String
, UUID
> registeredUsers
;
1368 registeredUsers
= accountManager
.getRegisteredUsers(getIasKeyStore(),
1369 missingUuids
.stream().map(a
-> a
.getNumber().get()).collect(Collectors
.toSet()),
1371 } catch (IOException
| Quote
.InvalidQuoteFormatException
| UnauthenticatedQuoteException
| SignatureException
| UnauthenticatedResponseException e
) {
1372 logger
.warn("Failed to resolve uuids from server, ignoring: {}", e
.getMessage());
1373 registeredUsers
= new HashMap
<>();
1376 for (SignalServiceAddress address
: missingUuids
) {
1377 final String number
= address
.getNumber().get();
1378 if (registeredUsers
.containsKey(number
)) {
1379 final SignalServiceAddress newAddress
= resolveSignalServiceAddress(new SignalServiceAddress(
1380 registeredUsers
.get(number
),
1382 signalServiceAddresses
.add(newAddress
);
1384 signalServiceAddresses
.add(address
);
1388 return signalServiceAddresses
;
1391 private Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1392 SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
1393 ) throws IOException
{
1394 recipients
= recipients
.stream().map(this::resolveSignalServiceAddress
).collect(Collectors
.toSet());
1395 final long timestamp
= System
.currentTimeMillis();
1396 messageBuilder
.withTimestamp(timestamp
);
1397 getOrCreateMessagePipe();
1398 getOrCreateUnidentifiedMessagePipe();
1399 SignalServiceDataMessage message
= null;
1401 message
= messageBuilder
.build();
1402 if (message
.getGroupContext().isPresent()) {
1404 SignalServiceMessageSender messageSender
= createMessageSender();
1405 final boolean isRecipientUpdate
= false;
1406 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
),
1407 unidentifiedAccessHelper
.getAccessFor(recipients
),
1410 for (SendMessageResult r
: result
) {
1411 if (r
.getIdentityFailure() != null) {
1412 account
.getSignalProtocolStore()
1413 .saveIdentity(r
.getAddress(),
1414 r
.getIdentityFailure().getIdentityKey(),
1415 TrustLevel
.UNTRUSTED
);
1418 return new Pair
<>(timestamp
, result
);
1419 } catch (UntrustedIdentityException e
) {
1420 account
.getSignalProtocolStore()
1421 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1423 TrustLevel
.UNTRUSTED
);
1424 return new Pair
<>(timestamp
, Collections
.emptyList());
1427 // Send to all individually, so sync messages are sent correctly
1428 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1429 for (SignalServiceAddress address
: recipients
) {
1430 ContactInfo contact
= account
.getContactStore().getContact(address
);
1431 if (contact
!= null) {
1432 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1433 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1435 messageBuilder
.withExpiration(0);
1436 messageBuilder
.withProfileKey(null);
1438 message
= messageBuilder
.build();
1439 if (address
.matches(account
.getSelfAddress())) {
1440 results
.add(sendSelfMessage(message
));
1442 results
.add(sendMessage(address
, message
));
1445 return new Pair
<>(timestamp
, results
);
1448 if (message
!= null && message
.isEndSession()) {
1449 for (SignalServiceAddress recipient
: recipients
) {
1450 handleEndSession(recipient
);
1457 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1458 SignalServiceMessageSender messageSender
= createMessageSender();
1460 SignalServiceAddress recipient
= account
.getSelfAddress();
1462 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= unidentifiedAccessHelper
.getAccessFor(recipient
);
1463 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1464 message
.getTimestamp(),
1466 message
.getExpiresInSeconds(),
1467 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1469 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1472 long startTime
= System
.currentTimeMillis();
1473 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1474 return SendMessageResult
.success(recipient
,
1475 unidentifiedAccess
.isPresent(),
1477 System
.currentTimeMillis() - startTime
);
1478 } catch (UntrustedIdentityException e
) {
1479 account
.getSignalProtocolStore()
1480 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1482 TrustLevel
.UNTRUSTED
);
1483 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1487 private SendMessageResult
sendMessage(
1488 SignalServiceAddress address
, SignalServiceDataMessage message
1489 ) throws IOException
{
1490 SignalServiceMessageSender messageSender
= createMessageSender();
1493 return messageSender
.sendMessage(address
, unidentifiedAccessHelper
.getAccessFor(address
), message
);
1494 } catch (UntrustedIdentityException e
) {
1495 account
.getSignalProtocolStore()
1496 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1498 TrustLevel
.UNTRUSTED
);
1499 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1503 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1504 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(),
1505 account
.getSignalProtocolStore(),
1506 Utils
.getCertificateValidator());
1508 return cipher
.decrypt(envelope
);
1509 } catch (ProtocolUntrustedIdentityException e
) {
1510 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1511 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
1513 account
.getSignalProtocolStore()
1514 .saveIdentity(resolveSignalServiceAddress(identityException
.getName()),
1515 identityException
.getUntrustedIdentity(),
1516 TrustLevel
.UNTRUSTED
);
1517 throw identityException
;
1519 throw new AssertionError(e
);
1523 private void handleEndSession(SignalServiceAddress source
) {
1524 account
.getSignalProtocolStore().deleteAllSessions(source
);
1527 private static int currentTimeDays() {
1528 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
1531 private GroupsV2AuthorizationString
getGroupAuthForToday(
1532 final GroupSecretParams groupSecretParams
1533 ) throws IOException
{
1534 final int today
= currentTimeDays();
1535 // Returns credentials for the next 7 days
1536 final HashMap
<Integer
, AuthCredentialResponse
> credentials
= groupsV2Api
.getCredentials(today
);
1537 // TODO cache credentials until they expire
1538 AuthCredentialResponse authCredentialResponse
= credentials
.get(today
);
1540 return groupsV2Api
.getGroupsV2AuthorizationString(account
.getUuid(),
1543 authCredentialResponse
);
1544 } catch (VerificationFailedException e
) {
1545 throw new IOException(e
);
1549 private List
<HandleAction
> handleSignalServiceDataMessage(
1550 SignalServiceDataMessage message
,
1552 SignalServiceAddress source
,
1553 SignalServiceAddress destination
,
1554 boolean ignoreAttachments
1556 List
<HandleAction
> actions
= new ArrayList
<>();
1557 if (message
.getGroupContext().isPresent()) {
1558 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1559 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1560 GroupIdV1 groupId
= GroupId
.v1(groupInfo
.getGroupId());
1561 GroupInfo group
= account
.getGroupStore().getGroup(groupId
);
1562 if (group
== null || group
instanceof GroupInfoV1
) {
1563 GroupInfoV1 groupV1
= (GroupInfoV1
) group
;
1564 switch (groupInfo
.getType()) {
1566 if (groupV1
== null) {
1567 groupV1
= new GroupInfoV1(groupId
);
1570 if (groupInfo
.getAvatar().isPresent()) {
1571 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1572 if (avatar
.isPointer()) {
1574 retrieveGroupAvatarAttachment(avatar
.asPointer(), groupV1
.getGroupId());
1575 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1576 logger
.warn("Failed to retrieve avatar for group {}, ignoring: {}",
1583 if (groupInfo
.getName().isPresent()) {
1584 groupV1
.name
= groupInfo
.getName().get();
1587 if (groupInfo
.getMembers().isPresent()) {
1588 groupV1
.addMembers(groupInfo
.getMembers()
1591 .map(this::resolveSignalServiceAddress
)
1592 .collect(Collectors
.toSet()));
1595 account
.getGroupStore().updateGroup(groupV1
);
1599 if (groupV1
== null && !isSync
) {
1600 actions
.add(new SendGroupInfoRequestAction(source
, groupId
));
1604 if (groupV1
!= null) {
1605 groupV1
.removeMember(source
);
1606 account
.getGroupStore().updateGroup(groupV1
);
1611 if (groupV1
!= null && !isSync
) {
1612 actions
.add(new SendGroupUpdateAction(source
, groupV1
.getGroupId()));
1617 // Received a group v1 message for a v2 group
1620 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1621 final SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1622 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1624 getOrMigrateGroup(groupMasterKey
,
1625 groupContext
.getRevision(),
1626 groupContext
.hasSignedGroupChange() ? groupContext
.getSignedGroupChange() : null);
1630 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1631 if (conversationPartnerAddress
!= null && message
.isEndSession()) {
1632 handleEndSession(conversationPartnerAddress
);
1634 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1635 if (message
.getGroupContext().isPresent()) {
1636 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1637 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1638 GroupInfoV1 group
= account
.getGroupStore().getOrCreateGroupV1(GroupId
.v1(groupInfo
.getGroupId()));
1639 if (group
!= null) {
1640 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1641 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1642 account
.getGroupStore().updateGroup(group
);
1645 } else if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1646 // disappearing message timer already stored in the DecryptedGroup
1648 } else if (conversationPartnerAddress
!= null) {
1649 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1650 if (contact
== null) {
1651 contact
= new ContactInfo(conversationPartnerAddress
);
1653 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1654 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1655 account
.getContactStore().updateContact(contact
);
1659 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1660 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1661 if (attachment
.isPointer()) {
1663 retrieveAttachment(attachment
.asPointer());
1664 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1665 logger
.warn("Failed to retrieve attachment ({}), ignoring: {}",
1666 attachment
.asPointer().getRemoteId(),
1672 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1673 final ProfileKey profileKey
;
1675 profileKey
= new ProfileKey(message
.getProfileKey().get());
1676 } catch (InvalidInputException e
) {
1677 throw new AssertionError(e
);
1679 if (source
.matches(account
.getSelfAddress())) {
1680 this.account
.setProfileKey(profileKey
);
1682 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1684 if (message
.getPreviews().isPresent()) {
1685 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1686 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1687 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1688 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1690 retrieveAttachment(attachment
);
1691 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1692 logger
.warn("Failed to retrieve preview image ({}), ignoring: {}",
1693 attachment
.getRemoteId(),
1699 if (message
.getQuote().isPresent()) {
1700 final SignalServiceDataMessage
.Quote quote
= message
.getQuote().get();
1702 for (SignalServiceDataMessage
.Quote
.QuotedAttachment quotedAttachment
: quote
.getAttachments()) {
1703 final SignalServiceAttachment attachment
= quotedAttachment
.getThumbnail();
1704 if (attachment
!= null && attachment
.isPointer()) {
1706 retrieveAttachment(attachment
.asPointer());
1707 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1708 logger
.warn("Failed to retrieve quote attachment thumbnail ({}), ignoring: {}",
1709 attachment
.asPointer().getRemoteId(),
1715 if (message
.getSticker().isPresent()) {
1716 final SignalServiceDataMessage
.Sticker messageSticker
= message
.getSticker().get();
1717 Sticker sticker
= account
.getStickerStore().getSticker(messageSticker
.getPackId());
1718 if (sticker
== null) {
1719 sticker
= new Sticker(messageSticker
.getPackId(), messageSticker
.getPackKey());
1720 account
.getStickerStore().updateSticker(sticker
);
1726 private GroupInfoV2
getOrMigrateGroup(
1727 final GroupMasterKey groupMasterKey
, final int revision
, final byte[] signedGroupChange
1729 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
1731 GroupIdV2 groupId
= GroupUtils
.getGroupIdV2(groupSecretParams
);
1732 GroupInfo groupInfo
= account
.getGroupStore().getGroup(groupId
);
1733 final GroupInfoV2 groupInfoV2
;
1734 if (groupInfo
instanceof GroupInfoV1
) {
1735 // Received a v2 group message for a v1 group, we need to locally migrate the group
1736 account
.getGroupStore().deleteGroup(groupInfo
.getGroupId());
1737 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1738 logger
.info("Locally migrated group {} to group v2, id: {}",
1739 groupInfo
.getGroupId().toBase64(),
1740 groupInfoV2
.getGroupId().toBase64());
1741 } else if (groupInfo
instanceof GroupInfoV2
) {
1742 groupInfoV2
= (GroupInfoV2
) groupInfo
;
1744 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1747 if (groupInfoV2
.getGroup() == null || groupInfoV2
.getGroup().getRevision() < revision
) {
1748 DecryptedGroup group
= null;
1749 if (signedGroupChange
!= null
1750 && groupInfoV2
.getGroup() != null
1751 && groupInfoV2
.getGroup().getRevision() + 1 == revision
) {
1752 group
= groupHelper
.getUpdatedDecryptedGroup(groupInfoV2
.getGroup(), signedGroupChange
, groupMasterKey
);
1754 if (group
== null) {
1755 group
= groupHelper
.getDecryptedGroup(groupSecretParams
);
1757 if (group
!= null) {
1758 storeProfileKeysFromMembers(group
);
1759 final String avatar
= group
.getAvatar();
1760 if (avatar
!= null && !avatar
.isEmpty()) {
1762 retrieveGroupAvatar(groupId
, groupSecretParams
, avatar
);
1763 } catch (IOException e
) {
1764 logger
.warn("Failed to download group avatar, ignoring: {}", e
.getMessage());
1768 groupInfoV2
.setGroup(group
);
1769 account
.getGroupStore().updateGroup(groupInfoV2
);
1775 private void storeProfileKeysFromMembers(final DecryptedGroup group
) {
1776 for (DecryptedMember member
: group
.getMembersList()) {
1777 final SignalServiceAddress address
= resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil
.parseOrThrow(
1778 member
.getUuid().toByteArray()), null));
1780 account
.getProfileStore()
1781 .storeProfileKey(address
, new ProfileKey(member
.getProfileKey().toByteArray()));
1782 } catch (InvalidInputException ignored
) {
1787 private void retryFailedReceivedMessages(
1788 ReceiveMessageHandler handler
, boolean ignoreAttachments
1790 final File cachePath
= getMessageCachePath();
1791 if (!cachePath
.exists()) {
1794 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1795 if (!dir
.isDirectory()) {
1796 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1800 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1801 if (!fileEntry
.isFile()) {
1804 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1806 // Try to delete directory if empty
1811 private void retryFailedReceivedMessage(
1812 final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
1814 SignalServiceEnvelope envelope
;
1816 envelope
= Utils
.loadEnvelope(fileEntry
);
1817 if (envelope
== null) {
1820 } catch (IOException e
) {
1821 e
.printStackTrace();
1824 SignalServiceContent content
= null;
1825 if (!envelope
.isReceipt()) {
1827 content
= decryptMessage(envelope
);
1828 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1830 } catch (Exception er
) {
1831 // All other errors are not recoverable, so delete the cached message
1833 Files
.delete(fileEntry
.toPath());
1834 } catch (IOException e
) {
1835 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry
, e
.getMessage());
1839 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1840 for (HandleAction action
: actions
) {
1842 action
.execute(this);
1843 } catch (Throwable e
) {
1844 e
.printStackTrace();
1849 handler
.handleMessage(envelope
, content
, null);
1851 Files
.delete(fileEntry
.toPath());
1852 } catch (IOException e
) {
1853 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry
, e
.getMessage());
1857 public void receiveMessages(
1860 boolean returnOnTimeout
,
1861 boolean ignoreAttachments
,
1862 ReceiveMessageHandler handler
1863 ) throws IOException
{
1864 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1866 Set
<HandleAction
> queuedActions
= null;
1868 getOrCreateMessagePipe();
1870 boolean hasCaughtUpWithOldMessages
= false;
1873 SignalServiceEnvelope envelope
;
1874 SignalServiceContent content
= null;
1875 Exception exception
= null;
1876 final long now
= new Date().getTime();
1878 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1879 // store message on disk, before acknowledging receipt to the server
1881 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1882 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1883 Utils
.storeEnvelope(envelope1
, cacheFile
);
1884 } catch (IOException e
) {
1885 logger
.warn("Failed to store encrypted message in disk cache, ignoring: {}", e
.getMessage());
1888 if (result
.isPresent()) {
1889 envelope
= result
.get();
1891 // Received indicator that server queue is empty
1892 hasCaughtUpWithOldMessages
= true;
1894 if (queuedActions
!= null) {
1895 for (HandleAction action
: queuedActions
) {
1897 action
.execute(this);
1898 } catch (Throwable e
) {
1899 e
.printStackTrace();
1903 queuedActions
.clear();
1904 queuedActions
= null;
1907 // Continue to wait another timeout for new messages
1910 } catch (TimeoutException e
) {
1911 if (returnOnTimeout
) return;
1913 } catch (InvalidVersionException e
) {
1914 logger
.warn("Error while receiving messages, ignoring: {}", e
.getMessage());
1918 if (envelope
.hasSource()) {
1919 // Store uuid if we don't have it already
1920 SignalServiceAddress source
= envelope
.getSourceAddress();
1921 resolveSignalServiceAddress(source
);
1923 if (!envelope
.isReceipt()) {
1925 content
= decryptMessage(envelope
);
1926 } catch (Exception e
) {
1929 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1930 if (hasCaughtUpWithOldMessages
) {
1931 for (HandleAction action
: actions
) {
1933 action
.execute(this);
1934 } catch (Throwable e
) {
1935 e
.printStackTrace();
1939 if (queuedActions
== null) {
1940 queuedActions
= new HashSet
<>();
1942 queuedActions
.addAll(actions
);
1946 if (!isMessageBlocked(envelope
, content
)) {
1947 handler
.handleMessage(envelope
, content
, exception
);
1949 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1950 File cacheFile
= null;
1952 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1953 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1954 Files
.delete(cacheFile
.toPath());
1955 // Try to delete directory if empty
1956 getMessageCachePath().delete();
1957 } catch (IOException e
) {
1958 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", cacheFile
, e
.getMessage());
1964 private boolean isMessageBlocked(
1965 SignalServiceEnvelope envelope
, SignalServiceContent content
1967 SignalServiceAddress source
;
1968 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1969 source
= envelope
.getSourceAddress();
1970 } else if (content
!= null) {
1971 source
= content
.getSender();
1975 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1976 if (sourceContact
!= null && sourceContact
.blocked
) {
1980 if (content
!= null && content
.getDataMessage().isPresent()) {
1981 SignalServiceDataMessage message
= content
.getDataMessage().get();
1982 if (message
.getGroupContext().isPresent()) {
1983 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1984 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1985 if (groupInfo
.getType() != SignalServiceGroup
.Type
.DELIVER
) {
1989 GroupId groupId
= GroupUtils
.getGroupId(message
.getGroupContext().get());
1990 GroupInfo group
= account
.getGroupStore().getGroup(groupId
);
1991 if (group
!= null && group
.isBlocked()) {
1999 private List
<HandleAction
> handleMessage(
2000 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
2002 List
<HandleAction
> actions
= new ArrayList
<>();
2003 if (content
!= null) {
2004 final SignalServiceAddress sender
;
2005 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
2006 sender
= envelope
.getSourceAddress();
2008 sender
= content
.getSender();
2010 // Store uuid if we don't have it already
2011 resolveSignalServiceAddress(sender
);
2013 if (content
.getDataMessage().isPresent()) {
2014 SignalServiceDataMessage message
= content
.getDataMessage().get();
2016 if (content
.isNeedsReceipt()) {
2017 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
2020 actions
.addAll(handleSignalServiceDataMessage(message
,
2023 account
.getSelfAddress(),
2024 ignoreAttachments
));
2026 if (content
.getSyncMessage().isPresent()) {
2027 account
.setMultiDevice(true);
2028 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
2029 if (syncMessage
.getSent().isPresent()) {
2030 SentTranscriptMessage message
= syncMessage
.getSent().get();
2031 final SignalServiceAddress destination
= message
.getDestination().orNull();
2032 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
2036 ignoreAttachments
));
2038 if (syncMessage
.getRequest().isPresent()) {
2039 RequestMessage rm
= syncMessage
.getRequest().get();
2040 if (rm
.isContactsRequest()) {
2041 actions
.add(SendSyncContactsAction
.create());
2043 if (rm
.isGroupsRequest()) {
2044 actions
.add(SendSyncGroupsAction
.create());
2046 if (rm
.isBlockedListRequest()) {
2047 actions
.add(SendSyncBlockedListAction
.create());
2049 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
2051 if (syncMessage
.getGroups().isPresent()) {
2052 File tmpFile
= null;
2054 tmpFile
= IOUtils
.createTempFile();
2055 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups()
2057 .asPointer(), tmpFile
)) {
2058 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
2060 while ((g
= s
.read()) != null) {
2061 GroupInfoV1 syncGroup
= account
.getGroupStore()
2062 .getOrCreateGroupV1(GroupId
.v1(g
.getId()));
2063 if (syncGroup
!= null) {
2064 if (g
.getName().isPresent()) {
2065 syncGroup
.name
= g
.getName().get();
2067 syncGroup
.addMembers(g
.getMembers()
2069 .map(this::resolveSignalServiceAddress
)
2070 .collect(Collectors
.toSet()));
2071 if (!g
.isActive()) {
2072 syncGroup
.removeMember(account
.getSelfAddress());
2074 // Add ourself to the member set as it's marked as active
2075 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
2077 syncGroup
.blocked
= g
.isBlocked();
2078 if (g
.getColor().isPresent()) {
2079 syncGroup
.color
= g
.getColor().get();
2082 if (g
.getAvatar().isPresent()) {
2083 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.getGroupId());
2085 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
2086 syncGroup
.archived
= g
.isArchived();
2087 account
.getGroupStore().updateGroup(syncGroup
);
2091 } catch (Exception e
) {
2092 logger
.warn("Failed to handle received sync groups “{}”, ignoring: {}",
2095 e
.printStackTrace();
2097 if (tmpFile
!= null) {
2099 Files
.delete(tmpFile
.toPath());
2100 } catch (IOException e
) {
2101 logger
.warn("Failed to delete received groups temp file “{}”, ignoring: {}",
2108 if (syncMessage
.getBlockedList().isPresent()) {
2109 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
2110 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
2111 setContactBlocked(resolveSignalServiceAddress(address
), true);
2113 for (GroupId groupId
: blockedListMessage
.getGroupIds()
2115 .map(GroupId
::unknownVersion
)
2116 .collect(Collectors
.toSet())) {
2118 setGroupBlocked(groupId
, true);
2119 } catch (GroupNotFoundException e
) {
2120 logger
.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
2121 groupId
.toBase64());
2125 if (syncMessage
.getContacts().isPresent()) {
2126 File tmpFile
= null;
2128 tmpFile
= IOUtils
.createTempFile();
2129 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
2130 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream()
2131 .asPointer(), tmpFile
)) {
2132 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
2133 if (contactsMessage
.isComplete()) {
2134 account
.getContactStore().clear();
2137 while ((c
= s
.read()) != null) {
2138 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
2139 account
.setProfileKey(c
.getProfileKey().get());
2141 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
2142 ContactInfo contact
= account
.getContactStore().getContact(address
);
2143 if (contact
== null) {
2144 contact
= new ContactInfo(address
);
2146 if (c
.getName().isPresent()) {
2147 contact
.name
= c
.getName().get();
2149 if (c
.getColor().isPresent()) {
2150 contact
.color
= c
.getColor().get();
2152 if (c
.getProfileKey().isPresent()) {
2153 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
2155 if (c
.getVerified().isPresent()) {
2156 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
2157 account
.getSignalProtocolStore()
2158 .setIdentityTrustLevel(verifiedMessage
.getDestination(),
2159 verifiedMessage
.getIdentityKey(),
2160 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2162 if (c
.getExpirationTimer().isPresent()) {
2163 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
2165 contact
.blocked
= c
.isBlocked();
2166 contact
.inboxPosition
= c
.getInboxPosition().orNull();
2167 contact
.archived
= c
.isArchived();
2168 account
.getContactStore().updateContact(contact
);
2170 if (c
.getAvatar().isPresent()) {
2171 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
2175 } catch (Exception e
) {
2176 e
.printStackTrace();
2178 if (tmpFile
!= null) {
2180 Files
.delete(tmpFile
.toPath());
2181 } catch (IOException e
) {
2182 logger
.warn("Failed to delete received contacts temp file “{}”, ignoring: {}",
2189 if (syncMessage
.getVerified().isPresent()) {
2190 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
2191 account
.getSignalProtocolStore()
2192 .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()),
2193 verifiedMessage
.getIdentityKey(),
2194 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2196 if (syncMessage
.getStickerPackOperations().isPresent()) {
2197 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations()
2199 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
2200 if (!m
.getPackId().isPresent()) {
2203 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
2204 if (sticker
== null) {
2205 if (!m
.getPackKey().isPresent()) {
2208 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
2210 sticker
.setInstalled(!m
.getType().isPresent()
2211 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
2212 account
.getStickerStore().updateSticker(sticker
);
2215 if (syncMessage
.getConfiguration().isPresent()) {
2223 private File
getContactAvatarFile(String number
) {
2224 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
2227 private File
retrieveContactAvatarAttachment(
2228 SignalServiceAttachment attachment
, String number
2229 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2230 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2231 if (attachment
.isPointer()) {
2232 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2233 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
2235 SignalServiceAttachmentStream stream
= attachment
.asStream();
2236 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
2240 private File
getGroupAvatarFile(GroupId groupId
) {
2241 return new File(pathConfig
.getAvatarsPath(), "group-" + groupId
.toBase64().replace("/", "_"));
2244 private File
retrieveGroupAvatarAttachment(
2245 SignalServiceAttachment attachment
, GroupId groupId
2246 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2247 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2248 if (attachment
.isPointer()) {
2249 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2250 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
2252 SignalServiceAttachmentStream stream
= attachment
.asStream();
2253 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
2257 private File
retrieveGroupAvatar(
2258 GroupId groupId
, GroupSecretParams groupSecretParams
, String cdnKey
2259 ) throws IOException
{
2260 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2261 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2262 File outputFile
= getGroupAvatarFile(groupId
);
2263 GroupsV2Operations
.GroupOperations groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
2265 File tmpFile
= IOUtils
.createTempFile();
2266 tmpFile
.deleteOnExit();
2267 try (InputStream input
= receiver
.retrieveGroupsV2ProfileAvatar(cdnKey
,
2269 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2270 byte[] encryptedData
= IOUtils
.readFully(input
);
2272 byte[] decryptedData
= groupOperations
.decryptAvatar(encryptedData
);
2273 try (OutputStream output
= new FileOutputStream(outputFile
)) {
2274 output
.write(decryptedData
);
2278 Files
.delete(tmpFile
.toPath());
2279 } catch (IOException e
) {
2280 logger
.warn("Failed to delete received group avatar temp file “{}”, ignoring: {}",
2288 private File
getProfileAvatarFile(SignalServiceAddress address
) {
2289 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
2292 private File
retrieveProfileAvatar(
2293 SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
2294 ) throws IOException
{
2295 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2296 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2297 File outputFile
= getProfileAvatarFile(address
);
2299 File tmpFile
= IOUtils
.createTempFile();
2300 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
,
2303 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2304 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2305 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
2308 Files
.delete(tmpFile
.toPath());
2309 } catch (IOException e
) {
2310 logger
.warn("Failed to delete received profile avatar temp file “{}”, ignoring: {}",
2318 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
2319 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
2322 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2323 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
2324 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
2327 private File
retrieveAttachment(
2328 SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
2329 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2330 if (storePreview
&& pointer
.getPreview().isPresent()) {
2331 File previewFile
= new File(outputFile
+ ".preview");
2332 try (OutputStream output
= new FileOutputStream(previewFile
)) {
2333 byte[] preview
= pointer
.getPreview().get();
2334 output
.write(preview
, 0, preview
.length
);
2335 } catch (FileNotFoundException e
) {
2336 e
.printStackTrace();
2341 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2343 File tmpFile
= IOUtils
.createTempFile();
2344 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
,
2346 ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
2347 IOUtils
.copyStreamToFile(input
, outputFile
);
2350 Files
.delete(tmpFile
.toPath());
2351 } catch (IOException e
) {
2352 logger
.warn("Failed to delete received attachment temp file “{}”, ignoring: {}",
2360 private InputStream
retrieveAttachmentAsStream(
2361 SignalServiceAttachmentPointer pointer
, File tmpFile
2362 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2363 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2364 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
2367 void sendGroups() throws IOException
, UntrustedIdentityException
{
2368 File groupsFile
= IOUtils
.createTempFile();
2371 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
2372 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
2373 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2374 if (record instanceof GroupInfoV1
) {
2375 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
2376 out
.write(new DeviceGroup(groupInfo
.getGroupId().serialize(),
2377 Optional
.fromNullable(groupInfo
.name
),
2378 new ArrayList
<>(groupInfo
.getMembers()),
2379 createGroupAvatarAttachment(groupInfo
.getGroupId()),
2380 groupInfo
.isMember(account
.getSelfAddress()),
2381 Optional
.of(groupInfo
.messageExpirationTime
),
2382 Optional
.fromNullable(groupInfo
.color
),
2384 Optional
.fromNullable(groupInfo
.inboxPosition
),
2385 groupInfo
.archived
));
2390 if (groupsFile
.exists() && groupsFile
.length() > 0) {
2391 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
2392 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2393 .withStream(groupsFileStream
)
2394 .withContentType("application/octet-stream")
2395 .withLength(groupsFile
.length())
2398 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
2403 Files
.delete(groupsFile
.toPath());
2404 } catch (IOException e
) {
2405 logger
.warn("Failed to delete groups temp file “{}”, ignoring: {}", groupsFile
, e
.getMessage());
2410 public void sendContacts() throws IOException
, UntrustedIdentityException
{
2411 File contactsFile
= IOUtils
.createTempFile();
2414 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
2415 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
2416 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2417 VerifiedMessage verifiedMessage
= null;
2418 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore()
2419 .getIdentity(record.getAddress());
2420 if (currentIdentity
!= null) {
2421 verifiedMessage
= new VerifiedMessage(record.getAddress(),
2422 currentIdentity
.getIdentityKey(),
2423 currentIdentity
.getTrustLevel().toVerifiedState(),
2424 currentIdentity
.getDateAdded().getTime());
2427 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
2428 out
.write(new DeviceContact(record.getAddress(),
2429 Optional
.fromNullable(record.name
),
2430 createContactAvatarAttachment(record.number
),
2431 Optional
.fromNullable(record.color
),
2432 Optional
.fromNullable(verifiedMessage
),
2433 Optional
.fromNullable(profileKey
),
2435 Optional
.of(record.messageExpirationTime
),
2436 Optional
.fromNullable(record.inboxPosition
),
2440 if (account
.getProfileKey() != null) {
2441 // Send our own profile key as well
2442 out
.write(new DeviceContact(account
.getSelfAddress(),
2447 Optional
.of(account
.getProfileKey()),
2455 if (contactsFile
.exists() && contactsFile
.length() > 0) {
2456 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
2457 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2458 .withStream(contactsFileStream
)
2459 .withContentType("application/octet-stream")
2460 .withLength(contactsFile
.length())
2463 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
2468 Files
.delete(contactsFile
.toPath());
2469 } catch (IOException e
) {
2470 logger
.warn("Failed to delete contacts temp file “{}”, ignoring: {}", contactsFile
, e
.getMessage());
2475 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2476 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2477 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2478 if (record.blocked
) {
2479 addresses
.add(record.getAddress());
2482 List
<byte[]> groupIds
= new ArrayList
<>();
2483 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2484 if (record.isBlocked()) {
2485 groupIds
.add(record.getGroupId().serialize());
2488 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2491 private void sendVerifiedMessage(
2492 SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
2493 ) throws IOException
, UntrustedIdentityException
{
2494 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
,
2496 trustLevel
.toVerifiedState(),
2497 System
.currentTimeMillis());
2498 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2501 public List
<ContactInfo
> getContacts() {
2502 return account
.getContactStore().getContacts();
2505 public ContactInfo
getContact(String number
) {
2506 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
2509 public GroupInfo
getGroup(GroupId groupId
) {
2510 return account
.getGroupStore().getGroup(groupId
);
2513 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
2514 return account
.getSignalProtocolStore().getIdentities();
2517 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
2518 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2522 * Trust this the identity with this fingerprint
2524 * @param name username of the identity
2525 * @param fingerprint Fingerprint
2527 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2528 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2529 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2533 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2534 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2538 account
.getSignalProtocolStore()
2539 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2541 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2542 } catch (IOException
| UntrustedIdentityException e
) {
2543 e
.printStackTrace();
2552 * Trust this the identity with this safety number
2554 * @param name username of the identity
2555 * @param safetyNumber Safety number
2557 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2558 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2559 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2563 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2564 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2568 account
.getSignalProtocolStore()
2569 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2571 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2572 } catch (IOException
| UntrustedIdentityException e
) {
2573 e
.printStackTrace();
2582 * Trust all keys of this identity without verification
2584 * @param name username of the identity
2586 public boolean trustIdentityAllKeys(String name
) {
2587 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2588 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2592 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2593 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2594 account
.getSignalProtocolStore()
2595 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2597 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2598 } catch (IOException
| UntrustedIdentityException e
) {
2599 e
.printStackTrace();
2607 public String
computeSafetyNumber(
2608 SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
2610 return Utils
.computeSafetyNumber(account
.getSelfAddress(),
2611 getIdentityKeyPair().getPublicKey(),
2616 void saveAccount() {
2620 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2621 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
)
2623 : Util
.canonicalizeNumber(identifier
, account
.getUsername());
2624 return resolveSignalServiceAddress(canonicalizedNumber
);
2627 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2628 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2630 return resolveSignalServiceAddress(address
);
2633 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2634 if (address
.matches(account
.getSelfAddress())) {
2635 return account
.getSelfAddress();
2638 return account
.getRecipientStore().resolveServiceAddress(address
);
2642 public void close() throws IOException
{
2643 if (messagePipe
!= null) {
2644 messagePipe
.shutdown();
2648 if (unidentifiedMessagePipe
!= null) {
2649 unidentifiedMessagePipe
.shutdown();
2650 unidentifiedMessagePipe
= null;
2656 public interface ReceiveMessageHandler
{
2658 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);