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
{
742 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
).withId(groupId
).build();
744 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().asGroupMessage(group
);
746 final GroupInfo g
= getGroupForSending(groupId
);
747 if (g
instanceof GroupInfoV1
) {
748 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
749 groupInfoV1
.removeMember(account
.getSelfAddress());
750 account
.getGroupStore().updateGroup(groupInfoV1
);
752 throw new RuntimeException("TODO Not implemented!");
755 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
758 private Pair
<byte[], List
<SendMessageResult
>> sendUpdateGroupMessage(
759 byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
760 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
762 SignalServiceDataMessage
.Builder messageBuilder
;
763 if (groupId
== null) {
765 GroupInfoV2 gv2
= groupHelper
.createGroupV2(name
, members
, avatarFile
);
767 GroupInfoV1 gv1
= new GroupInfoV1(KeyUtils
.createGroupId());
768 gv1
.addMembers(Collections
.singleton(account
.getSelfAddress()));
769 updateGroupV1(gv1
, name
, members
, avatarFile
);
770 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
773 messageBuilder
= getGroupUpdateMessageBuilder(gv2
, null);
777 GroupInfo group
= getGroupForSending(groupId
);
778 if (group
instanceof GroupInfoV2
) {
779 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= null;
780 if (members
!= null) {
781 final Set
<SignalServiceAddress
> newMembers
= new HashSet
<>(members
);
782 newMembers
.removeAll(group
.getMembers());
783 if (newMembers
.size() > 0) {
784 groupGroupChangePair
= groupHelper
.updateGroupV2((GroupInfoV2
) group
, newMembers
);
787 if (groupGroupChangePair
== null || name
!= null || avatarFile
!= null) {
788 if (groupGroupChangePair
!= null) {
789 ((GroupInfoV2
) group
).setGroup(groupGroupChangePair
.first());
790 messageBuilder
= getGroupUpdateMessageBuilder((GroupInfoV2
) group
,
791 groupGroupChangePair
.second().toByteArray());
792 sendMessage(messageBuilder
, group
.getMembersWithout(account
.getSelfAddress()));
795 groupGroupChangePair
= groupHelper
.updateGroupV2((GroupInfoV2
) group
, name
, avatarFile
);
798 ((GroupInfoV2
) group
).setGroup(groupGroupChangePair
.first());
799 messageBuilder
= getGroupUpdateMessageBuilder((GroupInfoV2
) group
,
800 groupGroupChangePair
.second().toByteArray());
803 GroupInfoV1 gv1
= (GroupInfoV1
) group
;
804 updateGroupV1(gv1
, name
, members
, avatarFile
);
805 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
810 account
.getGroupStore().updateGroup(g
);
812 final Pair
<Long
, List
<SendMessageResult
>> result
= sendMessage(messageBuilder
,
813 g
.getMembersWithout(account
.getSelfAddress()));
814 return new Pair
<>(g
.groupId
, result
.second());
817 private void updateGroupV1(
820 final Collection
<SignalServiceAddress
> members
,
821 final String avatarFile
822 ) throws IOException
{
827 if (members
!= null) {
828 final Set
<String
> newE164Members
= new HashSet
<>();
829 for (SignalServiceAddress member
: members
) {
830 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
833 newE164Members
.add(member
.getNumber().get());
836 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
837 if (contacts
.size() != newE164Members
.size()) {
838 // Some of the new members are not registered on Signal
839 for (ContactTokenDetails contact
: contacts
) {
840 newE164Members
.remove(contact
.getNumber());
842 throw new IOException("Failed to add members "
843 + Util
.join(", ", newE164Members
)
844 + " to group: Not registered on Signal");
847 g
.addMembers(members
);
850 if (avatarFile
!= null) {
851 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
852 File aFile
= getGroupAvatarFile(g
.groupId
);
853 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
857 Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
858 byte[] groupId
, SignalServiceAddress recipient
859 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
861 GroupInfo group
= getGroupForSending(groupId
);
862 if (!(group
instanceof GroupInfoV1
)) {
863 throw new RuntimeException("Received an invalid group request for a v2 group!");
865 g
= (GroupInfoV1
) group
;
867 if (!g
.isMember(recipient
)) {
868 throw new NotAGroupMemberException(groupId
, g
.name
);
871 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
873 // Send group message only to the recipient who requested it
874 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
877 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV1 g
) throws AttachmentInvalidException
{
878 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
881 .withMembers(new ArrayList
<>(g
.getMembers()));
883 File aFile
= getGroupAvatarFile(g
.groupId
);
884 if (aFile
.exists()) {
886 group
.withAvatar(Utils
.createAttachment(aFile
));
887 } catch (IOException e
) {
888 throw new AttachmentInvalidException(aFile
.toString(), e
);
892 return SignalServiceDataMessage
.newBuilder()
893 .asGroupMessage(group
.build())
894 .withExpiration(g
.getMessageExpirationTime());
897 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV2 g
, byte[] signedGroupChange
) {
898 SignalServiceGroupV2
.Builder group
= SignalServiceGroupV2
.newBuilder(g
.getMasterKey())
899 .withRevision(g
.getGroup().getRevision())
900 .withSignedGroupChange(signedGroupChange
);
901 return SignalServiceDataMessage
.newBuilder()
902 .asGroupMessage(group
.build())
903 .withExpiration(g
.getMessageExpirationTime());
906 Pair
<Long
, List
<SendMessageResult
>> sendGroupInfoRequest(
907 byte[] groupId
, SignalServiceAddress recipient
908 ) throws IOException
{
909 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
912 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
913 .asGroupMessage(group
.build());
915 // Send group info request message to the recipient who sent us a message with this groupId
916 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
920 SignalServiceAddress remoteAddress
, long messageId
921 ) throws IOException
, UntrustedIdentityException
{
922 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
923 Collections
.singletonList(messageId
),
924 System
.currentTimeMillis());
926 createMessageSender().sendReceipt(remoteAddress
,
927 unidentifiedAccessHelper
.getAccessFor(remoteAddress
),
931 public Pair
<Long
, List
<SendMessageResult
>> sendMessage(
932 String messageText
, List
<String
> attachments
, List
<String
> recipients
933 ) throws IOException
, AttachmentInvalidException
, InvalidNumberException
{
934 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
935 .withBody(messageText
);
936 if (attachments
!= null) {
937 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
939 // Upload attachments here, so we only upload once even for multiple recipients
940 SignalServiceMessageSender messageSender
= createMessageSender();
941 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
942 for (SignalServiceAttachment attachment
: attachmentStreams
) {
943 if (attachment
.isStream()) {
944 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
945 } else if (attachment
.isPointer()) {
946 attachmentPointers
.add(attachment
.asPointer());
950 messageBuilder
.withAttachments(attachmentPointers
);
952 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
955 public Pair
<Long
, List
<SendMessageResult
>> sendMessageReaction(
956 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, List
<String
> recipients
957 ) throws IOException
, InvalidNumberException
{
958 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
960 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
961 targetSentTimestamp
);
962 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
963 .withReaction(reaction
);
964 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
967 public Pair
<Long
, List
<SendMessageResult
>> sendEndSessionMessage(List
<String
> recipients
) throws IOException
, InvalidNumberException
{
968 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().asEndSessionMessage();
970 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
972 return sendMessage(messageBuilder
, signalServiceAddresses
);
973 } catch (Exception e
) {
974 for (SignalServiceAddress address
: signalServiceAddresses
) {
975 handleEndSession(address
);
982 public String
getContactName(String number
) throws InvalidNumberException
{
983 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
984 if (contact
== null) {
991 public void setContactName(String number
, String name
) throws InvalidNumberException
{
992 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
993 ContactInfo contact
= account
.getContactStore().getContact(address
);
994 if (contact
== null) {
995 contact
= new ContactInfo(address
);
998 account
.getContactStore().updateContact(contact
);
1002 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
1003 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
1006 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
1007 ContactInfo contact
= account
.getContactStore().getContact(address
);
1008 if (contact
== null) {
1009 contact
= new ContactInfo(address
);
1011 contact
.blocked
= blocked
;
1012 account
.getContactStore().updateContact(contact
);
1016 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
1017 GroupInfo group
= getGroup(groupId
);
1018 if (group
== null) {
1019 throw new GroupNotFoundException(groupId
);
1022 group
.setBlocked(blocked
);
1023 account
.getGroupStore().updateGroup(group
);
1027 public Pair
<byte[], List
<SendMessageResult
>> updateGroup(
1028 byte[] groupId
, String name
, List
<String
> members
, String avatar
1029 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
1030 return sendUpdateGroupMessage(groupId
,
1032 members
== null ?
null : getSignalServiceAddresses(members
),
1037 * Change the expiration timer for a contact
1039 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
1040 ContactInfo contact
= account
.getContactStore().getContact(address
);
1041 contact
.messageExpirationTime
= messageExpirationTimer
;
1042 account
.getContactStore().updateContact(contact
);
1043 sendExpirationTimerUpdate(address
);
1047 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
1048 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1049 .asExpirationUpdate();
1050 sendMessage(messageBuilder
, Collections
.singleton(address
));
1054 * Change the expiration timer for a contact
1056 public void setExpirationTimer(
1057 String number
, int messageExpirationTimer
1058 ) throws IOException
, InvalidNumberException
{
1059 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1060 setExpirationTimer(address
, messageExpirationTimer
);
1064 * Change the expiration timer for a group
1066 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
1067 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
1068 if (g
instanceof GroupInfoV1
) {
1069 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
1070 groupInfoV1
.messageExpirationTime
= messageExpirationTimer
;
1071 account
.getGroupStore().updateGroup(groupInfoV1
);
1073 throw new RuntimeException("TODO Not implemented!");
1078 * Upload the sticker pack from path.
1080 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
1081 * @return if successful, returns the URL to install the sticker pack in the signal app
1083 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
1084 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
1086 SignalServiceMessageSender messageSender
= createMessageSender();
1088 byte[] packKey
= KeyUtils
.createStickerUploadKey();
1089 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
1091 Sticker sticker
= new Sticker(Hex
.fromStringCondensed(packId
), packKey
);
1092 account
.getStickerStore().updateSticker(sticker
);
1096 return new URI("https",
1099 "pack_id=" + URLEncoder
.encode(packId
, StandardCharsets
.UTF_8
) + "&pack_key=" + URLEncoder
.encode(
1100 Hex
.toStringCondensed(packKey
),
1101 StandardCharsets
.UTF_8
)).toString();
1102 } catch (URISyntaxException e
) {
1103 throw new AssertionError(e
);
1107 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(
1109 ) throws IOException
, StickerPackInvalidException
{
1111 String rootPath
= null;
1113 final File file
= new File(path
);
1114 if (file
.getName().endsWith(".zip")) {
1115 zip
= new ZipFile(file
);
1116 } else if (file
.getName().equals("manifest.json")) {
1117 rootPath
= file
.getParent();
1119 throw new StickerPackInvalidException("Could not find manifest.json");
1122 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
1124 if (pack
.stickers
== null) {
1125 throw new StickerPackInvalidException("Must set a 'stickers' field.");
1128 if (pack
.stickers
.isEmpty()) {
1129 throw new StickerPackInvalidException("Must include stickers.");
1132 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
1133 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
1134 if (sticker
.file
== null) {
1135 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
1138 Pair
<InputStream
, Long
> data
;
1140 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
1141 } catch (IOException ignored
) {
1142 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
1145 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
1146 StickerInfo stickerInfo
= new StickerInfo(data
.first(),
1148 Optional
.fromNullable(sticker
.emoji
).or(""),
1150 stickers
.add(stickerInfo
);
1153 StickerInfo cover
= null;
1154 if (pack
.cover
!= null) {
1155 if (pack
.cover
.file
== null) {
1156 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
1159 Pair
<InputStream
, Long
> data
;
1161 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
1162 } catch (IOException ignored
) {
1163 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
1166 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
1167 cover
= new StickerInfo(data
.first(),
1169 Optional
.fromNullable(pack
.cover
.emoji
).or(""),
1173 return new SignalServiceStickerManifestUpload(pack
.title
, pack
.author
, cover
, stickers
);
1176 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
1177 InputStream inputStream
;
1179 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
1181 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
1183 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
1186 private static Pair
<InputStream
, Long
> getInputStreamAndLength(
1187 final String rootPath
, final ZipFile zip
, final String subfile
1188 ) throws IOException
{
1190 final ZipEntry entry
= zip
.getEntry(subfile
);
1191 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
1193 final File file
= new File(rootPath
, subfile
);
1194 return new Pair
<>(new FileInputStream(file
), file
.length());
1198 void requestSyncGroups() throws IOException
{
1199 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1200 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
)
1202 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1204 sendSyncMessage(message
);
1205 } catch (UntrustedIdentityException e
) {
1206 e
.printStackTrace();
1210 void requestSyncContacts() throws IOException
{
1211 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1212 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
)
1214 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1216 sendSyncMessage(message
);
1217 } catch (UntrustedIdentityException e
) {
1218 e
.printStackTrace();
1222 void requestSyncBlocked() throws IOException
{
1223 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1224 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
)
1226 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1228 sendSyncMessage(message
);
1229 } catch (UntrustedIdentityException e
) {
1230 e
.printStackTrace();
1234 void requestSyncConfiguration() throws IOException
{
1235 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1236 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
)
1238 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1240 sendSyncMessage(message
);
1241 } catch (UntrustedIdentityException e
) {
1242 e
.printStackTrace();
1246 private byte[] getSenderCertificate() {
1247 // TODO support UUID capable sender certificates
1248 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
1251 certificate
= accountManager
.getSenderCertificate();
1252 } catch (IOException e
) {
1253 System
.err
.println("Failed to get sender certificate: " + e
);
1256 // TODO cache for a day
1260 private void sendSyncMessage(SignalServiceSyncMessage message
) throws IOException
, UntrustedIdentityException
{
1261 SignalServiceMessageSender messageSender
= createMessageSender();
1263 messageSender
.sendMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
1264 } catch (UntrustedIdentityException e
) {
1265 account
.getSignalProtocolStore()
1266 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1268 TrustLevel
.UNTRUSTED
);
1273 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1274 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1276 for (String number
: numbers
) {
1277 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1279 return signalServiceAddresses
;
1282 private Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1283 SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
1284 ) throws IOException
{
1285 recipients
= recipients
.stream().map(this::resolveSignalServiceAddress
).collect(Collectors
.toSet());
1286 final long timestamp
= System
.currentTimeMillis();
1287 messageBuilder
.withTimestamp(timestamp
);
1288 getOrCreateMessagePipe();
1289 getOrCreateUnidentifiedMessagePipe();
1290 SignalServiceDataMessage message
= null;
1292 message
= messageBuilder
.build();
1293 if (message
.getGroupContext().isPresent()) {
1295 SignalServiceMessageSender messageSender
= createMessageSender();
1296 final boolean isRecipientUpdate
= false;
1297 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
),
1298 unidentifiedAccessHelper
.getAccessFor(recipients
),
1301 for (SendMessageResult r
: result
) {
1302 if (r
.getIdentityFailure() != null) {
1303 account
.getSignalProtocolStore()
1304 .saveIdentity(r
.getAddress(),
1305 r
.getIdentityFailure().getIdentityKey(),
1306 TrustLevel
.UNTRUSTED
);
1309 return new Pair
<>(timestamp
, result
);
1310 } catch (UntrustedIdentityException e
) {
1311 account
.getSignalProtocolStore()
1312 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1314 TrustLevel
.UNTRUSTED
);
1315 return new Pair
<>(timestamp
, Collections
.emptyList());
1318 // Send to all individually, so sync messages are sent correctly
1319 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1320 for (SignalServiceAddress address
: recipients
) {
1321 ContactInfo contact
= account
.getContactStore().getContact(address
);
1322 if (contact
!= null) {
1323 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1324 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1326 messageBuilder
.withExpiration(0);
1327 messageBuilder
.withProfileKey(null);
1329 message
= messageBuilder
.build();
1330 if (address
.matches(account
.getSelfAddress())) {
1331 results
.add(sendSelfMessage(message
));
1333 results
.add(sendMessage(address
, message
));
1336 return new Pair
<>(timestamp
, results
);
1339 if (message
!= null && message
.isEndSession()) {
1340 for (SignalServiceAddress recipient
: recipients
) {
1341 handleEndSession(recipient
);
1348 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1349 SignalServiceMessageSender messageSender
= createMessageSender();
1351 SignalServiceAddress recipient
= account
.getSelfAddress();
1353 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= unidentifiedAccessHelper
.getAccessFor(recipient
);
1354 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1355 message
.getTimestamp(),
1357 message
.getExpiresInSeconds(),
1358 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1360 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1363 long startTime
= System
.currentTimeMillis();
1364 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1365 return SendMessageResult
.success(recipient
,
1366 unidentifiedAccess
.isPresent(),
1368 System
.currentTimeMillis() - startTime
);
1369 } catch (UntrustedIdentityException e
) {
1370 account
.getSignalProtocolStore()
1371 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1373 TrustLevel
.UNTRUSTED
);
1374 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1378 private SendMessageResult
sendMessage(
1379 SignalServiceAddress address
, SignalServiceDataMessage message
1380 ) throws IOException
{
1381 SignalServiceMessageSender messageSender
= createMessageSender();
1384 return messageSender
.sendMessage(address
, unidentifiedAccessHelper
.getAccessFor(address
), message
);
1385 } catch (UntrustedIdentityException e
) {
1386 account
.getSignalProtocolStore()
1387 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1389 TrustLevel
.UNTRUSTED
);
1390 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1394 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1395 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(),
1396 account
.getSignalProtocolStore(),
1397 Utils
.getCertificateValidator());
1399 return cipher
.decrypt(envelope
);
1400 } catch (ProtocolUntrustedIdentityException e
) {
1401 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1402 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
1404 account
.getSignalProtocolStore()
1405 .saveIdentity(resolveSignalServiceAddress(identityException
.getName()),
1406 identityException
.getUntrustedIdentity(),
1407 TrustLevel
.UNTRUSTED
);
1408 throw identityException
;
1410 throw new AssertionError(e
);
1414 private void handleEndSession(SignalServiceAddress source
) {
1415 account
.getSignalProtocolStore().deleteAllSessions(source
);
1418 private static int currentTimeDays() {
1419 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
1422 private GroupsV2AuthorizationString
getGroupAuthForToday(
1423 final GroupSecretParams groupSecretParams
1424 ) throws IOException
{
1425 final int today
= currentTimeDays();
1426 // Returns credentials for the next 7 days
1427 final HashMap
<Integer
, AuthCredentialResponse
> credentials
= groupsV2Api
.getCredentials(today
);
1428 // TODO cache credentials until they expire
1429 AuthCredentialResponse authCredentialResponse
= credentials
.get(today
);
1431 return groupsV2Api
.getGroupsV2AuthorizationString(account
.getUuid(),
1434 authCredentialResponse
);
1435 } catch (VerificationFailedException e
) {
1436 throw new IOException(e
);
1440 private List
<HandleAction
> handleSignalServiceDataMessage(
1441 SignalServiceDataMessage message
,
1443 SignalServiceAddress source
,
1444 SignalServiceAddress destination
,
1445 boolean ignoreAttachments
1447 List
<HandleAction
> actions
= new ArrayList
<>();
1448 if (message
.getGroupContext().isPresent()) {
1449 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1450 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1451 GroupInfo group
= account
.getGroupStore().getGroupByV1Id(groupInfo
.getGroupId());
1452 if (group
== null || group
instanceof GroupInfoV1
) {
1453 GroupInfoV1 groupV1
= (GroupInfoV1
) group
;
1454 switch (groupInfo
.getType()) {
1456 if (groupV1
== null) {
1457 groupV1
= new GroupInfoV1(groupInfo
.getGroupId());
1460 if (groupInfo
.getAvatar().isPresent()) {
1461 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1462 if (avatar
.isPointer()) {
1464 retrieveGroupAvatarAttachment(avatar
.asPointer(), groupV1
.groupId
);
1465 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1466 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer()
1467 .getRemoteId() + "): " + e
.getMessage());
1472 if (groupInfo
.getName().isPresent()) {
1473 groupV1
.name
= groupInfo
.getName().get();
1476 if (groupInfo
.getMembers().isPresent()) {
1477 groupV1
.addMembers(groupInfo
.getMembers()
1480 .map(this::resolveSignalServiceAddress
)
1481 .collect(Collectors
.toSet()));
1484 account
.getGroupStore().updateGroup(groupV1
);
1488 if (groupV1
== null && !isSync
) {
1489 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1493 if (groupV1
!= null) {
1494 groupV1
.removeMember(source
);
1495 account
.getGroupStore().updateGroup(groupV1
);
1500 if (groupV1
!= null && !isSync
) {
1501 actions
.add(new SendGroupUpdateAction(source
, groupV1
.groupId
));
1506 // Received a group v1 message for a v2 group
1509 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1510 final SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1511 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1513 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
1515 byte[] groupId
= groupSecretParams
.getPublicParams().getGroupIdentifier().serialize();
1516 GroupInfo groupInfo
= account
.getGroupStore().getGroupByV2Id(groupId
);
1517 if (groupInfo
instanceof GroupInfoV1
) {
1518 // Received a v2 group message for a v2 group, we need to locally migrate the group
1519 account
.getGroupStore().deleteGroup(groupInfo
.groupId
);
1520 GroupInfoV2 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1521 groupInfoV2
.setGroup(getDecryptedGroup(groupSecretParams
));
1522 account
.getGroupStore().updateGroup(groupInfoV2
);
1523 System
.err
.println("Locally migrated group "
1524 + Base64
.encodeBytes(groupInfo
.groupId
)
1525 + " to group v2, id: "
1526 + Base64
.encodeBytes(groupInfoV2
.groupId
)
1528 } else if (groupInfo
== null || groupInfo
instanceof GroupInfoV2
) {
1529 GroupInfoV2 groupInfoV2
= groupInfo
== null
1530 ?
new GroupInfoV2(groupId
, groupMasterKey
)
1531 : (GroupInfoV2
) groupInfo
;
1533 if (groupInfoV2
.getGroup() == null
1534 || groupInfoV2
.getGroup().getRevision() < groupContext
.getRevision()) {
1535 DecryptedGroup group
= null;
1536 if (groupContext
.hasSignedGroupChange()
1537 && groupInfoV2
.getGroup() != null
1538 && groupInfoV2
.getGroup().getRevision() + 1 == groupContext
.getRevision()) {
1539 group
= groupHelper
.getUpdatedDecryptedGroup(groupInfoV2
.getGroup(),
1540 groupContext
.getSignedGroupChange(),
1543 if (group
== null) {
1544 group
= getDecryptedGroup(groupSecretParams
);
1546 groupInfoV2
.setGroup(group
);
1547 account
.getGroupStore().updateGroup(groupInfoV2
);
1552 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1553 if (message
.isEndSession()) {
1554 handleEndSession(conversationPartnerAddress
);
1556 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1557 if (message
.getGroupContext().isPresent()) {
1558 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1559 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1560 GroupInfoV1 group
= account
.getGroupStore().getOrCreateGroupV1(groupInfo
.getGroupId());
1561 if (group
!= null) {
1562 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1563 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1564 account
.getGroupStore().updateGroup(group
);
1567 } else if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1568 // disappearing message timer already stored in the DecryptedGroup
1571 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1572 if (contact
== null) {
1573 contact
= new ContactInfo(conversationPartnerAddress
);
1575 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1576 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1577 account
.getContactStore().updateContact(contact
);
1581 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1582 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1583 if (attachment
.isPointer()) {
1585 retrieveAttachment(attachment
.asPointer());
1586 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1587 System
.err
.println("Failed to retrieve attachment ("
1588 + attachment
.asPointer().getRemoteId()
1595 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1596 final ProfileKey profileKey
;
1598 profileKey
= new ProfileKey(message
.getProfileKey().get());
1599 } catch (InvalidInputException e
) {
1600 throw new AssertionError(e
);
1602 if (source
.matches(account
.getSelfAddress())) {
1603 this.account
.setProfileKey(profileKey
);
1605 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1607 if (message
.getPreviews().isPresent()) {
1608 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1609 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1610 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1611 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1613 retrieveAttachment(attachment
);
1614 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1615 System
.err
.println("Failed to retrieve attachment ("
1616 + attachment
.getRemoteId()
1623 if (message
.getSticker().isPresent()) {
1624 final SignalServiceDataMessage
.Sticker messageSticker
= message
.getSticker().get();
1625 Sticker sticker
= account
.getStickerStore().getSticker(messageSticker
.getPackId());
1626 if (sticker
== null) {
1627 sticker
= new Sticker(messageSticker
.getPackId(), messageSticker
.getPackKey());
1628 account
.getStickerStore().updateSticker(sticker
);
1634 private DecryptedGroup
getDecryptedGroup(final GroupSecretParams groupSecretParams
) {
1636 final GroupsV2AuthorizationString groupsV2AuthorizationString
= getGroupAuthForToday(groupSecretParams
);
1637 DecryptedGroup group
= groupsV2Api
.getGroup(groupSecretParams
, groupsV2AuthorizationString
);
1638 for (DecryptedMember member
: group
.getMembersList()) {
1639 final SignalServiceAddress address
= resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil
.parseOrThrow(
1640 member
.getUuid().toByteArray()), null));
1642 account
.getProfileStore()
1643 .storeProfileKey(address
, new ProfileKey(member
.getProfileKey().toByteArray()));
1644 } catch (InvalidInputException ignored
) {
1648 } catch (IOException
| VerificationFailedException
| InvalidGroupStateException e
) {
1649 System
.err
.println("Failed to retrieve Group V2 info, ignoring ...");
1654 private void retryFailedReceivedMessages(
1655 ReceiveMessageHandler handler
, boolean ignoreAttachments
1657 final File cachePath
= new File(getMessageCachePath());
1658 if (!cachePath
.exists()) {
1661 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1662 if (!dir
.isDirectory()) {
1663 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1667 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1668 if (!fileEntry
.isFile()) {
1671 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1673 // Try to delete directory if empty
1678 private void retryFailedReceivedMessage(
1679 final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
1681 SignalServiceEnvelope envelope
;
1683 envelope
= Utils
.loadEnvelope(fileEntry
);
1684 if (envelope
== null) {
1687 } catch (IOException e
) {
1688 e
.printStackTrace();
1691 SignalServiceContent content
= null;
1692 if (!envelope
.isReceipt()) {
1694 content
= decryptMessage(envelope
);
1695 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1697 } catch (Exception er
) {
1698 // All other errors are not recoverable, so delete the cached message
1700 Files
.delete(fileEntry
.toPath());
1701 } catch (IOException e
) {
1702 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1706 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1707 for (HandleAction action
: actions
) {
1709 action
.execute(this);
1710 } catch (Throwable e
) {
1711 e
.printStackTrace();
1716 handler
.handleMessage(envelope
, content
, null);
1718 Files
.delete(fileEntry
.toPath());
1719 } catch (IOException e
) {
1720 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1724 public void receiveMessages(
1727 boolean returnOnTimeout
,
1728 boolean ignoreAttachments
,
1729 ReceiveMessageHandler handler
1730 ) throws IOException
{
1731 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1733 Set
<HandleAction
> queuedActions
= null;
1735 getOrCreateMessagePipe();
1737 boolean hasCaughtUpWithOldMessages
= false;
1740 SignalServiceEnvelope envelope
;
1741 SignalServiceContent content
= null;
1742 Exception exception
= null;
1743 final long now
= new Date().getTime();
1745 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1746 // store message on disk, before acknowledging receipt to the server
1748 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1749 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1750 Utils
.storeEnvelope(envelope1
, cacheFile
);
1751 } catch (IOException e
) {
1752 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: "
1756 if (result
.isPresent()) {
1757 envelope
= result
.get();
1759 // Received indicator that server queue is empty
1760 hasCaughtUpWithOldMessages
= true;
1762 if (queuedActions
!= null) {
1763 for (HandleAction action
: queuedActions
) {
1765 action
.execute(this);
1766 } catch (Throwable e
) {
1767 e
.printStackTrace();
1771 queuedActions
.clear();
1772 queuedActions
= null;
1775 // Continue to wait another timeout for new messages
1778 } catch (TimeoutException e
) {
1779 if (returnOnTimeout
) return;
1781 } catch (InvalidVersionException e
) {
1782 System
.err
.println("Ignoring error: " + e
.getMessage());
1786 if (envelope
.hasSource()) {
1787 // Store uuid if we don't have it already
1788 SignalServiceAddress source
= envelope
.getSourceAddress();
1789 resolveSignalServiceAddress(source
);
1791 if (!envelope
.isReceipt()) {
1793 content
= decryptMessage(envelope
);
1794 } catch (Exception e
) {
1797 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1798 if (hasCaughtUpWithOldMessages
) {
1799 for (HandleAction action
: actions
) {
1801 action
.execute(this);
1802 } catch (Throwable e
) {
1803 e
.printStackTrace();
1807 if (queuedActions
== null) {
1808 queuedActions
= new HashSet
<>();
1810 queuedActions
.addAll(actions
);
1814 if (!isMessageBlocked(envelope
, content
)) {
1815 handler
.handleMessage(envelope
, content
, exception
);
1817 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1818 File cacheFile
= null;
1820 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1821 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1822 Files
.delete(cacheFile
.toPath());
1823 // Try to delete directory if empty
1824 new File(getMessageCachePath()).delete();
1825 } catch (IOException e
) {
1826 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1832 private boolean isMessageBlocked(
1833 SignalServiceEnvelope envelope
, SignalServiceContent content
1835 SignalServiceAddress source
;
1836 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1837 source
= envelope
.getSourceAddress();
1838 } else if (content
!= null) {
1839 source
= content
.getSender();
1843 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1844 if (sourceContact
!= null && sourceContact
.blocked
) {
1848 if (content
!= null && content
.getDataMessage().isPresent()) {
1849 SignalServiceDataMessage message
= content
.getDataMessage().get();
1850 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1851 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1852 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1853 return groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.isBlocked();
1859 private List
<HandleAction
> handleMessage(
1860 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
1862 List
<HandleAction
> actions
= new ArrayList
<>();
1863 if (content
!= null) {
1864 SignalServiceAddress sender
;
1865 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1866 sender
= envelope
.getSourceAddress();
1868 sender
= content
.getSender();
1870 // Store uuid if we don't have it already
1871 resolveSignalServiceAddress(sender
);
1873 if (content
.getDataMessage().isPresent()) {
1874 SignalServiceDataMessage message
= content
.getDataMessage().get();
1876 if (content
.isNeedsReceipt()) {
1877 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1880 actions
.addAll(handleSignalServiceDataMessage(message
,
1883 account
.getSelfAddress(),
1884 ignoreAttachments
));
1886 if (content
.getSyncMessage().isPresent()) {
1887 account
.setMultiDevice(true);
1888 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1889 if (syncMessage
.getSent().isPresent()) {
1890 SentTranscriptMessage message
= syncMessage
.getSent().get();
1891 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
1894 message
.getDestination().orNull(),
1895 ignoreAttachments
));
1897 if (syncMessage
.getRequest().isPresent()) {
1898 RequestMessage rm
= syncMessage
.getRequest().get();
1899 if (rm
.isContactsRequest()) {
1900 actions
.add(SendSyncContactsAction
.create());
1902 if (rm
.isGroupsRequest()) {
1903 actions
.add(SendSyncGroupsAction
.create());
1905 if (rm
.isBlockedListRequest()) {
1906 actions
.add(SendSyncBlockedListAction
.create());
1908 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
1910 if (syncMessage
.getGroups().isPresent()) {
1911 File tmpFile
= null;
1913 tmpFile
= IOUtils
.createTempFile();
1914 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups()
1916 .asPointer(), tmpFile
)) {
1917 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1919 while ((g
= s
.read()) != null) {
1920 GroupInfoV1 syncGroup
= account
.getGroupStore().getOrCreateGroupV1(g
.getId());
1921 if (syncGroup
!= null) {
1922 if (g
.getName().isPresent()) {
1923 syncGroup
.name
= g
.getName().get();
1925 syncGroup
.addMembers(g
.getMembers()
1927 .map(this::resolveSignalServiceAddress
)
1928 .collect(Collectors
.toSet()));
1929 if (!g
.isActive()) {
1930 syncGroup
.removeMember(account
.getSelfAddress());
1932 // Add ourself to the member set as it's marked as active
1933 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1935 syncGroup
.blocked
= g
.isBlocked();
1936 if (g
.getColor().isPresent()) {
1937 syncGroup
.color
= g
.getColor().get();
1940 if (g
.getAvatar().isPresent()) {
1941 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1943 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1944 syncGroup
.archived
= g
.isArchived();
1945 account
.getGroupStore().updateGroup(syncGroup
);
1949 } catch (Exception e
) {
1950 e
.printStackTrace();
1952 if (tmpFile
!= null) {
1954 Files
.delete(tmpFile
.toPath());
1955 } catch (IOException e
) {
1956 System
.err
.println("Failed to delete received groups temp file “"
1964 if (syncMessage
.getBlockedList().isPresent()) {
1965 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1966 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1967 setContactBlocked(resolveSignalServiceAddress(address
), true);
1969 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1971 setGroupBlocked(groupId
, true);
1972 } catch (GroupNotFoundException e
) {
1973 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: "
1974 + Base64
.encodeBytes(groupId
));
1978 if (syncMessage
.getContacts().isPresent()) {
1979 File tmpFile
= null;
1981 tmpFile
= IOUtils
.createTempFile();
1982 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1983 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream()
1984 .asPointer(), tmpFile
)) {
1985 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1986 if (contactsMessage
.isComplete()) {
1987 account
.getContactStore().clear();
1990 while ((c
= s
.read()) != null) {
1991 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1992 account
.setProfileKey(c
.getProfileKey().get());
1994 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1995 ContactInfo contact
= account
.getContactStore().getContact(address
);
1996 if (contact
== null) {
1997 contact
= new ContactInfo(address
);
1999 if (c
.getName().isPresent()) {
2000 contact
.name
= c
.getName().get();
2002 if (c
.getColor().isPresent()) {
2003 contact
.color
= c
.getColor().get();
2005 if (c
.getProfileKey().isPresent()) {
2006 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
2008 if (c
.getVerified().isPresent()) {
2009 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
2010 account
.getSignalProtocolStore()
2011 .setIdentityTrustLevel(verifiedMessage
.getDestination(),
2012 verifiedMessage
.getIdentityKey(),
2013 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2015 if (c
.getExpirationTimer().isPresent()) {
2016 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
2018 contact
.blocked
= c
.isBlocked();
2019 contact
.inboxPosition
= c
.getInboxPosition().orNull();
2020 contact
.archived
= c
.isArchived();
2021 account
.getContactStore().updateContact(contact
);
2023 if (c
.getAvatar().isPresent()) {
2024 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
2028 } catch (Exception e
) {
2029 e
.printStackTrace();
2031 if (tmpFile
!= null) {
2033 Files
.delete(tmpFile
.toPath());
2034 } catch (IOException e
) {
2035 System
.err
.println("Failed to delete received contacts temp file “"
2043 if (syncMessage
.getVerified().isPresent()) {
2044 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
2045 account
.getSignalProtocolStore()
2046 .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()),
2047 verifiedMessage
.getIdentityKey(),
2048 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2050 if (syncMessage
.getStickerPackOperations().isPresent()) {
2051 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations()
2053 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
2054 if (!m
.getPackId().isPresent()) {
2057 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
2058 if (sticker
== null) {
2059 if (!m
.getPackKey().isPresent()) {
2062 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
2064 sticker
.setInstalled(!m
.getType().isPresent()
2065 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
2066 account
.getStickerStore().updateSticker(sticker
);
2069 if (syncMessage
.getConfiguration().isPresent()) {
2077 private File
getContactAvatarFile(String number
) {
2078 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
2081 private File
retrieveContactAvatarAttachment(
2082 SignalServiceAttachment attachment
, String number
2083 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2084 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2085 if (attachment
.isPointer()) {
2086 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2087 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
2089 SignalServiceAttachmentStream stream
= attachment
.asStream();
2090 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
2094 private File
getGroupAvatarFile(byte[] groupId
) {
2095 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
2098 private File
retrieveGroupAvatarAttachment(
2099 SignalServiceAttachment attachment
, byte[] groupId
2100 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2101 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2102 if (attachment
.isPointer()) {
2103 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2104 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
2106 SignalServiceAttachmentStream stream
= attachment
.asStream();
2107 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
2111 private File
getProfileAvatarFile(SignalServiceAddress address
) {
2112 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
2115 private File
retrieveProfileAvatar(
2116 SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
2117 ) throws IOException
{
2118 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2119 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2120 File outputFile
= getProfileAvatarFile(address
);
2122 File tmpFile
= IOUtils
.createTempFile();
2123 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
,
2126 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2127 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2128 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
2131 Files
.delete(tmpFile
.toPath());
2132 } catch (IOException e
) {
2133 System
.err
.println("Failed to delete received avatar temp file “" + tmpFile
+ "”: " + e
.getMessage());
2139 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
2140 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
2143 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2144 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
2145 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
2148 private File
retrieveAttachment(
2149 SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
2150 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2151 if (storePreview
&& pointer
.getPreview().isPresent()) {
2152 File previewFile
= new File(outputFile
+ ".preview");
2153 try (OutputStream output
= new FileOutputStream(previewFile
)) {
2154 byte[] preview
= pointer
.getPreview().get();
2155 output
.write(preview
, 0, preview
.length
);
2156 } catch (FileNotFoundException e
) {
2157 e
.printStackTrace();
2162 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2164 File tmpFile
= IOUtils
.createTempFile();
2165 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
,
2167 ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
2168 IOUtils
.copyStreamToFile(input
, outputFile
);
2171 Files
.delete(tmpFile
.toPath());
2172 } catch (IOException e
) {
2173 System
.err
.println("Failed to delete received attachment temp file “"
2182 private InputStream
retrieveAttachmentAsStream(
2183 SignalServiceAttachmentPointer pointer
, File tmpFile
2184 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2185 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2186 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
2189 void sendGroups() throws IOException
, UntrustedIdentityException
{
2190 File groupsFile
= IOUtils
.createTempFile();
2193 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
2194 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
2195 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2196 if (record instanceof GroupInfoV1
) {
2197 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
2198 out
.write(new DeviceGroup(groupInfo
.groupId
,
2199 Optional
.fromNullable(groupInfo
.name
),
2200 new ArrayList
<>(groupInfo
.getMembers()),
2201 createGroupAvatarAttachment(groupInfo
.groupId
),
2202 groupInfo
.isMember(account
.getSelfAddress()),
2203 Optional
.of(groupInfo
.messageExpirationTime
),
2204 Optional
.fromNullable(groupInfo
.color
),
2206 Optional
.fromNullable(groupInfo
.inboxPosition
),
2207 groupInfo
.archived
));
2212 if (groupsFile
.exists() && groupsFile
.length() > 0) {
2213 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
2214 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2215 .withStream(groupsFileStream
)
2216 .withContentType("application/octet-stream")
2217 .withLength(groupsFile
.length())
2220 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
2225 Files
.delete(groupsFile
.toPath());
2226 } catch (IOException e
) {
2227 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
2232 public void sendContacts() throws IOException
, UntrustedIdentityException
{
2233 File contactsFile
= IOUtils
.createTempFile();
2236 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
2237 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
2238 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2239 VerifiedMessage verifiedMessage
= null;
2240 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore()
2241 .getIdentity(record.getAddress());
2242 if (currentIdentity
!= null) {
2243 verifiedMessage
= new VerifiedMessage(record.getAddress(),
2244 currentIdentity
.getIdentityKey(),
2245 currentIdentity
.getTrustLevel().toVerifiedState(),
2246 currentIdentity
.getDateAdded().getTime());
2249 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
2250 out
.write(new DeviceContact(record.getAddress(),
2251 Optional
.fromNullable(record.name
),
2252 createContactAvatarAttachment(record.number
),
2253 Optional
.fromNullable(record.color
),
2254 Optional
.fromNullable(verifiedMessage
),
2255 Optional
.fromNullable(profileKey
),
2257 Optional
.of(record.messageExpirationTime
),
2258 Optional
.fromNullable(record.inboxPosition
),
2262 if (account
.getProfileKey() != null) {
2263 // Send our own profile key as well
2264 out
.write(new DeviceContact(account
.getSelfAddress(),
2269 Optional
.of(account
.getProfileKey()),
2277 if (contactsFile
.exists() && contactsFile
.length() > 0) {
2278 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
2279 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2280 .withStream(contactsFileStream
)
2281 .withContentType("application/octet-stream")
2282 .withLength(contactsFile
.length())
2285 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
2290 Files
.delete(contactsFile
.toPath());
2291 } catch (IOException e
) {
2292 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
2297 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2298 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2299 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2300 if (record.blocked
) {
2301 addresses
.add(record.getAddress());
2304 List
<byte[]> groupIds
= new ArrayList
<>();
2305 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2306 if (record.isBlocked()) {
2307 groupIds
.add(record.groupId
);
2310 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2313 private void sendVerifiedMessage(
2314 SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
2315 ) throws IOException
, UntrustedIdentityException
{
2316 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
,
2318 trustLevel
.toVerifiedState(),
2319 System
.currentTimeMillis());
2320 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2323 public List
<ContactInfo
> getContacts() {
2324 return account
.getContactStore().getContacts();
2327 public ContactInfo
getContact(String number
) {
2328 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
2331 public GroupInfo
getGroup(byte[] groupId
) {
2332 return account
.getGroupStore().getGroup(groupId
);
2335 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
2336 return account
.getSignalProtocolStore().getIdentities();
2339 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
2340 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2344 * Trust this the identity with this fingerprint
2346 * @param name username of the identity
2347 * @param fingerprint Fingerprint
2349 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2350 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2351 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2355 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2356 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2360 account
.getSignalProtocolStore()
2361 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2363 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2364 } catch (IOException
| UntrustedIdentityException e
) {
2365 e
.printStackTrace();
2374 * Trust this the identity with this safety number
2376 * @param name username of the identity
2377 * @param safetyNumber Safety number
2379 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2380 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2381 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2385 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2386 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2390 account
.getSignalProtocolStore()
2391 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2393 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2394 } catch (IOException
| UntrustedIdentityException e
) {
2395 e
.printStackTrace();
2404 * Trust all keys of this identity without verification
2406 * @param name username of the identity
2408 public boolean trustIdentityAllKeys(String name
) {
2409 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2410 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2414 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2415 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2416 account
.getSignalProtocolStore()
2417 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2419 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2420 } catch (IOException
| UntrustedIdentityException e
) {
2421 e
.printStackTrace();
2429 public String
computeSafetyNumber(
2430 SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
2432 return Utils
.computeSafetyNumber(account
.getSelfAddress(),
2433 getIdentityKeyPair().getPublicKey(),
2438 void saveAccount() {
2442 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2443 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
)
2445 : Util
.canonicalizeNumber(identifier
, account
.getUsername());
2446 return resolveSignalServiceAddress(canonicalizedNumber
);
2449 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2450 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2452 return resolveSignalServiceAddress(address
);
2455 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2456 if (address
.matches(account
.getSelfAddress())) {
2457 return account
.getSelfAddress();
2460 return account
.getRecipientStore().resolveServiceAddress(address
);
2464 public void close() throws IOException
{
2465 if (messagePipe
!= null) {
2466 messagePipe
.shutdown();
2470 if (unidentifiedMessagePipe
!= null) {
2471 unidentifiedMessagePipe
.shutdown();
2472 unidentifiedMessagePipe
= null;
2478 public interface ReceiveMessageHandler
{
2480 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);