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
.DecryptedMember
;
49 import org
.signal
.zkgroup
.InvalidInputException
;
50 import org
.signal
.zkgroup
.VerificationFailedException
;
51 import org
.signal
.zkgroup
.auth
.AuthCredentialResponse
;
52 import org
.signal
.zkgroup
.groups
.GroupMasterKey
;
53 import org
.signal
.zkgroup
.groups
.GroupSecretParams
;
54 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
55 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
56 import org
.signal
.zkgroup
.profiles
.ProfileKeyCredential
;
57 import org
.whispersystems
.libsignal
.IdentityKey
;
58 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
59 import org
.whispersystems
.libsignal
.InvalidKeyException
;
60 import org
.whispersystems
.libsignal
.InvalidMessageException
;
61 import org
.whispersystems
.libsignal
.InvalidVersionException
;
62 import org
.whispersystems
.libsignal
.ecc
.Curve
;
63 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
64 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
65 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
66 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
67 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
68 import org
.whispersystems
.libsignal
.util
.Medium
;
69 import org
.whispersystems
.libsignal
.util
.Pair
;
70 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
71 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
72 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
73 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
74 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
75 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
76 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
77 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
78 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
79 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
80 import org
.whispersystems
.signalservice
.api
.groupsv2
.ClientZkOperations
;
81 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Api
;
82 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2AuthorizationString
;
83 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
84 import org
.whispersystems
.signalservice
.api
.groupsv2
.InvalidGroupStateException
;
85 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
86 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
87 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
88 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
89 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
90 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
91 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
92 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
93 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
94 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroupV2
;
95 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
96 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
97 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
98 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
99 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
100 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
101 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
102 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
103 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
104 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
105 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
106 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
107 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
108 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
109 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
110 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.StickerPackOperationMessage
;
111 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
112 import org
.whispersystems
.signalservice
.api
.profiles
.ProfileAndCredential
;
113 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
114 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
115 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
116 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
117 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
118 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
119 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
120 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
121 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
122 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
123 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
124 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
125 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
126 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
127 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
128 import org
.whispersystems
.util
.Base64
;
130 import java
.io
.Closeable
;
132 import java
.io
.FileInputStream
;
133 import java
.io
.FileNotFoundException
;
134 import java
.io
.FileOutputStream
;
135 import java
.io
.IOException
;
136 import java
.io
.InputStream
;
137 import java
.io
.OutputStream
;
139 import java
.net
.URISyntaxException
;
140 import java
.net
.URLEncoder
;
141 import java
.nio
.charset
.StandardCharsets
;
142 import java
.nio
.file
.Files
;
143 import java
.nio
.file
.Paths
;
144 import java
.nio
.file
.StandardCopyOption
;
145 import java
.util
.ArrayList
;
146 import java
.util
.Arrays
;
147 import java
.util
.Collection
;
148 import java
.util
.Collections
;
149 import java
.util
.Date
;
150 import java
.util
.HashMap
;
151 import java
.util
.HashSet
;
152 import java
.util
.List
;
153 import java
.util
.Locale
;
154 import java
.util
.Objects
;
155 import java
.util
.Set
;
156 import java
.util
.UUID
;
157 import java
.util
.concurrent
.ExecutorService
;
158 import java
.util
.concurrent
.TimeUnit
;
159 import java
.util
.concurrent
.TimeoutException
;
160 import java
.util
.stream
.Collectors
;
161 import java
.util
.zip
.ZipEntry
;
162 import java
.util
.zip
.ZipFile
;
164 import static org
.asamk
.signal
.manager
.ServiceConfig
.capabilities
;
166 public class Manager
implements Closeable
{
168 private final SleepTimer timer
= new UptimeSleepTimer();
170 private final SignalServiceConfiguration serviceConfiguration
;
171 private final String userAgent
;
172 private final boolean discoverableByPhoneNumber
= true;
173 private final boolean unrestrictedUnidentifiedAccess
= false;
175 private final SignalAccount account
;
176 private final PathConfig pathConfig
;
177 private SignalServiceAccountManager accountManager
;
178 private GroupsV2Api groupsV2Api
;
179 private final GroupsV2Operations groupsV2Operations
;
181 private SignalServiceMessageReceiver messageReceiver
= null;
182 private SignalServiceMessagePipe messagePipe
= null;
183 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
185 private final UnidentifiedAccessHelper unidentifiedAccessHelper
;
186 private final ProfileHelper profileHelper
;
187 private final GroupHelper groupHelper
;
190 SignalAccount account
,
191 PathConfig pathConfig
,
192 SignalServiceConfiguration serviceConfiguration
,
195 this.account
= account
;
196 this.pathConfig
= pathConfig
;
197 this.serviceConfiguration
= serviceConfiguration
;
198 this.userAgent
= userAgent
;
199 this.groupsV2Operations
= capabilities
.isGv2() ?
new GroupsV2Operations(ClientZkOperations
.create(
200 serviceConfiguration
)) : null;
201 this.accountManager
= createSignalServiceAccountManager();
202 this.groupsV2Api
= accountManager
.getGroupsV2Api();
204 this.account
.setResolver(this::resolveSignalServiceAddress
);
206 this.unidentifiedAccessHelper
= new UnidentifiedAccessHelper(account
::getProfileKey
,
207 account
.getProfileStore()::getProfileKey
,
208 this::getRecipientProfile
,
209 this::getSenderCertificate
);
210 this.profileHelper
= new ProfileHelper(account
.getProfileStore()::getProfileKey
,
211 unidentifiedAccessHelper
::getAccessFor
,
212 unidentified
-> unidentified ?
getOrCreateUnidentifiedMessagePipe() : getOrCreateMessagePipe(),
213 this::getOrCreateMessageReceiver
);
214 this.groupHelper
= new GroupHelper(this::getRecipientProfileKeyCredential
,
215 this::getRecipientProfile
,
216 account
::getSelfAddress
,
219 this::getGroupAuthForToday
);
222 public String
getUsername() {
223 return account
.getUsername();
226 public SignalServiceAddress
getSelfAddress() {
227 return account
.getSelfAddress();
230 private SignalServiceAccountManager
createSignalServiceAccountManager() {
231 return new SignalServiceAccountManager(serviceConfiguration
,
232 new DynamicCredentialsProvider(account
.getUuid(),
233 account
.getUsername(),
234 account
.getPassword(),
236 account
.getDeviceId()),
242 private IdentityKeyPair
getIdentityKeyPair() {
243 return account
.getSignalProtocolStore().getIdentityKeyPair();
246 public int getDeviceId() {
247 return account
.getDeviceId();
250 private String
getMessageCachePath() {
251 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
254 private String
getMessageCachePath(String sender
) {
255 if (sender
== null || sender
.isEmpty()) {
256 return getMessageCachePath();
259 return getMessageCachePath() + "/" + sender
.replace("/", "_");
262 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
263 String cachePath
= getMessageCachePath(sender
);
264 IOUtils
.createPrivateDirectories(cachePath
);
265 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
268 public static Manager
init(
269 String username
, String settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
270 ) throws IOException
{
271 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
273 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
274 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
275 int registrationId
= KeyHelper
.generateRegistrationId(false);
277 ProfileKey profileKey
= KeyUtils
.createProfileKey();
278 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(),
285 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
288 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
290 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
292 m
.migrateLegacyConfigs();
297 private void migrateLegacyConfigs() {
298 if (account
.getProfileKey() == null && isRegistered()) {
299 // Old config file, creating new profile key
300 account
.setProfileKey(KeyUtils
.createProfileKey());
303 // Store profile keys only in profile store
304 for (ContactInfo contact
: account
.getContactStore().getContacts()) {
305 String profileKeyString
= contact
.profileKey
;
306 if (profileKeyString
== null) {
309 final ProfileKey profileKey
;
311 profileKey
= new ProfileKey(Base64
.decode(profileKeyString
));
312 } catch (InvalidInputException
| IOException e
) {
315 contact
.profileKey
= null;
316 account
.getProfileStore().storeProfileKey(contact
.getAddress(), profileKey
);
320 public void checkAccountState() throws IOException
{
321 if (account
.isRegistered()) {
322 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
326 if (account
.getUuid() == null) {
327 account
.setUuid(accountManager
.getOwnUuid());
330 updateAccountAttributes();
334 public boolean isRegistered() {
335 return account
.isRegistered();
338 public void register(boolean voiceVerification
, String captcha
) throws IOException
{
339 account
.setPassword(KeyUtils
.createPassword());
341 // Resetting UUID, because registering doesn't work otherwise
342 account
.setUuid(null);
343 accountManager
= createSignalServiceAccountManager();
344 this.groupsV2Api
= accountManager
.getGroupsV2Api();
346 if (voiceVerification
) {
347 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(),
348 Optional
.fromNullable(captcha
),
351 accountManager
.requestSmsVerificationCode(false, Optional
.fromNullable(captcha
), Optional
.absent());
354 account
.setRegistered(false);
358 public void updateAccountAttributes() throws IOException
{
359 accountManager
.setAccountAttributes(account
.getSignalingKey(),
360 account
.getSignalProtocolStore().getLocalRegistrationId(),
362 account
.getRegistrationLockPin(),
363 account
.getRegistrationLock(),
364 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
365 unrestrictedUnidentifiedAccess
,
367 discoverableByPhoneNumber
);
370 public void setProfile(String name
, File avatar
) throws IOException
{
371 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
372 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
376 public void unregister() throws IOException
{
377 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
378 // If this is the master device, other users can't send messages to this number anymore.
379 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
380 accountManager
.setGcmId(Optional
.absent());
382 account
.setRegistered(false);
386 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
387 List
<DeviceInfo
> devices
= accountManager
.getDevices();
388 account
.setMultiDevice(devices
.size() > 1);
393 public void removeLinkedDevices(int deviceId
) throws IOException
{
394 accountManager
.removeDevice(deviceId
);
395 List
<DeviceInfo
> devices
= accountManager
.getDevices();
396 account
.setMultiDevice(devices
.size() > 1);
400 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
401 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
403 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
406 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
407 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
408 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
410 accountManager
.addDevice(deviceIdentifier
,
413 Optional
.of(account
.getProfileKey().serialize()),
415 account
.setMultiDevice(true);
419 private List
<PreKeyRecord
> generatePreKeys() {
420 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
422 final int offset
= account
.getPreKeyIdOffset();
423 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
424 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
425 ECKeyPair keyPair
= Curve
.generateKeyPair();
426 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
431 account
.addPreKeys(records
);
437 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
439 ECKeyPair keyPair
= Curve
.generateKeyPair();
440 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(),
441 keyPair
.getPublicKey().serialize());
442 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(),
443 System
.currentTimeMillis(),
447 account
.addSignedPreKey(record);
451 } catch (InvalidKeyException e
) {
452 throw new AssertionError(e
);
456 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
457 verificationCode
= verificationCode
.replace("-", "");
458 account
.setSignalingKey(KeyUtils
.createSignalingKey());
459 // TODO make unrestricted unidentified access configurable
460 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
,
461 account
.getSignalingKey(),
462 account
.getSignalProtocolStore().getLocalRegistrationId(),
466 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
467 unrestrictedUnidentifiedAccess
,
469 discoverableByPhoneNumber
);
471 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
472 // TODO response.isStorageCapable()
473 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
474 account
.setRegistered(true);
475 account
.setUuid(uuid
);
476 account
.setRegistrationLockPin(pin
);
477 account
.getSignalProtocolStore()
478 .saveIdentity(account
.getSelfAddress(),
479 getIdentityKeyPair().getPublicKey(),
480 TrustLevel
.TRUSTED_VERIFIED
);
486 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
487 if (pin
.isPresent()) {
488 account
.setRegistrationLockPin(pin
.get());
489 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
491 account
.setRegistrationLockPin(null);
492 accountManager
.removeRegistrationLockV1();
497 void refreshPreKeys() throws IOException
{
498 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
499 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
500 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
502 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
505 private SignalServiceMessageReceiver
createMessageReceiver() {
506 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
507 serviceConfiguration
).getProfileOperations() : null;
508 return new SignalServiceMessageReceiver(serviceConfiguration
,
510 account
.getUsername(),
511 account
.getPassword(),
512 account
.getDeviceId(),
513 account
.getSignalingKey(),
517 clientZkProfileOperations
);
520 private SignalServiceMessageReceiver
getOrCreateMessageReceiver() {
521 if (messageReceiver
== null) {
522 messageReceiver
= createMessageReceiver();
524 return messageReceiver
;
527 private SignalServiceMessagePipe
getOrCreateMessagePipe() {
528 if (messagePipe
== null) {
529 messagePipe
= getOrCreateMessageReceiver().createMessagePipe();
534 private SignalServiceMessagePipe
getOrCreateUnidentifiedMessagePipe() {
535 if (unidentifiedMessagePipe
== null) {
536 unidentifiedMessagePipe
= getOrCreateMessageReceiver().createUnidentifiedMessagePipe();
538 return unidentifiedMessagePipe
;
541 private SignalServiceMessageSender
createMessageSender() {
542 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
543 serviceConfiguration
).getProfileOperations() : null;
544 final ExecutorService executor
= null;
545 return new SignalServiceMessageSender(serviceConfiguration
,
547 account
.getUsername(),
548 account
.getPassword(),
549 account
.getDeviceId(),
550 account
.getSignalProtocolStore(),
552 account
.isMultiDevice(),
553 Optional
.fromNullable(messagePipe
),
554 Optional
.fromNullable(unidentifiedMessagePipe
),
556 clientZkProfileOperations
,
558 ServiceConfig
.MAX_ENVELOPE_SIZE
);
561 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
) throws IOException
{
562 return profileHelper
.retrieveProfileSync(address
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
565 private SignalProfile
getRecipientProfile(
566 SignalServiceAddress address
568 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
569 if (profileEntry
== null) {
572 long now
= new Date().getTime();
573 // Profiles are cache for 24h before retrieving them again
574 if (!profileEntry
.isRequestPending() && (
575 profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000
577 ProfileKey profileKey
= profileEntry
.getProfileKey();
578 profileEntry
.setRequestPending(true);
579 SignalProfile profile
;
581 profile
= retrieveRecipientProfile(address
, profileKey
);
582 } catch (IOException e
) {
583 System
.err
.println("Failed to retrieve profile, ignoring: " + e
.getMessage());
584 profileEntry
.setRequestPending(false);
587 profileEntry
.setRequestPending(false);
588 account
.getProfileStore()
589 .updateProfile(address
, profileKey
, now
, profile
, profileEntry
.getProfileKeyCredential());
592 return profileEntry
.getProfile();
595 private ProfileKeyCredential
getRecipientProfileKeyCredential(SignalServiceAddress address
) {
596 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
597 if (profileEntry
== null) {
600 if (profileEntry
.getProfileKeyCredential() == null) {
601 ProfileAndCredential profileAndCredential
;
603 profileAndCredential
= profileHelper
.retrieveProfileSync(address
,
604 SignalServiceProfile
.RequestType
.PROFILE_AND_CREDENTIAL
);
605 } catch (IOException e
) {
606 System
.err
.println("Failed to retrieve profile key credential, ignoring: " + e
.getMessage());
610 long now
= new Date().getTime();
611 final ProfileKeyCredential profileKeyCredential
= profileAndCredential
.getProfileKeyCredential().orNull();
612 final SignalProfile profile
= decryptProfile(address
,
613 profileEntry
.getProfileKey(),
614 profileAndCredential
.getProfile());
615 account
.getProfileStore()
616 .updateProfile(address
, profileEntry
.getProfileKey(), now
, profile
, profileKeyCredential
);
617 return profileKeyCredential
;
619 return profileEntry
.getProfileKeyCredential();
622 private SignalProfile
retrieveRecipientProfile(
623 SignalServiceAddress address
, ProfileKey profileKey
624 ) throws IOException
{
625 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
);
627 return decryptProfile(address
, profileKey
, encryptedProfile
);
630 private SignalProfile
decryptProfile(
631 final SignalServiceAddress address
, final ProfileKey profileKey
, final SignalServiceProfile encryptedProfile
633 File avatarFile
= null;
635 avatarFile
= encryptedProfile
.getAvatar() == null
637 : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
638 } catch (Throwable e
) {
639 System
.err
.println("Failed to retrieve profile avatar, ignoring: " + e
.getMessage());
642 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
646 name
= encryptedProfile
.getName() == null
648 : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName())));
649 } catch (IOException e
) {
652 String unidentifiedAccess
;
654 unidentifiedAccess
= encryptedProfile
.getUnidentifiedAccess() == null
655 || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess()))
657 : encryptedProfile
.getUnidentifiedAccess();
658 } catch (IOException e
) {
659 unidentifiedAccess
= null;
661 return new SignalProfile(encryptedProfile
.getIdentityKey(),
665 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
666 encryptedProfile
.getCapabilities());
667 } catch (InvalidCiphertextException e
) {
672 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
673 File file
= getGroupAvatarFile(groupId
);
674 if (!file
.exists()) {
675 return Optional
.absent();
678 return Optional
.of(Utils
.createAttachment(file
));
681 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
682 File file
= getContactAvatarFile(number
);
683 if (!file
.exists()) {
684 return Optional
.absent();
687 return Optional
.of(Utils
.createAttachment(file
));
690 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
691 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
693 throw new GroupNotFoundException(groupId
);
695 if (!g
.isMember(account
.getSelfAddress())) {
696 throw new NotAGroupMemberException(groupId
, g
.getTitle());
701 public List
<GroupInfo
> getGroups() {
702 return account
.getGroupStore().getGroups();
705 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
706 SignalServiceDataMessage
.Builder messageBuilder
, byte[] groupId
707 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
{
708 final GroupInfo g
= getGroupForSending(groupId
);
710 GroupUtils
.setGroupContext(messageBuilder
, g
);
711 messageBuilder
.withExpiration(g
.getMessageExpirationTime());
713 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
716 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
717 String messageText
, List
<String
> attachments
, byte[] groupId
718 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
719 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
720 .withBody(messageText
);
721 if (attachments
!= null) {
722 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
725 return sendGroupMessage(messageBuilder
, groupId
);
728 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessageReaction(
729 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, byte[] groupId
730 ) throws IOException
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
731 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
733 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
734 targetSentTimestamp
);
735 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
736 .withReaction(reaction
);
738 return sendGroupMessage(messageBuilder
, groupId
);
741 public Pair
<Long
, List
<SendMessageResult
>> sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, NotAGroupMemberException
{
743 SignalServiceDataMessage
.Builder messageBuilder
;
745 final GroupInfo g
= getGroupForSending(groupId
);
746 if (g
instanceof GroupInfoV1
) {
747 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
748 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
751 messageBuilder
= SignalServiceDataMessage
.newBuilder().asGroupMessage(group
);
752 groupInfoV1
.removeMember(account
.getSelfAddress());
753 account
.getGroupStore().updateGroup(groupInfoV1
);
755 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) g
;
756 final Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.leaveGroup(groupInfoV2
);
757 groupInfoV2
.setGroup(groupGroupChangePair
.first());
758 messageBuilder
= getGroupUpdateMessageBuilder(groupInfoV2
, groupGroupChangePair
.second().toByteArray());
759 account
.getGroupStore().updateGroup(groupInfoV2
);
762 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
765 private Pair
<byte[], List
<SendMessageResult
>> sendUpdateGroupMessage(
766 byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
767 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
769 SignalServiceDataMessage
.Builder messageBuilder
;
770 if (groupId
== null) {
772 GroupInfoV2 gv2
= groupHelper
.createGroupV2(name
, members
, avatarFile
);
774 GroupInfoV1 gv1
= new GroupInfoV1(KeyUtils
.createGroupId());
775 gv1
.addMembers(Collections
.singleton(account
.getSelfAddress()));
776 updateGroupV1(gv1
, name
, members
, avatarFile
);
777 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
780 messageBuilder
= getGroupUpdateMessageBuilder(gv2
, null);
784 GroupInfo group
= getGroupForSending(groupId
);
785 if (group
instanceof GroupInfoV2
) {
786 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= null;
787 if (members
!= null) {
788 final Set
<SignalServiceAddress
> newMembers
= new HashSet
<>(members
);
789 newMembers
.removeAll(group
.getMembers());
790 if (newMembers
.size() > 0) {
791 groupGroupChangePair
= groupHelper
.updateGroupV2((GroupInfoV2
) group
, newMembers
);
794 if (groupGroupChangePair
== null || name
!= null || avatarFile
!= null) {
795 if (groupGroupChangePair
!= null) {
796 ((GroupInfoV2
) group
).setGroup(groupGroupChangePair
.first());
797 messageBuilder
= getGroupUpdateMessageBuilder((GroupInfoV2
) group
,
798 groupGroupChangePair
.second().toByteArray());
799 sendMessage(messageBuilder
, group
.getMembersWithout(account
.getSelfAddress()));
802 groupGroupChangePair
= groupHelper
.updateGroupV2((GroupInfoV2
) group
, name
, avatarFile
);
805 ((GroupInfoV2
) group
).setGroup(groupGroupChangePair
.first());
806 messageBuilder
= getGroupUpdateMessageBuilder((GroupInfoV2
) group
,
807 groupGroupChangePair
.second().toByteArray());
810 GroupInfoV1 gv1
= (GroupInfoV1
) group
;
811 updateGroupV1(gv1
, name
, members
, avatarFile
);
812 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
817 account
.getGroupStore().updateGroup(g
);
819 final Pair
<Long
, List
<SendMessageResult
>> result
= sendMessage(messageBuilder
,
820 g
.getMembersWithout(account
.getSelfAddress()));
821 return new Pair
<>(g
.groupId
, result
.second());
824 private void updateGroupV1(
827 final Collection
<SignalServiceAddress
> members
,
828 final String avatarFile
829 ) throws IOException
{
834 if (members
!= null) {
835 final Set
<String
> newE164Members
= new HashSet
<>();
836 for (SignalServiceAddress member
: members
) {
837 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
840 newE164Members
.add(member
.getNumber().get());
843 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
844 if (contacts
.size() != newE164Members
.size()) {
845 // Some of the new members are not registered on Signal
846 for (ContactTokenDetails contact
: contacts
) {
847 newE164Members
.remove(contact
.getNumber());
849 throw new IOException("Failed to add members "
850 + Util
.join(", ", newE164Members
)
851 + " to group: Not registered on Signal");
854 g
.addMembers(members
);
857 if (avatarFile
!= null) {
858 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
859 File aFile
= getGroupAvatarFile(g
.groupId
);
860 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
864 Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
865 byte[] groupId
, SignalServiceAddress recipient
866 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
868 GroupInfo group
= getGroupForSending(groupId
);
869 if (!(group
instanceof GroupInfoV1
)) {
870 throw new RuntimeException("Received an invalid group request for a v2 group!");
872 g
= (GroupInfoV1
) group
;
874 if (!g
.isMember(recipient
)) {
875 throw new NotAGroupMemberException(groupId
, g
.name
);
878 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
880 // Send group message only to the recipient who requested it
881 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
884 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV1 g
) throws AttachmentInvalidException
{
885 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
888 .withMembers(new ArrayList
<>(g
.getMembers()));
890 File aFile
= getGroupAvatarFile(g
.groupId
);
891 if (aFile
.exists()) {
893 group
.withAvatar(Utils
.createAttachment(aFile
));
894 } catch (IOException e
) {
895 throw new AttachmentInvalidException(aFile
.toString(), e
);
899 return SignalServiceDataMessage
.newBuilder()
900 .asGroupMessage(group
.build())
901 .withExpiration(g
.getMessageExpirationTime());
904 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV2 g
, byte[] signedGroupChange
) {
905 SignalServiceGroupV2
.Builder group
= SignalServiceGroupV2
.newBuilder(g
.getMasterKey())
906 .withRevision(g
.getGroup().getRevision())
907 .withSignedGroupChange(signedGroupChange
);
908 return SignalServiceDataMessage
.newBuilder()
909 .asGroupMessage(group
.build())
910 .withExpiration(g
.getMessageExpirationTime());
913 Pair
<Long
, List
<SendMessageResult
>> sendGroupInfoRequest(
914 byte[] groupId
, SignalServiceAddress recipient
915 ) throws IOException
{
916 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
919 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
920 .asGroupMessage(group
.build());
922 // Send group info request message to the recipient who sent us a message with this groupId
923 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
927 SignalServiceAddress remoteAddress
, long messageId
928 ) throws IOException
, UntrustedIdentityException
{
929 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
930 Collections
.singletonList(messageId
),
931 System
.currentTimeMillis());
933 createMessageSender().sendReceipt(remoteAddress
,
934 unidentifiedAccessHelper
.getAccessFor(remoteAddress
),
938 public Pair
<Long
, List
<SendMessageResult
>> sendMessage(
939 String messageText
, List
<String
> attachments
, List
<String
> recipients
940 ) throws IOException
, AttachmentInvalidException
, InvalidNumberException
{
941 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
942 .withBody(messageText
);
943 if (attachments
!= null) {
944 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
946 // Upload attachments here, so we only upload once even for multiple recipients
947 SignalServiceMessageSender messageSender
= createMessageSender();
948 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
949 for (SignalServiceAttachment attachment
: attachmentStreams
) {
950 if (attachment
.isStream()) {
951 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
952 } else if (attachment
.isPointer()) {
953 attachmentPointers
.add(attachment
.asPointer());
957 messageBuilder
.withAttachments(attachmentPointers
);
959 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
962 public Pair
<Long
, List
<SendMessageResult
>> sendMessageReaction(
963 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, List
<String
> recipients
964 ) throws IOException
, InvalidNumberException
{
965 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
967 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
968 targetSentTimestamp
);
969 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
970 .withReaction(reaction
);
971 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
974 public Pair
<Long
, List
<SendMessageResult
>> sendEndSessionMessage(List
<String
> recipients
) throws IOException
, InvalidNumberException
{
975 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().asEndSessionMessage();
977 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
979 return sendMessage(messageBuilder
, signalServiceAddresses
);
980 } catch (Exception e
) {
981 for (SignalServiceAddress address
: signalServiceAddresses
) {
982 handleEndSession(address
);
989 public String
getContactName(String number
) throws InvalidNumberException
{
990 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
991 if (contact
== null) {
998 public void setContactName(String number
, String name
) throws InvalidNumberException
{
999 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1000 ContactInfo contact
= account
.getContactStore().getContact(address
);
1001 if (contact
== null) {
1002 contact
= new ContactInfo(address
);
1004 contact
.name
= name
;
1005 account
.getContactStore().updateContact(contact
);
1009 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
1010 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
1013 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
1014 ContactInfo contact
= account
.getContactStore().getContact(address
);
1015 if (contact
== null) {
1016 contact
= new ContactInfo(address
);
1018 contact
.blocked
= blocked
;
1019 account
.getContactStore().updateContact(contact
);
1023 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
1024 GroupInfo group
= getGroup(groupId
);
1025 if (group
== null) {
1026 throw new GroupNotFoundException(groupId
);
1029 group
.setBlocked(blocked
);
1030 account
.getGroupStore().updateGroup(group
);
1034 public Pair
<byte[], List
<SendMessageResult
>> updateGroup(
1035 byte[] groupId
, String name
, List
<String
> members
, String avatar
1036 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
1037 return sendUpdateGroupMessage(groupId
,
1039 members
== null ?
null : getSignalServiceAddresses(members
),
1044 * Change the expiration timer for a contact
1046 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
1047 ContactInfo contact
= account
.getContactStore().getContact(address
);
1048 contact
.messageExpirationTime
= messageExpirationTimer
;
1049 account
.getContactStore().updateContact(contact
);
1050 sendExpirationTimerUpdate(address
);
1054 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
1055 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1056 .asExpirationUpdate();
1057 sendMessage(messageBuilder
, Collections
.singleton(address
));
1061 * Change the expiration timer for a contact
1063 public void setExpirationTimer(
1064 String number
, int messageExpirationTimer
1065 ) throws IOException
, InvalidNumberException
{
1066 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1067 setExpirationTimer(address
, messageExpirationTimer
);
1071 * Change the expiration timer for a group
1073 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
1074 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
1075 if (g
instanceof GroupInfoV1
) {
1076 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
1077 groupInfoV1
.messageExpirationTime
= messageExpirationTimer
;
1078 account
.getGroupStore().updateGroup(groupInfoV1
);
1080 throw new RuntimeException("TODO Not implemented!");
1085 * Upload the sticker pack from path.
1087 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
1088 * @return if successful, returns the URL to install the sticker pack in the signal app
1090 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
1091 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
1093 SignalServiceMessageSender messageSender
= createMessageSender();
1095 byte[] packKey
= KeyUtils
.createStickerUploadKey();
1096 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
1098 Sticker sticker
= new Sticker(Hex
.fromStringCondensed(packId
), packKey
);
1099 account
.getStickerStore().updateSticker(sticker
);
1103 return new URI("https",
1106 "pack_id=" + URLEncoder
.encode(packId
, StandardCharsets
.UTF_8
) + "&pack_key=" + URLEncoder
.encode(
1107 Hex
.toStringCondensed(packKey
),
1108 StandardCharsets
.UTF_8
)).toString();
1109 } catch (URISyntaxException e
) {
1110 throw new AssertionError(e
);
1114 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(
1116 ) throws IOException
, StickerPackInvalidException
{
1118 String rootPath
= null;
1120 final File file
= new File(path
);
1121 if (file
.getName().endsWith(".zip")) {
1122 zip
= new ZipFile(file
);
1123 } else if (file
.getName().equals("manifest.json")) {
1124 rootPath
= file
.getParent();
1126 throw new StickerPackInvalidException("Could not find manifest.json");
1129 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
1131 if (pack
.stickers
== null) {
1132 throw new StickerPackInvalidException("Must set a 'stickers' field.");
1135 if (pack
.stickers
.isEmpty()) {
1136 throw new StickerPackInvalidException("Must include stickers.");
1139 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
1140 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
1141 if (sticker
.file
== null) {
1142 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
1145 Pair
<InputStream
, Long
> data
;
1147 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
1148 } catch (IOException ignored
) {
1149 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
1152 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
1153 StickerInfo stickerInfo
= new StickerInfo(data
.first(),
1155 Optional
.fromNullable(sticker
.emoji
).or(""),
1157 stickers
.add(stickerInfo
);
1160 StickerInfo cover
= null;
1161 if (pack
.cover
!= null) {
1162 if (pack
.cover
.file
== null) {
1163 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
1166 Pair
<InputStream
, Long
> data
;
1168 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
1169 } catch (IOException ignored
) {
1170 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
1173 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
1174 cover
= new StickerInfo(data
.first(),
1176 Optional
.fromNullable(pack
.cover
.emoji
).or(""),
1180 return new SignalServiceStickerManifestUpload(pack
.title
, pack
.author
, cover
, stickers
);
1183 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
1184 InputStream inputStream
;
1186 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
1188 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
1190 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
1193 private static Pair
<InputStream
, Long
> getInputStreamAndLength(
1194 final String rootPath
, final ZipFile zip
, final String subfile
1195 ) throws IOException
{
1197 final ZipEntry entry
= zip
.getEntry(subfile
);
1198 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
1200 final File file
= new File(rootPath
, subfile
);
1201 return new Pair
<>(new FileInputStream(file
), file
.length());
1205 void requestSyncGroups() throws IOException
{
1206 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1207 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
)
1209 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1211 sendSyncMessage(message
);
1212 } catch (UntrustedIdentityException e
) {
1213 e
.printStackTrace();
1217 void requestSyncContacts() throws IOException
{
1218 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1219 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
)
1221 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1223 sendSyncMessage(message
);
1224 } catch (UntrustedIdentityException e
) {
1225 e
.printStackTrace();
1229 void requestSyncBlocked() throws IOException
{
1230 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1231 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
)
1233 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1235 sendSyncMessage(message
);
1236 } catch (UntrustedIdentityException e
) {
1237 e
.printStackTrace();
1241 void requestSyncConfiguration() throws IOException
{
1242 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1243 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
)
1245 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1247 sendSyncMessage(message
);
1248 } catch (UntrustedIdentityException e
) {
1249 e
.printStackTrace();
1253 private byte[] getSenderCertificate() {
1254 // TODO support UUID capable sender certificates
1255 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
1258 certificate
= accountManager
.getSenderCertificate();
1259 } catch (IOException e
) {
1260 System
.err
.println("Failed to get sender certificate: " + e
);
1263 // TODO cache for a day
1267 private void sendSyncMessage(SignalServiceSyncMessage message
) throws IOException
, UntrustedIdentityException
{
1268 SignalServiceMessageSender messageSender
= createMessageSender();
1270 messageSender
.sendMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
1271 } catch (UntrustedIdentityException e
) {
1272 account
.getSignalProtocolStore()
1273 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1275 TrustLevel
.UNTRUSTED
);
1280 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1281 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1283 for (String number
: numbers
) {
1284 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1286 return signalServiceAddresses
;
1289 private Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1290 SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
1291 ) throws IOException
{
1292 recipients
= recipients
.stream().map(this::resolveSignalServiceAddress
).collect(Collectors
.toSet());
1293 final long timestamp
= System
.currentTimeMillis();
1294 messageBuilder
.withTimestamp(timestamp
);
1295 getOrCreateMessagePipe();
1296 getOrCreateUnidentifiedMessagePipe();
1297 SignalServiceDataMessage message
= null;
1299 message
= messageBuilder
.build();
1300 if (message
.getGroupContext().isPresent()) {
1302 SignalServiceMessageSender messageSender
= createMessageSender();
1303 final boolean isRecipientUpdate
= false;
1304 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
),
1305 unidentifiedAccessHelper
.getAccessFor(recipients
),
1308 for (SendMessageResult r
: result
) {
1309 if (r
.getIdentityFailure() != null) {
1310 account
.getSignalProtocolStore()
1311 .saveIdentity(r
.getAddress(),
1312 r
.getIdentityFailure().getIdentityKey(),
1313 TrustLevel
.UNTRUSTED
);
1316 return new Pair
<>(timestamp
, result
);
1317 } catch (UntrustedIdentityException e
) {
1318 account
.getSignalProtocolStore()
1319 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1321 TrustLevel
.UNTRUSTED
);
1322 return new Pair
<>(timestamp
, Collections
.emptyList());
1325 // Send to all individually, so sync messages are sent correctly
1326 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1327 for (SignalServiceAddress address
: recipients
) {
1328 ContactInfo contact
= account
.getContactStore().getContact(address
);
1329 if (contact
!= null) {
1330 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1331 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1333 messageBuilder
.withExpiration(0);
1334 messageBuilder
.withProfileKey(null);
1336 message
= messageBuilder
.build();
1337 if (address
.matches(account
.getSelfAddress())) {
1338 results
.add(sendSelfMessage(message
));
1340 results
.add(sendMessage(address
, message
));
1343 return new Pair
<>(timestamp
, results
);
1346 if (message
!= null && message
.isEndSession()) {
1347 for (SignalServiceAddress recipient
: recipients
) {
1348 handleEndSession(recipient
);
1355 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1356 SignalServiceMessageSender messageSender
= createMessageSender();
1358 SignalServiceAddress recipient
= account
.getSelfAddress();
1360 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= unidentifiedAccessHelper
.getAccessFor(recipient
);
1361 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1362 message
.getTimestamp(),
1364 message
.getExpiresInSeconds(),
1365 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1367 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1370 long startTime
= System
.currentTimeMillis();
1371 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1372 return SendMessageResult
.success(recipient
,
1373 unidentifiedAccess
.isPresent(),
1375 System
.currentTimeMillis() - startTime
);
1376 } catch (UntrustedIdentityException e
) {
1377 account
.getSignalProtocolStore()
1378 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1380 TrustLevel
.UNTRUSTED
);
1381 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1385 private SendMessageResult
sendMessage(
1386 SignalServiceAddress address
, SignalServiceDataMessage message
1387 ) throws IOException
{
1388 SignalServiceMessageSender messageSender
= createMessageSender();
1391 return messageSender
.sendMessage(address
, unidentifiedAccessHelper
.getAccessFor(address
), message
);
1392 } catch (UntrustedIdentityException e
) {
1393 account
.getSignalProtocolStore()
1394 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1396 TrustLevel
.UNTRUSTED
);
1397 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1401 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1402 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(),
1403 account
.getSignalProtocolStore(),
1404 Utils
.getCertificateValidator());
1406 return cipher
.decrypt(envelope
);
1407 } catch (ProtocolUntrustedIdentityException e
) {
1408 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1409 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
1411 account
.getSignalProtocolStore()
1412 .saveIdentity(resolveSignalServiceAddress(identityException
.getName()),
1413 identityException
.getUntrustedIdentity(),
1414 TrustLevel
.UNTRUSTED
);
1415 throw identityException
;
1417 throw new AssertionError(e
);
1421 private void handleEndSession(SignalServiceAddress source
) {
1422 account
.getSignalProtocolStore().deleteAllSessions(source
);
1425 private static int currentTimeDays() {
1426 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
1429 private GroupsV2AuthorizationString
getGroupAuthForToday(
1430 final GroupSecretParams groupSecretParams
1431 ) throws IOException
{
1432 final int today
= currentTimeDays();
1433 // Returns credentials for the next 7 days
1434 final HashMap
<Integer
, AuthCredentialResponse
> credentials
= groupsV2Api
.getCredentials(today
);
1435 // TODO cache credentials until they expire
1436 AuthCredentialResponse authCredentialResponse
= credentials
.get(today
);
1438 return groupsV2Api
.getGroupsV2AuthorizationString(account
.getUuid(),
1441 authCredentialResponse
);
1442 } catch (VerificationFailedException e
) {
1443 throw new IOException(e
);
1447 private List
<HandleAction
> handleSignalServiceDataMessage(
1448 SignalServiceDataMessage message
,
1450 SignalServiceAddress source
,
1451 SignalServiceAddress destination
,
1452 boolean ignoreAttachments
1454 List
<HandleAction
> actions
= new ArrayList
<>();
1455 if (message
.getGroupContext().isPresent()) {
1456 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1457 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1458 GroupInfo group
= account
.getGroupStore().getGroupByV1Id(groupInfo
.getGroupId());
1459 if (group
== null || group
instanceof GroupInfoV1
) {
1460 GroupInfoV1 groupV1
= (GroupInfoV1
) group
;
1461 switch (groupInfo
.getType()) {
1463 if (groupV1
== null) {
1464 groupV1
= new GroupInfoV1(groupInfo
.getGroupId());
1467 if (groupInfo
.getAvatar().isPresent()) {
1468 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1469 if (avatar
.isPointer()) {
1471 retrieveGroupAvatarAttachment(avatar
.asPointer(), groupV1
.groupId
);
1472 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1473 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer()
1474 .getRemoteId() + "): " + e
.getMessage());
1479 if (groupInfo
.getName().isPresent()) {
1480 groupV1
.name
= groupInfo
.getName().get();
1483 if (groupInfo
.getMembers().isPresent()) {
1484 groupV1
.addMembers(groupInfo
.getMembers()
1487 .map(this::resolveSignalServiceAddress
)
1488 .collect(Collectors
.toSet()));
1491 account
.getGroupStore().updateGroup(groupV1
);
1495 if (groupV1
== null && !isSync
) {
1496 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1500 if (groupV1
!= null) {
1501 groupV1
.removeMember(source
);
1502 account
.getGroupStore().updateGroup(groupV1
);
1507 if (groupV1
!= null && !isSync
) {
1508 actions
.add(new SendGroupUpdateAction(source
, groupV1
.groupId
));
1513 // Received a group v1 message for a v2 group
1516 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1517 final SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1518 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1520 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
1522 byte[] groupId
= groupSecretParams
.getPublicParams().getGroupIdentifier().serialize();
1523 GroupInfo groupInfo
= account
.getGroupStore().getGroupByV2Id(groupId
);
1524 if (groupInfo
instanceof GroupInfoV1
) {
1525 // Received a v2 group message for a v2 group, we need to locally migrate the group
1526 account
.getGroupStore().deleteGroup(groupInfo
.groupId
);
1527 GroupInfoV2 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1528 groupInfoV2
.setGroup(getDecryptedGroup(groupSecretParams
));
1529 account
.getGroupStore().updateGroup(groupInfoV2
);
1530 System
.err
.println("Locally migrated group "
1531 + Base64
.encodeBytes(groupInfo
.groupId
)
1532 + " to group v2, id: "
1533 + Base64
.encodeBytes(groupInfoV2
.groupId
)
1535 } else if (groupInfo
== null || groupInfo
instanceof GroupInfoV2
) {
1536 GroupInfoV2 groupInfoV2
= groupInfo
== null
1537 ?
new GroupInfoV2(groupId
, groupMasterKey
)
1538 : (GroupInfoV2
) groupInfo
;
1540 if (groupInfoV2
.getGroup() == null
1541 || groupInfoV2
.getGroup().getRevision() < groupContext
.getRevision()) {
1542 DecryptedGroup group
= null;
1543 if (groupContext
.hasSignedGroupChange()
1544 && groupInfoV2
.getGroup() != null
1545 && groupInfoV2
.getGroup().getRevision() + 1 == groupContext
.getRevision()) {
1546 group
= groupHelper
.getUpdatedDecryptedGroup(groupInfoV2
.getGroup(),
1547 groupContext
.getSignedGroupChange(),
1550 if (group
== null) {
1551 group
= getDecryptedGroup(groupSecretParams
);
1553 groupInfoV2
.setGroup(group
);
1554 account
.getGroupStore().updateGroup(groupInfoV2
);
1559 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1560 if (message
.isEndSession()) {
1561 handleEndSession(conversationPartnerAddress
);
1563 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1564 if (message
.getGroupContext().isPresent()) {
1565 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1566 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1567 GroupInfoV1 group
= account
.getGroupStore().getOrCreateGroupV1(groupInfo
.getGroupId());
1568 if (group
!= null) {
1569 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1570 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1571 account
.getGroupStore().updateGroup(group
);
1574 } else if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1575 // disappearing message timer already stored in the DecryptedGroup
1578 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1579 if (contact
== null) {
1580 contact
= new ContactInfo(conversationPartnerAddress
);
1582 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1583 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1584 account
.getContactStore().updateContact(contact
);
1588 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1589 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1590 if (attachment
.isPointer()) {
1592 retrieveAttachment(attachment
.asPointer());
1593 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1594 System
.err
.println("Failed to retrieve attachment ("
1595 + attachment
.asPointer().getRemoteId()
1602 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1603 final ProfileKey profileKey
;
1605 profileKey
= new ProfileKey(message
.getProfileKey().get());
1606 } catch (InvalidInputException e
) {
1607 throw new AssertionError(e
);
1609 if (source
.matches(account
.getSelfAddress())) {
1610 this.account
.setProfileKey(profileKey
);
1612 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1614 if (message
.getPreviews().isPresent()) {
1615 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1616 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1617 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1618 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1620 retrieveAttachment(attachment
);
1621 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1622 System
.err
.println("Failed to retrieve attachment ("
1623 + attachment
.getRemoteId()
1630 if (message
.getSticker().isPresent()) {
1631 final SignalServiceDataMessage
.Sticker messageSticker
= message
.getSticker().get();
1632 Sticker sticker
= account
.getStickerStore().getSticker(messageSticker
.getPackId());
1633 if (sticker
== null) {
1634 sticker
= new Sticker(messageSticker
.getPackId(), messageSticker
.getPackKey());
1635 account
.getStickerStore().updateSticker(sticker
);
1641 private DecryptedGroup
getDecryptedGroup(final GroupSecretParams groupSecretParams
) {
1643 final GroupsV2AuthorizationString groupsV2AuthorizationString
= getGroupAuthForToday(groupSecretParams
);
1644 DecryptedGroup group
= groupsV2Api
.getGroup(groupSecretParams
, groupsV2AuthorizationString
);
1645 for (DecryptedMember member
: group
.getMembersList()) {
1646 final SignalServiceAddress address
= resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil
.parseOrThrow(
1647 member
.getUuid().toByteArray()), null));
1649 account
.getProfileStore()
1650 .storeProfileKey(address
, new ProfileKey(member
.getProfileKey().toByteArray()));
1651 } catch (InvalidInputException ignored
) {
1655 } catch (IOException
| VerificationFailedException
| InvalidGroupStateException e
) {
1656 System
.err
.println("Failed to retrieve Group V2 info, ignoring ...");
1661 private void retryFailedReceivedMessages(
1662 ReceiveMessageHandler handler
, boolean ignoreAttachments
1664 final File cachePath
= new File(getMessageCachePath());
1665 if (!cachePath
.exists()) {
1668 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1669 if (!dir
.isDirectory()) {
1670 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1674 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1675 if (!fileEntry
.isFile()) {
1678 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1680 // Try to delete directory if empty
1685 private void retryFailedReceivedMessage(
1686 final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
1688 SignalServiceEnvelope envelope
;
1690 envelope
= Utils
.loadEnvelope(fileEntry
);
1691 if (envelope
== null) {
1694 } catch (IOException e
) {
1695 e
.printStackTrace();
1698 SignalServiceContent content
= null;
1699 if (!envelope
.isReceipt()) {
1701 content
= decryptMessage(envelope
);
1702 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1704 } catch (Exception er
) {
1705 // All other errors are not recoverable, so delete the cached message
1707 Files
.delete(fileEntry
.toPath());
1708 } catch (IOException e
) {
1709 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1713 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1714 for (HandleAction action
: actions
) {
1716 action
.execute(this);
1717 } catch (Throwable e
) {
1718 e
.printStackTrace();
1723 handler
.handleMessage(envelope
, content
, null);
1725 Files
.delete(fileEntry
.toPath());
1726 } catch (IOException e
) {
1727 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1731 public void receiveMessages(
1734 boolean returnOnTimeout
,
1735 boolean ignoreAttachments
,
1736 ReceiveMessageHandler handler
1737 ) throws IOException
{
1738 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1740 Set
<HandleAction
> queuedActions
= null;
1742 getOrCreateMessagePipe();
1744 boolean hasCaughtUpWithOldMessages
= false;
1747 SignalServiceEnvelope envelope
;
1748 SignalServiceContent content
= null;
1749 Exception exception
= null;
1750 final long now
= new Date().getTime();
1752 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1753 // store message on disk, before acknowledging receipt to the server
1755 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1756 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1757 Utils
.storeEnvelope(envelope1
, cacheFile
);
1758 } catch (IOException e
) {
1759 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: "
1763 if (result
.isPresent()) {
1764 envelope
= result
.get();
1766 // Received indicator that server queue is empty
1767 hasCaughtUpWithOldMessages
= true;
1769 if (queuedActions
!= null) {
1770 for (HandleAction action
: queuedActions
) {
1772 action
.execute(this);
1773 } catch (Throwable e
) {
1774 e
.printStackTrace();
1778 queuedActions
.clear();
1779 queuedActions
= null;
1782 // Continue to wait another timeout for new messages
1785 } catch (TimeoutException e
) {
1786 if (returnOnTimeout
) return;
1788 } catch (InvalidVersionException e
) {
1789 System
.err
.println("Ignoring error: " + e
.getMessage());
1793 if (envelope
.hasSource()) {
1794 // Store uuid if we don't have it already
1795 SignalServiceAddress source
= envelope
.getSourceAddress();
1796 resolveSignalServiceAddress(source
);
1798 if (!envelope
.isReceipt()) {
1800 content
= decryptMessage(envelope
);
1801 } catch (Exception e
) {
1804 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1805 if (hasCaughtUpWithOldMessages
) {
1806 for (HandleAction action
: actions
) {
1808 action
.execute(this);
1809 } catch (Throwable e
) {
1810 e
.printStackTrace();
1814 if (queuedActions
== null) {
1815 queuedActions
= new HashSet
<>();
1817 queuedActions
.addAll(actions
);
1821 if (!isMessageBlocked(envelope
, content
)) {
1822 handler
.handleMessage(envelope
, content
, exception
);
1824 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1825 File cacheFile
= null;
1827 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1828 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1829 Files
.delete(cacheFile
.toPath());
1830 // Try to delete directory if empty
1831 new File(getMessageCachePath()).delete();
1832 } catch (IOException e
) {
1833 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1839 private boolean isMessageBlocked(
1840 SignalServiceEnvelope envelope
, SignalServiceContent content
1842 SignalServiceAddress source
;
1843 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1844 source
= envelope
.getSourceAddress();
1845 } else if (content
!= null) {
1846 source
= content
.getSender();
1850 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1851 if (sourceContact
!= null && sourceContact
.blocked
) {
1855 if (content
!= null && content
.getDataMessage().isPresent()) {
1856 SignalServiceDataMessage message
= content
.getDataMessage().get();
1857 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1858 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1859 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1860 return groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.isBlocked();
1866 private List
<HandleAction
> handleMessage(
1867 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
1869 List
<HandleAction
> actions
= new ArrayList
<>();
1870 if (content
!= null) {
1871 final SignalServiceAddress sender
;
1872 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1873 sender
= envelope
.getSourceAddress();
1875 sender
= content
.getSender();
1877 // Store uuid if we don't have it already
1878 resolveSignalServiceAddress(sender
);
1880 if (content
.getDataMessage().isPresent()) {
1881 SignalServiceDataMessage message
= content
.getDataMessage().get();
1883 if (content
.isNeedsReceipt()) {
1884 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1887 actions
.addAll(handleSignalServiceDataMessage(message
,
1890 account
.getSelfAddress(),
1891 ignoreAttachments
));
1893 if (content
.getSyncMessage().isPresent()) {
1894 account
.setMultiDevice(true);
1895 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1896 if (syncMessage
.getSent().isPresent()) {
1897 SentTranscriptMessage message
= syncMessage
.getSent().get();
1898 final SignalServiceAddress destination
= message
.getDestination().orNull();
1899 if (destination
!= null) {
1900 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
1904 ignoreAttachments
));
1907 if (syncMessage
.getRequest().isPresent()) {
1908 RequestMessage rm
= syncMessage
.getRequest().get();
1909 if (rm
.isContactsRequest()) {
1910 actions
.add(SendSyncContactsAction
.create());
1912 if (rm
.isGroupsRequest()) {
1913 actions
.add(SendSyncGroupsAction
.create());
1915 if (rm
.isBlockedListRequest()) {
1916 actions
.add(SendSyncBlockedListAction
.create());
1918 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
1920 if (syncMessage
.getGroups().isPresent()) {
1921 File tmpFile
= null;
1923 tmpFile
= IOUtils
.createTempFile();
1924 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups()
1926 .asPointer(), tmpFile
)) {
1927 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1929 while ((g
= s
.read()) != null) {
1930 GroupInfoV1 syncGroup
= account
.getGroupStore().getOrCreateGroupV1(g
.getId());
1931 if (syncGroup
!= null) {
1932 if (g
.getName().isPresent()) {
1933 syncGroup
.name
= g
.getName().get();
1935 syncGroup
.addMembers(g
.getMembers()
1937 .map(this::resolveSignalServiceAddress
)
1938 .collect(Collectors
.toSet()));
1939 if (!g
.isActive()) {
1940 syncGroup
.removeMember(account
.getSelfAddress());
1942 // Add ourself to the member set as it's marked as active
1943 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1945 syncGroup
.blocked
= g
.isBlocked();
1946 if (g
.getColor().isPresent()) {
1947 syncGroup
.color
= g
.getColor().get();
1950 if (g
.getAvatar().isPresent()) {
1951 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1953 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1954 syncGroup
.archived
= g
.isArchived();
1955 account
.getGroupStore().updateGroup(syncGroup
);
1959 } catch (Exception e
) {
1960 e
.printStackTrace();
1962 if (tmpFile
!= null) {
1964 Files
.delete(tmpFile
.toPath());
1965 } catch (IOException e
) {
1966 System
.err
.println("Failed to delete received groups temp file “"
1974 if (syncMessage
.getBlockedList().isPresent()) {
1975 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1976 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1977 setContactBlocked(resolveSignalServiceAddress(address
), true);
1979 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1981 setGroupBlocked(groupId
, true);
1982 } catch (GroupNotFoundException e
) {
1983 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: "
1984 + Base64
.encodeBytes(groupId
));
1988 if (syncMessage
.getContacts().isPresent()) {
1989 File tmpFile
= null;
1991 tmpFile
= IOUtils
.createTempFile();
1992 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1993 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream()
1994 .asPointer(), tmpFile
)) {
1995 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1996 if (contactsMessage
.isComplete()) {
1997 account
.getContactStore().clear();
2000 while ((c
= s
.read()) != null) {
2001 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
2002 account
.setProfileKey(c
.getProfileKey().get());
2004 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
2005 ContactInfo contact
= account
.getContactStore().getContact(address
);
2006 if (contact
== null) {
2007 contact
= new ContactInfo(address
);
2009 if (c
.getName().isPresent()) {
2010 contact
.name
= c
.getName().get();
2012 if (c
.getColor().isPresent()) {
2013 contact
.color
= c
.getColor().get();
2015 if (c
.getProfileKey().isPresent()) {
2016 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
2018 if (c
.getVerified().isPresent()) {
2019 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
2020 account
.getSignalProtocolStore()
2021 .setIdentityTrustLevel(verifiedMessage
.getDestination(),
2022 verifiedMessage
.getIdentityKey(),
2023 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2025 if (c
.getExpirationTimer().isPresent()) {
2026 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
2028 contact
.blocked
= c
.isBlocked();
2029 contact
.inboxPosition
= c
.getInboxPosition().orNull();
2030 contact
.archived
= c
.isArchived();
2031 account
.getContactStore().updateContact(contact
);
2033 if (c
.getAvatar().isPresent()) {
2034 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
2038 } catch (Exception e
) {
2039 e
.printStackTrace();
2041 if (tmpFile
!= null) {
2043 Files
.delete(tmpFile
.toPath());
2044 } catch (IOException e
) {
2045 System
.err
.println("Failed to delete received contacts temp file “"
2053 if (syncMessage
.getVerified().isPresent()) {
2054 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
2055 account
.getSignalProtocolStore()
2056 .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()),
2057 verifiedMessage
.getIdentityKey(),
2058 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2060 if (syncMessage
.getStickerPackOperations().isPresent()) {
2061 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations()
2063 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
2064 if (!m
.getPackId().isPresent()) {
2067 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
2068 if (sticker
== null) {
2069 if (!m
.getPackKey().isPresent()) {
2072 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
2074 sticker
.setInstalled(!m
.getType().isPresent()
2075 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
2076 account
.getStickerStore().updateSticker(sticker
);
2079 if (syncMessage
.getConfiguration().isPresent()) {
2087 private File
getContactAvatarFile(String number
) {
2088 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
2091 private File
retrieveContactAvatarAttachment(
2092 SignalServiceAttachment attachment
, String number
2093 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2094 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2095 if (attachment
.isPointer()) {
2096 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2097 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
2099 SignalServiceAttachmentStream stream
= attachment
.asStream();
2100 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
2104 private File
getGroupAvatarFile(byte[] groupId
) {
2105 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
2108 private File
retrieveGroupAvatarAttachment(
2109 SignalServiceAttachment attachment
, byte[] groupId
2110 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2111 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2112 if (attachment
.isPointer()) {
2113 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2114 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
2116 SignalServiceAttachmentStream stream
= attachment
.asStream();
2117 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
2121 private File
getProfileAvatarFile(SignalServiceAddress address
) {
2122 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
2125 private File
retrieveProfileAvatar(
2126 SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
2127 ) throws IOException
{
2128 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2129 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2130 File outputFile
= getProfileAvatarFile(address
);
2132 File tmpFile
= IOUtils
.createTempFile();
2133 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
,
2136 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2137 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2138 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
2141 Files
.delete(tmpFile
.toPath());
2142 } catch (IOException e
) {
2143 System
.err
.println("Failed to delete received avatar temp file “" + tmpFile
+ "”: " + e
.getMessage());
2149 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
2150 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
2153 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2154 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
2155 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
2158 private File
retrieveAttachment(
2159 SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
2160 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2161 if (storePreview
&& pointer
.getPreview().isPresent()) {
2162 File previewFile
= new File(outputFile
+ ".preview");
2163 try (OutputStream output
= new FileOutputStream(previewFile
)) {
2164 byte[] preview
= pointer
.getPreview().get();
2165 output
.write(preview
, 0, preview
.length
);
2166 } catch (FileNotFoundException e
) {
2167 e
.printStackTrace();
2172 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2174 File tmpFile
= IOUtils
.createTempFile();
2175 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
,
2177 ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
2178 IOUtils
.copyStreamToFile(input
, outputFile
);
2181 Files
.delete(tmpFile
.toPath());
2182 } catch (IOException e
) {
2183 System
.err
.println("Failed to delete received attachment temp file “"
2192 private InputStream
retrieveAttachmentAsStream(
2193 SignalServiceAttachmentPointer pointer
, File tmpFile
2194 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2195 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2196 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
2199 void sendGroups() throws IOException
, UntrustedIdentityException
{
2200 File groupsFile
= IOUtils
.createTempFile();
2203 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
2204 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
2205 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2206 if (record instanceof GroupInfoV1
) {
2207 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
2208 out
.write(new DeviceGroup(groupInfo
.groupId
,
2209 Optional
.fromNullable(groupInfo
.name
),
2210 new ArrayList
<>(groupInfo
.getMembers()),
2211 createGroupAvatarAttachment(groupInfo
.groupId
),
2212 groupInfo
.isMember(account
.getSelfAddress()),
2213 Optional
.of(groupInfo
.messageExpirationTime
),
2214 Optional
.fromNullable(groupInfo
.color
),
2216 Optional
.fromNullable(groupInfo
.inboxPosition
),
2217 groupInfo
.archived
));
2222 if (groupsFile
.exists() && groupsFile
.length() > 0) {
2223 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
2224 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2225 .withStream(groupsFileStream
)
2226 .withContentType("application/octet-stream")
2227 .withLength(groupsFile
.length())
2230 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
2235 Files
.delete(groupsFile
.toPath());
2236 } catch (IOException e
) {
2237 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
2242 public void sendContacts() throws IOException
, UntrustedIdentityException
{
2243 File contactsFile
= IOUtils
.createTempFile();
2246 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
2247 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
2248 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2249 VerifiedMessage verifiedMessage
= null;
2250 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore()
2251 .getIdentity(record.getAddress());
2252 if (currentIdentity
!= null) {
2253 verifiedMessage
= new VerifiedMessage(record.getAddress(),
2254 currentIdentity
.getIdentityKey(),
2255 currentIdentity
.getTrustLevel().toVerifiedState(),
2256 currentIdentity
.getDateAdded().getTime());
2259 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
2260 out
.write(new DeviceContact(record.getAddress(),
2261 Optional
.fromNullable(record.name
),
2262 createContactAvatarAttachment(record.number
),
2263 Optional
.fromNullable(record.color
),
2264 Optional
.fromNullable(verifiedMessage
),
2265 Optional
.fromNullable(profileKey
),
2267 Optional
.of(record.messageExpirationTime
),
2268 Optional
.fromNullable(record.inboxPosition
),
2272 if (account
.getProfileKey() != null) {
2273 // Send our own profile key as well
2274 out
.write(new DeviceContact(account
.getSelfAddress(),
2279 Optional
.of(account
.getProfileKey()),
2287 if (contactsFile
.exists() && contactsFile
.length() > 0) {
2288 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
2289 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2290 .withStream(contactsFileStream
)
2291 .withContentType("application/octet-stream")
2292 .withLength(contactsFile
.length())
2295 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
2300 Files
.delete(contactsFile
.toPath());
2301 } catch (IOException e
) {
2302 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
2307 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2308 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2309 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2310 if (record.blocked
) {
2311 addresses
.add(record.getAddress());
2314 List
<byte[]> groupIds
= new ArrayList
<>();
2315 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2316 if (record.isBlocked()) {
2317 groupIds
.add(record.groupId
);
2320 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2323 private void sendVerifiedMessage(
2324 SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
2325 ) throws IOException
, UntrustedIdentityException
{
2326 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
,
2328 trustLevel
.toVerifiedState(),
2329 System
.currentTimeMillis());
2330 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2333 public List
<ContactInfo
> getContacts() {
2334 return account
.getContactStore().getContacts();
2337 public ContactInfo
getContact(String number
) {
2338 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
2341 public GroupInfo
getGroup(byte[] groupId
) {
2342 return account
.getGroupStore().getGroup(groupId
);
2345 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
2346 return account
.getSignalProtocolStore().getIdentities();
2349 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
2350 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2354 * Trust this the identity with this fingerprint
2356 * @param name username of the identity
2357 * @param fingerprint Fingerprint
2359 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2360 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2361 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2365 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2366 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2370 account
.getSignalProtocolStore()
2371 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2373 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2374 } catch (IOException
| UntrustedIdentityException e
) {
2375 e
.printStackTrace();
2384 * Trust this the identity with this safety number
2386 * @param name username of the identity
2387 * @param safetyNumber Safety number
2389 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2390 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2391 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2395 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2396 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2400 account
.getSignalProtocolStore()
2401 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2403 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2404 } catch (IOException
| UntrustedIdentityException e
) {
2405 e
.printStackTrace();
2414 * Trust all keys of this identity without verification
2416 * @param name username of the identity
2418 public boolean trustIdentityAllKeys(String name
) {
2419 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2420 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2424 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2425 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2426 account
.getSignalProtocolStore()
2427 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2429 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2430 } catch (IOException
| UntrustedIdentityException e
) {
2431 e
.printStackTrace();
2439 public String
computeSafetyNumber(
2440 SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
2442 return Utils
.computeSafetyNumber(account
.getSelfAddress(),
2443 getIdentityKeyPair().getPublicKey(),
2448 void saveAccount() {
2452 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2453 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
)
2455 : Util
.canonicalizeNumber(identifier
, account
.getUsername());
2456 return resolveSignalServiceAddress(canonicalizedNumber
);
2459 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2460 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2462 return resolveSignalServiceAddress(address
);
2465 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2466 if (address
.matches(account
.getSelfAddress())) {
2467 return account
.getSelfAddress();
2470 return account
.getRecipientStore().resolveServiceAddress(address
);
2474 public void close() throws IOException
{
2475 if (messagePipe
!= null) {
2476 messagePipe
.shutdown();
2480 if (unidentifiedMessagePipe
!= null) {
2481 unidentifiedMessagePipe
.shutdown();
2482 unidentifiedMessagePipe
= null;
2488 public interface ReceiveMessageHandler
{
2490 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);