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
.local
.DecryptedGroup
;
47 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedMember
;
48 import org
.signal
.zkgroup
.InvalidInputException
;
49 import org
.signal
.zkgroup
.VerificationFailedException
;
50 import org
.signal
.zkgroup
.auth
.AuthCredentialResponse
;
51 import org
.signal
.zkgroup
.groups
.GroupMasterKey
;
52 import org
.signal
.zkgroup
.groups
.GroupSecretParams
;
53 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
54 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
55 import org
.signal
.zkgroup
.profiles
.ProfileKeyCredential
;
56 import org
.whispersystems
.libsignal
.IdentityKey
;
57 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
58 import org
.whispersystems
.libsignal
.InvalidKeyException
;
59 import org
.whispersystems
.libsignal
.InvalidMessageException
;
60 import org
.whispersystems
.libsignal
.InvalidVersionException
;
61 import org
.whispersystems
.libsignal
.ecc
.Curve
;
62 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
63 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
64 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
65 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
66 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
67 import org
.whispersystems
.libsignal
.util
.Medium
;
68 import org
.whispersystems
.libsignal
.util
.Pair
;
69 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
70 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
71 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
72 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
73 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
74 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
75 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
76 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
77 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
78 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
79 import org
.whispersystems
.signalservice
.api
.groupsv2
.ClientZkOperations
;
80 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Api
;
81 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2AuthorizationString
;
82 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
83 import org
.whispersystems
.signalservice
.api
.groupsv2
.InvalidGroupStateException
;
84 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
85 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
86 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
87 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
88 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
89 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
90 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
91 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
92 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
93 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroupV2
;
94 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
95 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
96 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
97 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
98 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
99 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
100 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
101 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
102 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
103 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
104 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
105 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
106 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
107 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
108 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
109 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.StickerPackOperationMessage
;
110 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
111 import org
.whispersystems
.signalservice
.api
.profiles
.ProfileAndCredential
;
112 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
113 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
114 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
115 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
116 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
117 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
118 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
119 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
120 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
121 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
122 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
123 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
124 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
125 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
126 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
127 import org
.whispersystems
.util
.Base64
;
129 import java
.io
.Closeable
;
131 import java
.io
.FileInputStream
;
132 import java
.io
.FileNotFoundException
;
133 import java
.io
.FileOutputStream
;
134 import java
.io
.IOException
;
135 import java
.io
.InputStream
;
136 import java
.io
.OutputStream
;
138 import java
.net
.URISyntaxException
;
139 import java
.net
.URLEncoder
;
140 import java
.nio
.charset
.StandardCharsets
;
141 import java
.nio
.file
.Files
;
142 import java
.nio
.file
.Paths
;
143 import java
.nio
.file
.StandardCopyOption
;
144 import java
.util
.ArrayList
;
145 import java
.util
.Arrays
;
146 import java
.util
.Collection
;
147 import java
.util
.Collections
;
148 import java
.util
.Date
;
149 import java
.util
.HashMap
;
150 import java
.util
.HashSet
;
151 import java
.util
.List
;
152 import java
.util
.Locale
;
153 import java
.util
.Objects
;
154 import java
.util
.Set
;
155 import java
.util
.UUID
;
156 import java
.util
.concurrent
.ExecutorService
;
157 import java
.util
.concurrent
.TimeUnit
;
158 import java
.util
.concurrent
.TimeoutException
;
159 import java
.util
.stream
.Collectors
;
160 import java
.util
.zip
.ZipEntry
;
161 import java
.util
.zip
.ZipFile
;
163 import static org
.asamk
.signal
.manager
.ServiceConfig
.capabilities
;
165 public class Manager
implements Closeable
{
167 private final SleepTimer timer
= new UptimeSleepTimer();
169 private final SignalServiceConfiguration serviceConfiguration
;
170 private final String userAgent
;
171 private final boolean discoverableByPhoneNumber
= true;
172 private final boolean unrestrictedUnidentifiedAccess
= false;
174 private final SignalAccount account
;
175 private final PathConfig pathConfig
;
176 private SignalServiceAccountManager accountManager
;
177 private GroupsV2Api groupsV2Api
;
178 private final GroupsV2Operations groupsV2Operations
;
180 private SignalServiceMessageReceiver messageReceiver
= null;
181 private SignalServiceMessagePipe messagePipe
= null;
182 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
184 private final UnidentifiedAccessHelper unidentifiedAccessHelper
;
185 private final ProfileHelper profileHelper
;
186 private final GroupHelper groupHelper
;
189 SignalAccount account
,
190 PathConfig pathConfig
,
191 SignalServiceConfiguration serviceConfiguration
,
194 this.account
= account
;
195 this.pathConfig
= pathConfig
;
196 this.serviceConfiguration
= serviceConfiguration
;
197 this.userAgent
= userAgent
;
198 this.groupsV2Operations
= capabilities
.isGv2() ?
new GroupsV2Operations(ClientZkOperations
.create(
199 serviceConfiguration
)) : null;
200 this.accountManager
= createSignalServiceAccountManager();
201 this.groupsV2Api
= accountManager
.getGroupsV2Api();
203 this.account
.setResolver(this::resolveSignalServiceAddress
);
205 this.unidentifiedAccessHelper
= new UnidentifiedAccessHelper(account
::getProfileKey
,
206 account
.getProfileStore()::getProfileKey
,
207 this::getRecipientProfile
,
208 this::getSenderCertificate
);
209 this.profileHelper
= new ProfileHelper(account
.getProfileStore()::getProfileKey
,
210 unidentifiedAccessHelper
::getAccessFor
,
211 unidentified
-> unidentified ?
getOrCreateUnidentifiedMessagePipe() : getOrCreateMessagePipe(),
212 this::getOrCreateMessageReceiver
);
213 this.groupHelper
= new GroupHelper(this::getRecipientProfileKeyCredential
,
214 this::getRecipientProfile
,
215 account
::getSelfAddress
,
219 public String
getUsername() {
220 return account
.getUsername();
223 public SignalServiceAddress
getSelfAddress() {
224 return account
.getSelfAddress();
227 private SignalServiceAccountManager
createSignalServiceAccountManager() {
228 return new SignalServiceAccountManager(serviceConfiguration
,
229 new DynamicCredentialsProvider(account
.getUuid(),
230 account
.getUsername(),
231 account
.getPassword(),
233 account
.getDeviceId()),
239 private IdentityKeyPair
getIdentityKeyPair() {
240 return account
.getSignalProtocolStore().getIdentityKeyPair();
243 public int getDeviceId() {
244 return account
.getDeviceId();
247 private String
getMessageCachePath() {
248 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
251 private String
getMessageCachePath(String sender
) {
252 if (sender
== null || sender
.isEmpty()) {
253 return getMessageCachePath();
256 return getMessageCachePath() + "/" + sender
.replace("/", "_");
259 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
260 String cachePath
= getMessageCachePath(sender
);
261 IOUtils
.createPrivateDirectories(cachePath
);
262 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
265 public static Manager
init(
266 String username
, String settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
267 ) throws IOException
{
268 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
270 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
271 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
272 int registrationId
= KeyHelper
.generateRegistrationId(false);
274 ProfileKey profileKey
= KeyUtils
.createProfileKey();
275 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(),
282 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
285 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
287 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
289 m
.migrateLegacyConfigs();
294 private void migrateLegacyConfigs() {
295 if (account
.getProfileKey() == null && isRegistered()) {
296 // Old config file, creating new profile key
297 account
.setProfileKey(KeyUtils
.createProfileKey());
300 // Store profile keys only in profile store
301 for (ContactInfo contact
: account
.getContactStore().getContacts()) {
302 String profileKeyString
= contact
.profileKey
;
303 if (profileKeyString
== null) {
306 final ProfileKey profileKey
;
308 profileKey
= new ProfileKey(Base64
.decode(profileKeyString
));
309 } catch (InvalidInputException
| IOException e
) {
312 contact
.profileKey
= null;
313 account
.getProfileStore().storeProfileKey(contact
.getAddress(), profileKey
);
317 public void checkAccountState() throws IOException
{
318 if (account
.isRegistered()) {
319 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
323 if (account
.getUuid() == null) {
324 account
.setUuid(accountManager
.getOwnUuid());
327 updateAccountAttributes();
331 public boolean isRegistered() {
332 return account
.isRegistered();
335 public void register(boolean voiceVerification
, String captcha
) throws IOException
{
336 account
.setPassword(KeyUtils
.createPassword());
338 // Resetting UUID, because registering doesn't work otherwise
339 account
.setUuid(null);
340 accountManager
= createSignalServiceAccountManager();
341 this.groupsV2Api
= accountManager
.getGroupsV2Api();
343 if (voiceVerification
) {
344 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(),
345 Optional
.fromNullable(captcha
),
348 accountManager
.requestSmsVerificationCode(false, Optional
.fromNullable(captcha
), Optional
.absent());
351 account
.setRegistered(false);
355 public void updateAccountAttributes() throws IOException
{
356 accountManager
.setAccountAttributes(account
.getSignalingKey(),
357 account
.getSignalProtocolStore().getLocalRegistrationId(),
359 account
.getRegistrationLockPin(),
360 account
.getRegistrationLock(),
361 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
362 unrestrictedUnidentifiedAccess
,
364 discoverableByPhoneNumber
);
367 public void setProfile(String name
, File avatar
) throws IOException
{
368 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
369 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
373 public void unregister() throws IOException
{
374 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
375 // If this is the master device, other users can't send messages to this number anymore.
376 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
377 accountManager
.setGcmId(Optional
.absent());
379 account
.setRegistered(false);
383 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
384 List
<DeviceInfo
> devices
= accountManager
.getDevices();
385 account
.setMultiDevice(devices
.size() > 1);
390 public void removeLinkedDevices(int deviceId
) throws IOException
{
391 accountManager
.removeDevice(deviceId
);
392 List
<DeviceInfo
> devices
= accountManager
.getDevices();
393 account
.setMultiDevice(devices
.size() > 1);
397 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
398 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
400 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
403 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
404 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
405 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
407 accountManager
.addDevice(deviceIdentifier
,
410 Optional
.of(account
.getProfileKey().serialize()),
412 account
.setMultiDevice(true);
416 private List
<PreKeyRecord
> generatePreKeys() {
417 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
419 final int offset
= account
.getPreKeyIdOffset();
420 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
421 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
422 ECKeyPair keyPair
= Curve
.generateKeyPair();
423 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
428 account
.addPreKeys(records
);
434 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
436 ECKeyPair keyPair
= Curve
.generateKeyPair();
437 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(),
438 keyPair
.getPublicKey().serialize());
439 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(),
440 System
.currentTimeMillis(),
444 account
.addSignedPreKey(record);
448 } catch (InvalidKeyException e
) {
449 throw new AssertionError(e
);
453 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
454 verificationCode
= verificationCode
.replace("-", "");
455 account
.setSignalingKey(KeyUtils
.createSignalingKey());
456 // TODO make unrestricted unidentified access configurable
457 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
,
458 account
.getSignalingKey(),
459 account
.getSignalProtocolStore().getLocalRegistrationId(),
463 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
464 unrestrictedUnidentifiedAccess
,
466 discoverableByPhoneNumber
);
468 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
469 // TODO response.isStorageCapable()
470 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
471 account
.setRegistered(true);
472 account
.setUuid(uuid
);
473 account
.setRegistrationLockPin(pin
);
474 account
.getSignalProtocolStore()
475 .saveIdentity(account
.getSelfAddress(),
476 getIdentityKeyPair().getPublicKey(),
477 TrustLevel
.TRUSTED_VERIFIED
);
483 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
484 if (pin
.isPresent()) {
485 account
.setRegistrationLockPin(pin
.get());
486 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
488 account
.setRegistrationLockPin(null);
489 accountManager
.removeRegistrationLockV1();
494 void refreshPreKeys() throws IOException
{
495 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
496 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
497 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
499 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
502 private SignalServiceMessageReceiver
createMessageReceiver() {
503 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
504 serviceConfiguration
).getProfileOperations() : null;
505 return new SignalServiceMessageReceiver(serviceConfiguration
,
507 account
.getUsername(),
508 account
.getPassword(),
509 account
.getDeviceId(),
510 account
.getSignalingKey(),
514 clientZkProfileOperations
);
517 private SignalServiceMessageReceiver
getOrCreateMessageReceiver() {
518 if (messageReceiver
== null) {
519 messageReceiver
= createMessageReceiver();
521 return messageReceiver
;
524 private SignalServiceMessagePipe
getOrCreateMessagePipe() {
525 if (messagePipe
== null) {
526 messagePipe
= getOrCreateMessageReceiver().createMessagePipe();
531 private SignalServiceMessagePipe
getOrCreateUnidentifiedMessagePipe() {
532 if (unidentifiedMessagePipe
== null) {
533 unidentifiedMessagePipe
= getOrCreateMessageReceiver().createUnidentifiedMessagePipe();
535 return unidentifiedMessagePipe
;
538 private SignalServiceMessageSender
createMessageSender() {
539 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
540 serviceConfiguration
).getProfileOperations() : null;
541 final ExecutorService executor
= null;
542 return new SignalServiceMessageSender(serviceConfiguration
,
544 account
.getUsername(),
545 account
.getPassword(),
546 account
.getDeviceId(),
547 account
.getSignalProtocolStore(),
549 account
.isMultiDevice(),
550 Optional
.fromNullable(messagePipe
),
551 Optional
.fromNullable(unidentifiedMessagePipe
),
553 clientZkProfileOperations
,
555 ServiceConfig
.MAX_ENVELOPE_SIZE
);
558 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
) throws IOException
{
559 return profileHelper
.retrieveProfileSync(address
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
562 private SignalProfile
getRecipientProfile(
563 SignalServiceAddress address
565 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
566 if (profileEntry
== null) {
569 long now
= new Date().getTime();
570 // Profiles are cache for 24h before retrieving them again
571 if (!profileEntry
.isRequestPending() && (
572 profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000
574 ProfileKey profileKey
= profileEntry
.getProfileKey();
575 profileEntry
.setRequestPending(true);
576 SignalProfile profile
;
578 profile
= retrieveRecipientProfile(address
, profileKey
);
579 } catch (IOException e
) {
580 System
.err
.println("Failed to retrieve profile, ignoring: " + e
.getMessage());
581 profileEntry
.setRequestPending(false);
584 profileEntry
.setRequestPending(false);
585 account
.getProfileStore()
586 .updateProfile(address
, profileKey
, now
, profile
, profileEntry
.getProfileKeyCredential());
589 return profileEntry
.getProfile();
592 private ProfileKeyCredential
getRecipientProfileKeyCredential(SignalServiceAddress address
) {
593 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
594 if (profileEntry
== null) {
597 if (profileEntry
.getProfileKeyCredential() == null) {
598 ProfileAndCredential profileAndCredential
;
600 profileAndCredential
= profileHelper
.retrieveProfileSync(address
,
601 SignalServiceProfile
.RequestType
.PROFILE_AND_CREDENTIAL
);
602 } catch (IOException e
) {
603 System
.err
.println("Failed to retrieve profile key credential, ignoring: " + e
.getMessage());
607 long now
= new Date().getTime();
608 final ProfileKeyCredential profileKeyCredential
= profileAndCredential
.getProfileKeyCredential().orNull();
609 final SignalProfile profile
= decryptProfile(address
,
610 profileEntry
.getProfileKey(),
611 profileAndCredential
.getProfile());
612 account
.getProfileStore()
613 .updateProfile(address
, profileEntry
.getProfileKey(), now
, profile
, profileKeyCredential
);
614 return profileKeyCredential
;
616 return profileEntry
.getProfileKeyCredential();
619 private SignalProfile
retrieveRecipientProfile(
620 SignalServiceAddress address
, ProfileKey profileKey
621 ) throws IOException
{
622 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
);
624 return decryptProfile(address
, profileKey
, encryptedProfile
);
627 private SignalProfile
decryptProfile(
628 final SignalServiceAddress address
, final ProfileKey profileKey
, final SignalServiceProfile encryptedProfile
630 File avatarFile
= null;
632 avatarFile
= encryptedProfile
.getAvatar() == null
634 : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
635 } catch (Throwable e
) {
636 System
.err
.println("Failed to retrieve profile avatar, ignoring: " + e
.getMessage());
639 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
643 name
= encryptedProfile
.getName() == null
645 : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName())));
646 } catch (IOException e
) {
649 String unidentifiedAccess
;
651 unidentifiedAccess
= encryptedProfile
.getUnidentifiedAccess() == null
652 || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess()))
654 : encryptedProfile
.getUnidentifiedAccess();
655 } catch (IOException e
) {
656 unidentifiedAccess
= null;
658 return new SignalProfile(encryptedProfile
.getIdentityKey(),
662 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
663 encryptedProfile
.getCapabilities());
664 } catch (InvalidCiphertextException e
) {
669 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
670 File file
= getGroupAvatarFile(groupId
);
671 if (!file
.exists()) {
672 return Optional
.absent();
675 return Optional
.of(Utils
.createAttachment(file
));
678 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
679 File file
= getContactAvatarFile(number
);
680 if (!file
.exists()) {
681 return Optional
.absent();
684 return Optional
.of(Utils
.createAttachment(file
));
687 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
688 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
690 throw new GroupNotFoundException(groupId
);
692 if (!g
.isMember(account
.getSelfAddress())) {
693 throw new NotAGroupMemberException(groupId
, g
.getTitle());
698 public List
<GroupInfo
> getGroups() {
699 return account
.getGroupStore().getGroups();
702 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
703 SignalServiceDataMessage
.Builder messageBuilder
, byte[] groupId
704 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
{
705 final GroupInfo g
= getGroupForSending(groupId
);
707 GroupUtils
.setGroupContext(messageBuilder
, g
);
708 messageBuilder
.withExpiration(g
.getMessageExpirationTime());
710 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
713 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
714 String messageText
, List
<String
> attachments
, byte[] groupId
715 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
716 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
717 .withBody(messageText
);
718 if (attachments
!= null) {
719 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
722 return sendGroupMessage(messageBuilder
, groupId
);
725 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessageReaction(
726 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, byte[] groupId
727 ) throws IOException
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
728 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
730 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
731 targetSentTimestamp
);
732 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
733 .withReaction(reaction
);
735 return sendGroupMessage(messageBuilder
, groupId
);
738 public Pair
<Long
, List
<SendMessageResult
>> sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, NotAGroupMemberException
{
739 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
).withId(groupId
).build();
741 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().asGroupMessage(group
);
743 final GroupInfo g
= getGroupForSending(groupId
);
744 if (g
instanceof GroupInfoV1
) {
745 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
746 groupInfoV1
.removeMember(account
.getSelfAddress());
747 account
.getGroupStore().updateGroup(groupInfoV1
);
749 throw new RuntimeException("TODO Not implemented!");
752 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
755 private GroupInfoV2
createGroupV2(
756 String name
, Collection
<SignalServiceAddress
> members
, InputStream avatar
757 ) throws IOException
{
758 byte[] avatarBytes
= avatar
== null ?
null : IOUtils
.readFully(avatar
);
759 final GroupsV2Operations
.NewGroup newGroup
= groupHelper
.createGroupV2(name
, members
, avatarBytes
);
760 final GroupSecretParams groupSecretParams
= newGroup
.getGroupSecretParams();
762 final GroupsV2AuthorizationString groupAuthForToday
;
763 final DecryptedGroup decryptedGroup
;
765 groupAuthForToday
= getGroupAuthForToday(groupSecretParams
);
766 groupsV2Api
.putNewGroup(newGroup
, groupAuthForToday
);
767 decryptedGroup
= groupsV2Api
.getGroup(groupSecretParams
, groupAuthForToday
);
768 } catch (IOException
| VerificationFailedException
| InvalidGroupStateException e
) {
769 System
.err
.println("Failed to create V2 group: " + e
.getMessage());
772 if (decryptedGroup
== null) {
773 System
.err
.println("Failed to create V2 group!");
777 final byte[] groupId
= groupSecretParams
.getPublicParams().getGroupIdentifier().serialize();
778 final GroupMasterKey masterKey
= groupSecretParams
.getMasterKey();
779 GroupInfoV2 g
= new GroupInfoV2(groupId
, masterKey
);
780 g
.setGroup(decryptedGroup
);
785 private Pair
<byte[], List
<SendMessageResult
>> sendUpdateGroupMessage(
786 byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
787 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
789 SignalServiceDataMessage
.Builder messageBuilder
;
790 if (groupId
== null) {
792 InputStream avatar
= avatarFile
== null ?
null : new FileInputStream(avatarFile
);
793 GroupInfoV2 gv2
= createGroupV2(name
, members
, avatar
);
795 GroupInfoV1 gv1
= new GroupInfoV1(KeyUtils
.createGroupId());
796 gv1
.addMembers(Collections
.singleton(account
.getSelfAddress()));
797 updateGroupV1(gv1
, name
, members
, avatarFile
);
798 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
801 messageBuilder
= getGroupUpdateMessageBuilder(gv2
);
805 GroupInfo group
= getGroupForSending(groupId
);
806 if (!(group
instanceof GroupInfoV1
)) {
807 throw new RuntimeException("TODO Not implemented!");
809 GroupInfoV1 gv1
= (GroupInfoV1
) group
;
810 updateGroupV1(gv1
, name
, members
, avatarFile
);
811 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
815 account
.getGroupStore().updateGroup(g
);
817 final Pair
<Long
, List
<SendMessageResult
>> result
= sendMessage(messageBuilder
,
818 g
.getMembersWithout(account
.getSelfAddress()));
819 return new Pair
<>(g
.groupId
, result
.second());
822 private void updateGroupV1(
825 final Collection
<SignalServiceAddress
> members
,
826 final String avatarFile
827 ) throws IOException
{
832 if (members
!= null) {
833 final Set
<String
> newE164Members
= new HashSet
<>();
834 for (SignalServiceAddress member
: members
) {
835 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
838 newE164Members
.add(member
.getNumber().get());
841 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
842 if (contacts
.size() != newE164Members
.size()) {
843 // Some of the new members are not registered on Signal
844 for (ContactTokenDetails contact
: contacts
) {
845 newE164Members
.remove(contact
.getNumber());
847 throw new IOException("Failed to add members "
848 + Util
.join(", ", newE164Members
)
849 + " to group: Not registered on Signal");
852 g
.addMembers(members
);
855 if (avatarFile
!= null) {
856 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
857 File aFile
= getGroupAvatarFile(g
.groupId
);
858 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
862 Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
863 byte[] groupId
, SignalServiceAddress recipient
864 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
866 GroupInfo group
= getGroupForSending(groupId
);
867 if (!(group
instanceof GroupInfoV1
)) {
868 throw new RuntimeException("Received an invalid group request for a v2 group!");
870 g
= (GroupInfoV1
) group
;
872 if (!g
.isMember(recipient
)) {
873 throw new NotAGroupMemberException(groupId
, g
.name
);
876 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
878 // Send group message only to the recipient who requested it
879 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
882 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV1 g
) throws AttachmentInvalidException
{
883 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
886 .withMembers(new ArrayList
<>(g
.getMembers()));
888 File aFile
= getGroupAvatarFile(g
.groupId
);
889 if (aFile
.exists()) {
891 group
.withAvatar(Utils
.createAttachment(aFile
));
892 } catch (IOException e
) {
893 throw new AttachmentInvalidException(aFile
.toString(), e
);
897 return SignalServiceDataMessage
.newBuilder()
898 .asGroupMessage(group
.build())
899 .withExpiration(g
.getMessageExpirationTime());
902 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV2 g
) {
903 SignalServiceGroupV2
.Builder group
= SignalServiceGroupV2
.newBuilder(g
.getMasterKey())
904 .withRevision(g
.getGroup().getRevision())
905 // .withSignedGroupChange() // TODO
907 return SignalServiceDataMessage
.newBuilder()
908 .asGroupMessage(group
.build())
909 .withExpiration(g
.getMessageExpirationTime());
912 Pair
<Long
, List
<SendMessageResult
>> sendGroupInfoRequest(
913 byte[] groupId
, SignalServiceAddress recipient
914 ) throws IOException
{
915 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
918 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
919 .asGroupMessage(group
.build());
921 // Send group info request message to the recipient who sent us a message with this groupId
922 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
926 SignalServiceAddress remoteAddress
, long messageId
927 ) throws IOException
, UntrustedIdentityException
{
928 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
929 Collections
.singletonList(messageId
),
930 System
.currentTimeMillis());
932 createMessageSender().sendReceipt(remoteAddress
,
933 unidentifiedAccessHelper
.getAccessFor(remoteAddress
),
937 public Pair
<Long
, List
<SendMessageResult
>> sendMessage(
938 String messageText
, List
<String
> attachments
, List
<String
> recipients
939 ) throws IOException
, AttachmentInvalidException
, InvalidNumberException
{
940 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
941 .withBody(messageText
);
942 if (attachments
!= null) {
943 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
945 // Upload attachments here, so we only upload once even for multiple recipients
946 SignalServiceMessageSender messageSender
= createMessageSender();
947 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
948 for (SignalServiceAttachment attachment
: attachmentStreams
) {
949 if (attachment
.isStream()) {
950 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
951 } else if (attachment
.isPointer()) {
952 attachmentPointers
.add(attachment
.asPointer());
956 messageBuilder
.withAttachments(attachmentPointers
);
958 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
961 public Pair
<Long
, List
<SendMessageResult
>> sendMessageReaction(
962 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, List
<String
> recipients
963 ) throws IOException
, InvalidNumberException
{
964 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
966 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
967 targetSentTimestamp
);
968 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
969 .withReaction(reaction
);
970 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
973 public Pair
<Long
, List
<SendMessageResult
>> sendEndSessionMessage(List
<String
> recipients
) throws IOException
, InvalidNumberException
{
974 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().asEndSessionMessage();
976 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
978 return sendMessage(messageBuilder
, signalServiceAddresses
);
979 } catch (Exception e
) {
980 for (SignalServiceAddress address
: signalServiceAddresses
) {
981 handleEndSession(address
);
988 public String
getContactName(String number
) throws InvalidNumberException
{
989 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
990 if (contact
== null) {
997 public void setContactName(String number
, String name
) throws InvalidNumberException
{
998 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
999 ContactInfo contact
= account
.getContactStore().getContact(address
);
1000 if (contact
== null) {
1001 contact
= new ContactInfo(address
);
1003 contact
.name
= name
;
1004 account
.getContactStore().updateContact(contact
);
1008 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
1009 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
1012 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
1013 ContactInfo contact
= account
.getContactStore().getContact(address
);
1014 if (contact
== null) {
1015 contact
= new ContactInfo(address
);
1017 contact
.blocked
= blocked
;
1018 account
.getContactStore().updateContact(contact
);
1022 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
1023 GroupInfo group
= getGroup(groupId
);
1024 if (group
== null) {
1025 throw new GroupNotFoundException(groupId
);
1028 group
.setBlocked(blocked
);
1029 account
.getGroupStore().updateGroup(group
);
1033 public Pair
<byte[], List
<SendMessageResult
>> updateGroup(
1034 byte[] groupId
, String name
, List
<String
> members
, String avatar
1035 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
1036 return sendUpdateGroupMessage(groupId
,
1038 members
== null ?
null : getSignalServiceAddresses(members
),
1043 * Change the expiration timer for a contact
1045 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
1046 ContactInfo contact
= account
.getContactStore().getContact(address
);
1047 contact
.messageExpirationTime
= messageExpirationTimer
;
1048 account
.getContactStore().updateContact(contact
);
1049 sendExpirationTimerUpdate(address
);
1053 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
1054 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1055 .asExpirationUpdate();
1056 sendMessage(messageBuilder
, Collections
.singleton(address
));
1060 * Change the expiration timer for a contact
1062 public void setExpirationTimer(
1063 String number
, int messageExpirationTimer
1064 ) throws IOException
, InvalidNumberException
{
1065 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1066 setExpirationTimer(address
, messageExpirationTimer
);
1070 * Change the expiration timer for a group
1072 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
1073 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
1074 if (g
instanceof GroupInfoV1
) {
1075 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
1076 groupInfoV1
.messageExpirationTime
= messageExpirationTimer
;
1077 account
.getGroupStore().updateGroup(groupInfoV1
);
1079 throw new RuntimeException("TODO Not implemented!");
1084 * Upload the sticker pack from path.
1086 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
1087 * @return if successful, returns the URL to install the sticker pack in the signal app
1089 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
1090 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
1092 SignalServiceMessageSender messageSender
= createMessageSender();
1094 byte[] packKey
= KeyUtils
.createStickerUploadKey();
1095 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
1097 Sticker sticker
= new Sticker(Hex
.fromStringCondensed(packId
), packKey
);
1098 account
.getStickerStore().updateSticker(sticker
);
1102 return new URI("https",
1105 "pack_id=" + URLEncoder
.encode(packId
, StandardCharsets
.UTF_8
) + "&pack_key=" + URLEncoder
.encode(
1106 Hex
.toStringCondensed(packKey
),
1107 StandardCharsets
.UTF_8
)).toString();
1108 } catch (URISyntaxException e
) {
1109 throw new AssertionError(e
);
1113 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(
1115 ) throws IOException
, StickerPackInvalidException
{
1117 String rootPath
= null;
1119 final File file
= new File(path
);
1120 if (file
.getName().endsWith(".zip")) {
1121 zip
= new ZipFile(file
);
1122 } else if (file
.getName().equals("manifest.json")) {
1123 rootPath
= file
.getParent();
1125 throw new StickerPackInvalidException("Could not find manifest.json");
1128 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
1130 if (pack
.stickers
== null) {
1131 throw new StickerPackInvalidException("Must set a 'stickers' field.");
1134 if (pack
.stickers
.isEmpty()) {
1135 throw new StickerPackInvalidException("Must include stickers.");
1138 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
1139 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
1140 if (sticker
.file
== null) {
1141 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
1144 Pair
<InputStream
, Long
> data
;
1146 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
1147 } catch (IOException ignored
) {
1148 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
1151 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
1152 StickerInfo stickerInfo
= new StickerInfo(data
.first(),
1154 Optional
.fromNullable(sticker
.emoji
).or(""),
1156 stickers
.add(stickerInfo
);
1159 StickerInfo cover
= null;
1160 if (pack
.cover
!= null) {
1161 if (pack
.cover
.file
== null) {
1162 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
1165 Pair
<InputStream
, Long
> data
;
1167 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
1168 } catch (IOException ignored
) {
1169 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
1172 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
1173 cover
= new StickerInfo(data
.first(),
1175 Optional
.fromNullable(pack
.cover
.emoji
).or(""),
1179 return new SignalServiceStickerManifestUpload(pack
.title
, pack
.author
, cover
, stickers
);
1182 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
1183 InputStream inputStream
;
1185 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
1187 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
1189 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
1192 private static Pair
<InputStream
, Long
> getInputStreamAndLength(
1193 final String rootPath
, final ZipFile zip
, final String subfile
1194 ) throws IOException
{
1196 final ZipEntry entry
= zip
.getEntry(subfile
);
1197 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
1199 final File file
= new File(rootPath
, subfile
);
1200 return new Pair
<>(new FileInputStream(file
), file
.length());
1204 void requestSyncGroups() throws IOException
{
1205 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1206 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
)
1208 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1210 sendSyncMessage(message
);
1211 } catch (UntrustedIdentityException e
) {
1212 e
.printStackTrace();
1216 void requestSyncContacts() throws IOException
{
1217 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1218 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
)
1220 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1222 sendSyncMessage(message
);
1223 } catch (UntrustedIdentityException e
) {
1224 e
.printStackTrace();
1228 void requestSyncBlocked() throws IOException
{
1229 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1230 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
)
1232 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1234 sendSyncMessage(message
);
1235 } catch (UntrustedIdentityException e
) {
1236 e
.printStackTrace();
1240 void requestSyncConfiguration() throws IOException
{
1241 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1242 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
)
1244 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1246 sendSyncMessage(message
);
1247 } catch (UntrustedIdentityException e
) {
1248 e
.printStackTrace();
1252 private byte[] getSenderCertificate() {
1253 // TODO support UUID capable sender certificates
1254 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
1257 certificate
= accountManager
.getSenderCertificate();
1258 } catch (IOException e
) {
1259 System
.err
.println("Failed to get sender certificate: " + e
);
1262 // TODO cache for a day
1266 private void sendSyncMessage(SignalServiceSyncMessage message
) throws IOException
, UntrustedIdentityException
{
1267 SignalServiceMessageSender messageSender
= createMessageSender();
1269 messageSender
.sendMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
1270 } catch (UntrustedIdentityException e
) {
1271 account
.getSignalProtocolStore()
1272 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1274 TrustLevel
.UNTRUSTED
);
1279 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1280 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1282 for (String number
: numbers
) {
1283 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1285 return signalServiceAddresses
;
1288 private Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1289 SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
1290 ) throws IOException
{
1291 recipients
= recipients
.stream().map(this::resolveSignalServiceAddress
).collect(Collectors
.toSet());
1292 final long timestamp
= System
.currentTimeMillis();
1293 messageBuilder
.withTimestamp(timestamp
);
1294 getOrCreateMessagePipe();
1295 getOrCreateUnidentifiedMessagePipe();
1296 SignalServiceDataMessage message
= null;
1298 message
= messageBuilder
.build();
1299 if (message
.getGroupContext().isPresent()) {
1301 SignalServiceMessageSender messageSender
= createMessageSender();
1302 final boolean isRecipientUpdate
= false;
1303 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
),
1304 unidentifiedAccessHelper
.getAccessFor(recipients
),
1307 for (SendMessageResult r
: result
) {
1308 if (r
.getIdentityFailure() != null) {
1309 account
.getSignalProtocolStore()
1310 .saveIdentity(r
.getAddress(),
1311 r
.getIdentityFailure().getIdentityKey(),
1312 TrustLevel
.UNTRUSTED
);
1315 return new Pair
<>(timestamp
, result
);
1316 } catch (UntrustedIdentityException e
) {
1317 account
.getSignalProtocolStore()
1318 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1320 TrustLevel
.UNTRUSTED
);
1321 return new Pair
<>(timestamp
, Collections
.emptyList());
1324 // Send to all individually, so sync messages are sent correctly
1325 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1326 for (SignalServiceAddress address
: recipients
) {
1327 ContactInfo contact
= account
.getContactStore().getContact(address
);
1328 if (contact
!= null) {
1329 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1330 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1332 messageBuilder
.withExpiration(0);
1333 messageBuilder
.withProfileKey(null);
1335 message
= messageBuilder
.build();
1336 if (address
.matches(account
.getSelfAddress())) {
1337 results
.add(sendSelfMessage(message
));
1339 results
.add(sendMessage(address
, message
));
1342 return new Pair
<>(timestamp
, results
);
1345 if (message
!= null && message
.isEndSession()) {
1346 for (SignalServiceAddress recipient
: recipients
) {
1347 handleEndSession(recipient
);
1354 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1355 SignalServiceMessageSender messageSender
= createMessageSender();
1357 SignalServiceAddress recipient
= account
.getSelfAddress();
1359 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= unidentifiedAccessHelper
.getAccessFor(recipient
);
1360 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1361 message
.getTimestamp(),
1363 message
.getExpiresInSeconds(),
1364 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1366 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1369 long startTime
= System
.currentTimeMillis();
1370 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1371 return SendMessageResult
.success(recipient
,
1372 unidentifiedAccess
.isPresent(),
1374 System
.currentTimeMillis() - startTime
);
1375 } catch (UntrustedIdentityException e
) {
1376 account
.getSignalProtocolStore()
1377 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1379 TrustLevel
.UNTRUSTED
);
1380 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1384 private SendMessageResult
sendMessage(
1385 SignalServiceAddress address
, SignalServiceDataMessage message
1386 ) throws IOException
{
1387 SignalServiceMessageSender messageSender
= createMessageSender();
1390 return messageSender
.sendMessage(address
, unidentifiedAccessHelper
.getAccessFor(address
), message
);
1391 } catch (UntrustedIdentityException e
) {
1392 account
.getSignalProtocolStore()
1393 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1395 TrustLevel
.UNTRUSTED
);
1396 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1400 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1401 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(),
1402 account
.getSignalProtocolStore(),
1403 Utils
.getCertificateValidator());
1405 return cipher
.decrypt(envelope
);
1406 } catch (ProtocolUntrustedIdentityException e
) {
1407 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1408 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
1410 account
.getSignalProtocolStore()
1411 .saveIdentity(resolveSignalServiceAddress(identityException
.getName()),
1412 identityException
.getUntrustedIdentity(),
1413 TrustLevel
.UNTRUSTED
);
1414 throw identityException
;
1416 throw new AssertionError(e
);
1420 private void handleEndSession(SignalServiceAddress source
) {
1421 account
.getSignalProtocolStore().deleteAllSessions(source
);
1424 private static int currentTimeDays() {
1425 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
1428 private GroupsV2AuthorizationString
getGroupAuthForToday(
1429 final GroupSecretParams groupSecretParams
1430 ) throws IOException
, VerificationFailedException
{
1431 final int today
= currentTimeDays();
1432 // Returns credentials for the next 7 days
1433 final HashMap
<Integer
, AuthCredentialResponse
> credentials
= groupsV2Api
.getCredentials(today
);
1434 // TODO cache credentials until they expire
1435 AuthCredentialResponse authCredentialResponse
= credentials
.get(today
);
1436 return groupsV2Api
.getGroupsV2AuthorizationString(account
.getUuid(),
1439 authCredentialResponse
);
1442 private List
<HandleAction
> handleSignalServiceDataMessage(
1443 SignalServiceDataMessage message
,
1445 SignalServiceAddress source
,
1446 SignalServiceAddress destination
,
1447 boolean ignoreAttachments
1449 List
<HandleAction
> actions
= new ArrayList
<>();
1450 if (message
.getGroupContext().isPresent()) {
1451 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1452 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1453 GroupInfo group
= account
.getGroupStore().getGroupByV1Id(groupInfo
.getGroupId());
1454 if (group
== null || group
instanceof GroupInfoV1
) {
1455 GroupInfoV1 groupV1
= (GroupInfoV1
) group
;
1456 switch (groupInfo
.getType()) {
1458 if (groupV1
== null) {
1459 groupV1
= new GroupInfoV1(groupInfo
.getGroupId());
1462 if (groupInfo
.getAvatar().isPresent()) {
1463 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1464 if (avatar
.isPointer()) {
1466 retrieveGroupAvatarAttachment(avatar
.asPointer(), groupV1
.groupId
);
1467 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1468 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer()
1469 .getRemoteId() + "): " + e
.getMessage());
1474 if (groupInfo
.getName().isPresent()) {
1475 groupV1
.name
= groupInfo
.getName().get();
1478 if (groupInfo
.getMembers().isPresent()) {
1479 groupV1
.addMembers(groupInfo
.getMembers()
1482 .map(this::resolveSignalServiceAddress
)
1483 .collect(Collectors
.toSet()));
1486 account
.getGroupStore().updateGroup(groupV1
);
1490 if (groupV1
== null && !isSync
) {
1491 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1495 if (groupV1
!= null) {
1496 groupV1
.removeMember(source
);
1497 account
.getGroupStore().updateGroup(groupV1
);
1502 if (groupV1
!= null && !isSync
) {
1503 actions
.add(new SendGroupUpdateAction(source
, groupV1
.groupId
));
1508 // Received a group v1 message for a v2 group
1511 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1512 final SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1513 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1515 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
1517 byte[] groupId
= groupSecretParams
.getPublicParams().getGroupIdentifier().serialize();
1518 GroupInfo groupInfo
= account
.getGroupStore().getGroupByV2Id(groupId
);
1519 if (groupInfo
instanceof GroupInfoV1
) {
1520 // Received a v2 group message for a v2 group, we need to locally migrate the group
1521 account
.getGroupStore().deleteGroup(groupInfo
.groupId
);
1522 GroupInfoV2 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1523 groupInfoV2
.setGroup(getDecryptedGroup(groupSecretParams
));
1524 account
.getGroupStore().updateGroup(groupInfoV2
);
1525 System
.err
.println("Locally migrated group "
1526 + Base64
.encodeBytes(groupInfo
.groupId
)
1527 + " to group v2, id: "
1528 + Base64
.encodeBytes(groupInfoV2
.groupId
)
1530 } else if (groupInfo
== null || groupInfo
instanceof GroupInfoV2
) {
1531 GroupInfoV2 groupInfoV2
= groupInfo
== null
1532 ?
new GroupInfoV2(groupId
, groupMasterKey
)
1533 : (GroupInfoV2
) groupInfo
;
1535 if (groupInfoV2
.getGroup() == null
1536 || groupInfoV2
.getGroup().getRevision() < groupContext
.getRevision()) {
1537 DecryptedGroup group
= null;
1538 if (groupContext
.hasSignedGroupChange()
1539 && groupInfoV2
.getGroup() != null
1540 && groupInfoV2
.getGroup().getRevision() + 1 == groupContext
.getRevision()) {
1541 group
= groupHelper
.getUpdatedDecryptedGroup(groupInfoV2
.getGroup(),
1542 groupContext
.getSignedGroupChange(),
1545 if (group
== null) {
1546 group
= getDecryptedGroup(groupSecretParams
);
1548 groupInfoV2
.setGroup(group
);
1549 account
.getGroupStore().updateGroup(groupInfoV2
);
1554 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1555 if (message
.isEndSession()) {
1556 handleEndSession(conversationPartnerAddress
);
1558 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1559 if (message
.getGroupContext().isPresent()) {
1560 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1561 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1562 GroupInfoV1 group
= account
.getGroupStore().getOrCreateGroupV1(groupInfo
.getGroupId());
1563 if (group
!= null) {
1564 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1565 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1566 account
.getGroupStore().updateGroup(group
);
1569 } else if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1570 // disappearing message timer already stored in the DecryptedGroup
1573 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1574 if (contact
== null) {
1575 contact
= new ContactInfo(conversationPartnerAddress
);
1577 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1578 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1579 account
.getContactStore().updateContact(contact
);
1583 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1584 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1585 if (attachment
.isPointer()) {
1587 retrieveAttachment(attachment
.asPointer());
1588 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1589 System
.err
.println("Failed to retrieve attachment ("
1590 + attachment
.asPointer().getRemoteId()
1597 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1598 final ProfileKey profileKey
;
1600 profileKey
= new ProfileKey(message
.getProfileKey().get());
1601 } catch (InvalidInputException e
) {
1602 throw new AssertionError(e
);
1604 if (source
.matches(account
.getSelfAddress())) {
1605 this.account
.setProfileKey(profileKey
);
1607 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1609 if (message
.getPreviews().isPresent()) {
1610 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1611 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1612 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1613 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1615 retrieveAttachment(attachment
);
1616 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1617 System
.err
.println("Failed to retrieve attachment ("
1618 + attachment
.getRemoteId()
1625 if (message
.getSticker().isPresent()) {
1626 final SignalServiceDataMessage
.Sticker messageSticker
= message
.getSticker().get();
1627 Sticker sticker
= account
.getStickerStore().getSticker(messageSticker
.getPackId());
1628 if (sticker
== null) {
1629 sticker
= new Sticker(messageSticker
.getPackId(), messageSticker
.getPackKey());
1630 account
.getStickerStore().updateSticker(sticker
);
1636 private DecryptedGroup
getDecryptedGroup(final GroupSecretParams groupSecretParams
) {
1638 final GroupsV2AuthorizationString groupsV2AuthorizationString
= getGroupAuthForToday(groupSecretParams
);
1639 DecryptedGroup group
= groupsV2Api
.getGroup(groupSecretParams
, groupsV2AuthorizationString
);
1640 for (DecryptedMember member
: group
.getMembersList()) {
1641 final SignalServiceAddress address
= resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil
.parseOrThrow(
1642 member
.getUuid().toByteArray()), null));
1644 account
.getProfileStore()
1645 .storeProfileKey(address
, new ProfileKey(member
.getProfileKey().toByteArray()));
1646 } catch (InvalidInputException ignored
) {
1650 } catch (IOException
| VerificationFailedException
| InvalidGroupStateException e
) {
1651 System
.err
.println("Failed to retrieve Group V2 info, ignoring ...");
1656 private void retryFailedReceivedMessages(
1657 ReceiveMessageHandler handler
, boolean ignoreAttachments
1659 final File cachePath
= new File(getMessageCachePath());
1660 if (!cachePath
.exists()) {
1663 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1664 if (!dir
.isDirectory()) {
1665 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1669 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1670 if (!fileEntry
.isFile()) {
1673 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1675 // Try to delete directory if empty
1680 private void retryFailedReceivedMessage(
1681 final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
1683 SignalServiceEnvelope envelope
;
1685 envelope
= Utils
.loadEnvelope(fileEntry
);
1686 if (envelope
== null) {
1689 } catch (IOException e
) {
1690 e
.printStackTrace();
1693 SignalServiceContent content
= null;
1694 if (!envelope
.isReceipt()) {
1696 content
= decryptMessage(envelope
);
1697 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1699 } catch (Exception er
) {
1700 // All other errors are not recoverable, so delete the cached message
1702 Files
.delete(fileEntry
.toPath());
1703 } catch (IOException e
) {
1704 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1708 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1709 for (HandleAction action
: actions
) {
1711 action
.execute(this);
1712 } catch (Throwable e
) {
1713 e
.printStackTrace();
1718 handler
.handleMessage(envelope
, content
, null);
1720 Files
.delete(fileEntry
.toPath());
1721 } catch (IOException e
) {
1722 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1726 public void receiveMessages(
1729 boolean returnOnTimeout
,
1730 boolean ignoreAttachments
,
1731 ReceiveMessageHandler handler
1732 ) throws IOException
{
1733 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1735 Set
<HandleAction
> queuedActions
= null;
1737 getOrCreateMessagePipe();
1739 boolean hasCaughtUpWithOldMessages
= false;
1742 SignalServiceEnvelope envelope
;
1743 SignalServiceContent content
= null;
1744 Exception exception
= null;
1745 final long now
= new Date().getTime();
1747 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1748 // store message on disk, before acknowledging receipt to the server
1750 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1751 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1752 Utils
.storeEnvelope(envelope1
, cacheFile
);
1753 } catch (IOException e
) {
1754 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: "
1758 if (result
.isPresent()) {
1759 envelope
= result
.get();
1761 // Received indicator that server queue is empty
1762 hasCaughtUpWithOldMessages
= true;
1764 if (queuedActions
!= null) {
1765 for (HandleAction action
: queuedActions
) {
1767 action
.execute(this);
1768 } catch (Throwable e
) {
1769 e
.printStackTrace();
1773 queuedActions
.clear();
1774 queuedActions
= null;
1777 // Continue to wait another timeout for new messages
1780 } catch (TimeoutException e
) {
1781 if (returnOnTimeout
) return;
1783 } catch (InvalidVersionException e
) {
1784 System
.err
.println("Ignoring error: " + e
.getMessage());
1788 if (envelope
.hasSource()) {
1789 // Store uuid if we don't have it already
1790 SignalServiceAddress source
= envelope
.getSourceAddress();
1791 resolveSignalServiceAddress(source
);
1793 if (!envelope
.isReceipt()) {
1795 content
= decryptMessage(envelope
);
1796 } catch (Exception e
) {
1799 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1800 if (hasCaughtUpWithOldMessages
) {
1801 for (HandleAction action
: actions
) {
1803 action
.execute(this);
1804 } catch (Throwable e
) {
1805 e
.printStackTrace();
1809 if (queuedActions
== null) {
1810 queuedActions
= new HashSet
<>();
1812 queuedActions
.addAll(actions
);
1816 if (!isMessageBlocked(envelope
, content
)) {
1817 handler
.handleMessage(envelope
, content
, exception
);
1819 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1820 File cacheFile
= null;
1822 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1823 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1824 Files
.delete(cacheFile
.toPath());
1825 // Try to delete directory if empty
1826 new File(getMessageCachePath()).delete();
1827 } catch (IOException e
) {
1828 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1834 private boolean isMessageBlocked(
1835 SignalServiceEnvelope envelope
, SignalServiceContent content
1837 SignalServiceAddress source
;
1838 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1839 source
= envelope
.getSourceAddress();
1840 } else if (content
!= null) {
1841 source
= content
.getSender();
1845 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1846 if (sourceContact
!= null && sourceContact
.blocked
) {
1850 if (content
!= null && content
.getDataMessage().isPresent()) {
1851 SignalServiceDataMessage message
= content
.getDataMessage().get();
1852 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1853 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1854 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1855 return groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.isBlocked();
1861 private List
<HandleAction
> handleMessage(
1862 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
1864 List
<HandleAction
> actions
= new ArrayList
<>();
1865 if (content
!= null) {
1866 SignalServiceAddress sender
;
1867 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1868 sender
= envelope
.getSourceAddress();
1870 sender
= content
.getSender();
1872 // Store uuid if we don't have it already
1873 resolveSignalServiceAddress(sender
);
1875 if (content
.getDataMessage().isPresent()) {
1876 SignalServiceDataMessage message
= content
.getDataMessage().get();
1878 if (content
.isNeedsReceipt()) {
1879 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1882 actions
.addAll(handleSignalServiceDataMessage(message
,
1885 account
.getSelfAddress(),
1886 ignoreAttachments
));
1888 if (content
.getSyncMessage().isPresent()) {
1889 account
.setMultiDevice(true);
1890 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1891 if (syncMessage
.getSent().isPresent()) {
1892 SentTranscriptMessage message
= syncMessage
.getSent().get();
1893 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
1896 message
.getDestination().orNull(),
1897 ignoreAttachments
));
1899 if (syncMessage
.getRequest().isPresent()) {
1900 RequestMessage rm
= syncMessage
.getRequest().get();
1901 if (rm
.isContactsRequest()) {
1902 actions
.add(SendSyncContactsAction
.create());
1904 if (rm
.isGroupsRequest()) {
1905 actions
.add(SendSyncGroupsAction
.create());
1907 if (rm
.isBlockedListRequest()) {
1908 actions
.add(SendSyncBlockedListAction
.create());
1910 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
1912 if (syncMessage
.getGroups().isPresent()) {
1913 File tmpFile
= null;
1915 tmpFile
= IOUtils
.createTempFile();
1916 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups()
1918 .asPointer(), tmpFile
)) {
1919 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1921 while ((g
= s
.read()) != null) {
1922 GroupInfoV1 syncGroup
= account
.getGroupStore().getOrCreateGroupV1(g
.getId());
1923 if (syncGroup
!= null) {
1924 if (g
.getName().isPresent()) {
1925 syncGroup
.name
= g
.getName().get();
1927 syncGroup
.addMembers(g
.getMembers()
1929 .map(this::resolveSignalServiceAddress
)
1930 .collect(Collectors
.toSet()));
1931 if (!g
.isActive()) {
1932 syncGroup
.removeMember(account
.getSelfAddress());
1934 // Add ourself to the member set as it's marked as active
1935 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1937 syncGroup
.blocked
= g
.isBlocked();
1938 if (g
.getColor().isPresent()) {
1939 syncGroup
.color
= g
.getColor().get();
1942 if (g
.getAvatar().isPresent()) {
1943 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1945 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1946 syncGroup
.archived
= g
.isArchived();
1947 account
.getGroupStore().updateGroup(syncGroup
);
1951 } catch (Exception e
) {
1952 e
.printStackTrace();
1954 if (tmpFile
!= null) {
1956 Files
.delete(tmpFile
.toPath());
1957 } catch (IOException e
) {
1958 System
.err
.println("Failed to delete received groups temp file “"
1966 if (syncMessage
.getBlockedList().isPresent()) {
1967 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1968 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1969 setContactBlocked(resolveSignalServiceAddress(address
), true);
1971 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1973 setGroupBlocked(groupId
, true);
1974 } catch (GroupNotFoundException e
) {
1975 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: "
1976 + Base64
.encodeBytes(groupId
));
1980 if (syncMessage
.getContacts().isPresent()) {
1981 File tmpFile
= null;
1983 tmpFile
= IOUtils
.createTempFile();
1984 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1985 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream()
1986 .asPointer(), tmpFile
)) {
1987 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1988 if (contactsMessage
.isComplete()) {
1989 account
.getContactStore().clear();
1992 while ((c
= s
.read()) != null) {
1993 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1994 account
.setProfileKey(c
.getProfileKey().get());
1996 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1997 ContactInfo contact
= account
.getContactStore().getContact(address
);
1998 if (contact
== null) {
1999 contact
= new ContactInfo(address
);
2001 if (c
.getName().isPresent()) {
2002 contact
.name
= c
.getName().get();
2004 if (c
.getColor().isPresent()) {
2005 contact
.color
= c
.getColor().get();
2007 if (c
.getProfileKey().isPresent()) {
2008 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
2010 if (c
.getVerified().isPresent()) {
2011 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
2012 account
.getSignalProtocolStore()
2013 .setIdentityTrustLevel(verifiedMessage
.getDestination(),
2014 verifiedMessage
.getIdentityKey(),
2015 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2017 if (c
.getExpirationTimer().isPresent()) {
2018 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
2020 contact
.blocked
= c
.isBlocked();
2021 contact
.inboxPosition
= c
.getInboxPosition().orNull();
2022 contact
.archived
= c
.isArchived();
2023 account
.getContactStore().updateContact(contact
);
2025 if (c
.getAvatar().isPresent()) {
2026 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
2030 } catch (Exception e
) {
2031 e
.printStackTrace();
2033 if (tmpFile
!= null) {
2035 Files
.delete(tmpFile
.toPath());
2036 } catch (IOException e
) {
2037 System
.err
.println("Failed to delete received contacts temp file “"
2045 if (syncMessage
.getVerified().isPresent()) {
2046 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
2047 account
.getSignalProtocolStore()
2048 .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()),
2049 verifiedMessage
.getIdentityKey(),
2050 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2052 if (syncMessage
.getStickerPackOperations().isPresent()) {
2053 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations()
2055 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
2056 if (!m
.getPackId().isPresent()) {
2059 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
2060 if (sticker
== null) {
2061 if (!m
.getPackKey().isPresent()) {
2064 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
2066 sticker
.setInstalled(!m
.getType().isPresent()
2067 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
2068 account
.getStickerStore().updateSticker(sticker
);
2071 if (syncMessage
.getConfiguration().isPresent()) {
2079 private File
getContactAvatarFile(String number
) {
2080 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
2083 private File
retrieveContactAvatarAttachment(
2084 SignalServiceAttachment attachment
, String number
2085 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2086 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2087 if (attachment
.isPointer()) {
2088 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2089 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
2091 SignalServiceAttachmentStream stream
= attachment
.asStream();
2092 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
2096 private File
getGroupAvatarFile(byte[] groupId
) {
2097 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
2100 private File
retrieveGroupAvatarAttachment(
2101 SignalServiceAttachment attachment
, byte[] groupId
2102 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2103 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2104 if (attachment
.isPointer()) {
2105 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2106 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
2108 SignalServiceAttachmentStream stream
= attachment
.asStream();
2109 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
2113 private File
getProfileAvatarFile(SignalServiceAddress address
) {
2114 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
2117 private File
retrieveProfileAvatar(
2118 SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
2119 ) throws IOException
{
2120 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2121 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2122 File outputFile
= getProfileAvatarFile(address
);
2124 File tmpFile
= IOUtils
.createTempFile();
2125 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
,
2128 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2129 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2130 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
2133 Files
.delete(tmpFile
.toPath());
2134 } catch (IOException e
) {
2135 System
.err
.println("Failed to delete received avatar temp file “" + tmpFile
+ "”: " + e
.getMessage());
2141 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
2142 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
2145 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2146 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
2147 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
2150 private File
retrieveAttachment(
2151 SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
2152 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2153 if (storePreview
&& pointer
.getPreview().isPresent()) {
2154 File previewFile
= new File(outputFile
+ ".preview");
2155 try (OutputStream output
= new FileOutputStream(previewFile
)) {
2156 byte[] preview
= pointer
.getPreview().get();
2157 output
.write(preview
, 0, preview
.length
);
2158 } catch (FileNotFoundException e
) {
2159 e
.printStackTrace();
2164 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2166 File tmpFile
= IOUtils
.createTempFile();
2167 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
,
2169 ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
2170 IOUtils
.copyStreamToFile(input
, outputFile
);
2173 Files
.delete(tmpFile
.toPath());
2174 } catch (IOException e
) {
2175 System
.err
.println("Failed to delete received attachment temp file “"
2184 private InputStream
retrieveAttachmentAsStream(
2185 SignalServiceAttachmentPointer pointer
, File tmpFile
2186 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2187 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2188 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
2191 void sendGroups() throws IOException
, UntrustedIdentityException
{
2192 File groupsFile
= IOUtils
.createTempFile();
2195 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
2196 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
2197 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2198 if (record instanceof GroupInfoV1
) {
2199 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
2200 out
.write(new DeviceGroup(groupInfo
.groupId
,
2201 Optional
.fromNullable(groupInfo
.name
),
2202 new ArrayList
<>(groupInfo
.getMembers()),
2203 createGroupAvatarAttachment(groupInfo
.groupId
),
2204 groupInfo
.isMember(account
.getSelfAddress()),
2205 Optional
.of(groupInfo
.messageExpirationTime
),
2206 Optional
.fromNullable(groupInfo
.color
),
2208 Optional
.fromNullable(groupInfo
.inboxPosition
),
2209 groupInfo
.archived
));
2214 if (groupsFile
.exists() && groupsFile
.length() > 0) {
2215 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
2216 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2217 .withStream(groupsFileStream
)
2218 .withContentType("application/octet-stream")
2219 .withLength(groupsFile
.length())
2222 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
2227 Files
.delete(groupsFile
.toPath());
2228 } catch (IOException e
) {
2229 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
2234 public void sendContacts() throws IOException
, UntrustedIdentityException
{
2235 File contactsFile
= IOUtils
.createTempFile();
2238 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
2239 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
2240 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2241 VerifiedMessage verifiedMessage
= null;
2242 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore()
2243 .getIdentity(record.getAddress());
2244 if (currentIdentity
!= null) {
2245 verifiedMessage
= new VerifiedMessage(record.getAddress(),
2246 currentIdentity
.getIdentityKey(),
2247 currentIdentity
.getTrustLevel().toVerifiedState(),
2248 currentIdentity
.getDateAdded().getTime());
2251 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
2252 out
.write(new DeviceContact(record.getAddress(),
2253 Optional
.fromNullable(record.name
),
2254 createContactAvatarAttachment(record.number
),
2255 Optional
.fromNullable(record.color
),
2256 Optional
.fromNullable(verifiedMessage
),
2257 Optional
.fromNullable(profileKey
),
2259 Optional
.of(record.messageExpirationTime
),
2260 Optional
.fromNullable(record.inboxPosition
),
2264 if (account
.getProfileKey() != null) {
2265 // Send our own profile key as well
2266 out
.write(new DeviceContact(account
.getSelfAddress(),
2271 Optional
.of(account
.getProfileKey()),
2279 if (contactsFile
.exists() && contactsFile
.length() > 0) {
2280 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
2281 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2282 .withStream(contactsFileStream
)
2283 .withContentType("application/octet-stream")
2284 .withLength(contactsFile
.length())
2287 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
2292 Files
.delete(contactsFile
.toPath());
2293 } catch (IOException e
) {
2294 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
2299 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2300 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2301 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2302 if (record.blocked
) {
2303 addresses
.add(record.getAddress());
2306 List
<byte[]> groupIds
= new ArrayList
<>();
2307 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2308 if (record.isBlocked()) {
2309 groupIds
.add(record.groupId
);
2312 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2315 private void sendVerifiedMessage(
2316 SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
2317 ) throws IOException
, UntrustedIdentityException
{
2318 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
,
2320 trustLevel
.toVerifiedState(),
2321 System
.currentTimeMillis());
2322 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2325 public List
<ContactInfo
> getContacts() {
2326 return account
.getContactStore().getContacts();
2329 public ContactInfo
getContact(String number
) {
2330 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
2333 public GroupInfo
getGroup(byte[] groupId
) {
2334 return account
.getGroupStore().getGroup(groupId
);
2337 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
2338 return account
.getSignalProtocolStore().getIdentities();
2341 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
2342 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2346 * Trust this the identity with this fingerprint
2348 * @param name username of the identity
2349 * @param fingerprint Fingerprint
2351 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2352 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2353 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2357 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2358 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2362 account
.getSignalProtocolStore()
2363 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2365 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2366 } catch (IOException
| UntrustedIdentityException e
) {
2367 e
.printStackTrace();
2376 * Trust this the identity with this safety number
2378 * @param name username of the identity
2379 * @param safetyNumber Safety number
2381 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2382 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2383 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2387 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2388 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2392 account
.getSignalProtocolStore()
2393 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2395 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2396 } catch (IOException
| UntrustedIdentityException e
) {
2397 e
.printStackTrace();
2406 * Trust all keys of this identity without verification
2408 * @param name username of the identity
2410 public boolean trustIdentityAllKeys(String name
) {
2411 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2412 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2416 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2417 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2418 account
.getSignalProtocolStore()
2419 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2421 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2422 } catch (IOException
| UntrustedIdentityException e
) {
2423 e
.printStackTrace();
2431 public String
computeSafetyNumber(
2432 SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
2434 return Utils
.computeSafetyNumber(account
.getSelfAddress(),
2435 getIdentityKeyPair().getPublicKey(),
2440 void saveAccount() {
2444 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2445 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
)
2447 : Util
.canonicalizeNumber(identifier
, account
.getUsername());
2448 return resolveSignalServiceAddress(canonicalizedNumber
);
2451 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2452 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2454 return resolveSignalServiceAddress(address
);
2457 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2458 if (address
.matches(account
.getSelfAddress())) {
2459 return account
.getSelfAddress();
2462 return account
.getRecipientStore().resolveServiceAddress(address
);
2466 public void close() throws IOException
{
2467 if (messagePipe
!= null) {
2468 messagePipe
.shutdown();
2472 if (unidentifiedMessagePipe
!= null) {
2473 unidentifiedMessagePipe
.shutdown();
2474 unidentifiedMessagePipe
= null;
2480 public interface ReceiveMessageHandler
{
2482 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);