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("TODO Not implemented!");
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().getGroup(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 System
.err
.println("Received a group v1 message for a v2 group: " + group
.getTitle());
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().getGroup(groupId
);
1519 if (groupInfo
instanceof GroupInfoV1
) {
1520 // TODO upgrade group
1521 } else if (groupInfo
== null || groupInfo
instanceof GroupInfoV2
) {
1522 GroupInfoV2 groupInfoV2
= groupInfo
== null
1523 ?
new GroupInfoV2(groupId
, groupMasterKey
)
1524 : (GroupInfoV2
) groupInfo
;
1526 if (groupInfoV2
.getGroup() == null
1527 || groupInfoV2
.getGroup().getRevision() < groupContext
.getRevision()) {
1528 // TODO check if revision is only 1 behind and a signedGroupChange is available
1530 final GroupsV2AuthorizationString groupsV2AuthorizationString
= getGroupAuthForToday(
1532 final DecryptedGroup group
= groupsV2Api
.getGroup(groupSecretParams
,
1533 groupsV2AuthorizationString
);
1534 groupInfoV2
.setGroup(group
);
1535 for (DecryptedMember member
: group
.getMembersList()) {
1536 final SignalServiceAddress address
= resolveSignalServiceAddress(new SignalServiceAddress(
1537 UuidUtil
.parseOrThrow(member
.getUuid().toByteArray()),
1540 account
.getProfileStore()
1541 .storeProfileKey(address
,
1542 new ProfileKey(member
.getProfileKey().toByteArray()));
1543 } catch (InvalidInputException ignored
) {
1546 } catch (IOException
| VerificationFailedException
| InvalidGroupStateException e
) {
1547 System
.err
.println("Failed to retrieve Group V2 info, ignoring ...");
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 void retryFailedReceivedMessages(
1637 ReceiveMessageHandler handler
, boolean ignoreAttachments
1639 final File cachePath
= new File(getMessageCachePath());
1640 if (!cachePath
.exists()) {
1643 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1644 if (!dir
.isDirectory()) {
1645 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1649 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1650 if (!fileEntry
.isFile()) {
1653 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1655 // Try to delete directory if empty
1660 private void retryFailedReceivedMessage(
1661 final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
1663 SignalServiceEnvelope envelope
;
1665 envelope
= Utils
.loadEnvelope(fileEntry
);
1666 if (envelope
== null) {
1669 } catch (IOException e
) {
1670 e
.printStackTrace();
1673 SignalServiceContent content
= null;
1674 if (!envelope
.isReceipt()) {
1676 content
= decryptMessage(envelope
);
1677 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1679 } catch (Exception er
) {
1680 // All other errors are not recoverable, so delete the cached message
1682 Files
.delete(fileEntry
.toPath());
1683 } catch (IOException e
) {
1684 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1688 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1689 for (HandleAction action
: actions
) {
1691 action
.execute(this);
1692 } catch (Throwable e
) {
1693 e
.printStackTrace();
1698 handler
.handleMessage(envelope
, content
, null);
1700 Files
.delete(fileEntry
.toPath());
1701 } catch (IOException e
) {
1702 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1706 public void receiveMessages(
1709 boolean returnOnTimeout
,
1710 boolean ignoreAttachments
,
1711 ReceiveMessageHandler handler
1712 ) throws IOException
{
1713 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1715 Set
<HandleAction
> queuedActions
= null;
1717 getOrCreateMessagePipe();
1719 boolean hasCaughtUpWithOldMessages
= false;
1722 SignalServiceEnvelope envelope
;
1723 SignalServiceContent content
= null;
1724 Exception exception
= null;
1725 final long now
= new Date().getTime();
1727 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1728 // store message on disk, before acknowledging receipt to the server
1730 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1731 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1732 Utils
.storeEnvelope(envelope1
, cacheFile
);
1733 } catch (IOException e
) {
1734 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: "
1738 if (result
.isPresent()) {
1739 envelope
= result
.get();
1741 // Received indicator that server queue is empty
1742 hasCaughtUpWithOldMessages
= true;
1744 if (queuedActions
!= null) {
1745 for (HandleAction action
: queuedActions
) {
1747 action
.execute(this);
1748 } catch (Throwable e
) {
1749 e
.printStackTrace();
1753 queuedActions
.clear();
1754 queuedActions
= null;
1757 // Continue to wait another timeout for new messages
1760 } catch (TimeoutException e
) {
1761 if (returnOnTimeout
) return;
1763 } catch (InvalidVersionException e
) {
1764 System
.err
.println("Ignoring error: " + e
.getMessage());
1768 if (envelope
.hasSource()) {
1769 // Store uuid if we don't have it already
1770 SignalServiceAddress source
= envelope
.getSourceAddress();
1771 resolveSignalServiceAddress(source
);
1773 if (!envelope
.isReceipt()) {
1775 content
= decryptMessage(envelope
);
1776 } catch (Exception e
) {
1779 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1780 if (hasCaughtUpWithOldMessages
) {
1781 for (HandleAction action
: actions
) {
1783 action
.execute(this);
1784 } catch (Throwable e
) {
1785 e
.printStackTrace();
1789 if (queuedActions
== null) {
1790 queuedActions
= new HashSet
<>();
1792 queuedActions
.addAll(actions
);
1796 if (!isMessageBlocked(envelope
, content
)) {
1797 handler
.handleMessage(envelope
, content
, exception
);
1799 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1800 File cacheFile
= null;
1802 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1803 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1804 Files
.delete(cacheFile
.toPath());
1805 // Try to delete directory if empty
1806 new File(getMessageCachePath()).delete();
1807 } catch (IOException e
) {
1808 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1814 private boolean isMessageBlocked(
1815 SignalServiceEnvelope envelope
, SignalServiceContent content
1817 SignalServiceAddress source
;
1818 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1819 source
= envelope
.getSourceAddress();
1820 } else if (content
!= null) {
1821 source
= content
.getSender();
1825 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1826 if (sourceContact
!= null && sourceContact
.blocked
) {
1830 if (content
!= null && content
.getDataMessage().isPresent()) {
1831 SignalServiceDataMessage message
= content
.getDataMessage().get();
1832 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1833 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1834 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1835 return groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.isBlocked();
1841 private List
<HandleAction
> handleMessage(
1842 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
1844 List
<HandleAction
> actions
= new ArrayList
<>();
1845 if (content
!= null) {
1846 SignalServiceAddress sender
;
1847 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1848 sender
= envelope
.getSourceAddress();
1850 sender
= content
.getSender();
1852 // Store uuid if we don't have it already
1853 resolveSignalServiceAddress(sender
);
1855 if (content
.getDataMessage().isPresent()) {
1856 SignalServiceDataMessage message
= content
.getDataMessage().get();
1858 if (content
.isNeedsReceipt()) {
1859 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1862 actions
.addAll(handleSignalServiceDataMessage(message
,
1865 account
.getSelfAddress(),
1866 ignoreAttachments
));
1868 if (content
.getSyncMessage().isPresent()) {
1869 account
.setMultiDevice(true);
1870 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1871 if (syncMessage
.getSent().isPresent()) {
1872 SentTranscriptMessage message
= syncMessage
.getSent().get();
1873 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
1876 message
.getDestination().orNull(),
1877 ignoreAttachments
));
1879 if (syncMessage
.getRequest().isPresent()) {
1880 RequestMessage rm
= syncMessage
.getRequest().get();
1881 if (rm
.isContactsRequest()) {
1882 actions
.add(SendSyncContactsAction
.create());
1884 if (rm
.isGroupsRequest()) {
1885 actions
.add(SendSyncGroupsAction
.create());
1887 if (rm
.isBlockedListRequest()) {
1888 actions
.add(SendSyncBlockedListAction
.create());
1890 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
1892 if (syncMessage
.getGroups().isPresent()) {
1893 File tmpFile
= null;
1895 tmpFile
= IOUtils
.createTempFile();
1896 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups()
1898 .asPointer(), tmpFile
)) {
1899 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1901 while ((g
= s
.read()) != null) {
1902 GroupInfoV1 syncGroup
= account
.getGroupStore().getOrCreateGroupV1(g
.getId());
1903 if (syncGroup
!= null) {
1904 if (g
.getName().isPresent()) {
1905 syncGroup
.name
= g
.getName().get();
1907 syncGroup
.addMembers(g
.getMembers()
1909 .map(this::resolveSignalServiceAddress
)
1910 .collect(Collectors
.toSet()));
1911 if (!g
.isActive()) {
1912 syncGroup
.removeMember(account
.getSelfAddress());
1914 // Add ourself to the member set as it's marked as active
1915 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1917 syncGroup
.blocked
= g
.isBlocked();
1918 if (g
.getColor().isPresent()) {
1919 syncGroup
.color
= g
.getColor().get();
1922 if (g
.getAvatar().isPresent()) {
1923 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1925 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1926 syncGroup
.archived
= g
.isArchived();
1927 account
.getGroupStore().updateGroup(syncGroup
);
1931 } catch (Exception e
) {
1932 e
.printStackTrace();
1934 if (tmpFile
!= null) {
1936 Files
.delete(tmpFile
.toPath());
1937 } catch (IOException e
) {
1938 System
.err
.println("Failed to delete received groups temp file “"
1946 if (syncMessage
.getBlockedList().isPresent()) {
1947 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1948 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1949 setContactBlocked(resolveSignalServiceAddress(address
), true);
1951 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1953 setGroupBlocked(groupId
, true);
1954 } catch (GroupNotFoundException e
) {
1955 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: "
1956 + Base64
.encodeBytes(groupId
));
1960 if (syncMessage
.getContacts().isPresent()) {
1961 File tmpFile
= null;
1963 tmpFile
= IOUtils
.createTempFile();
1964 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1965 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream()
1966 .asPointer(), tmpFile
)) {
1967 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1968 if (contactsMessage
.isComplete()) {
1969 account
.getContactStore().clear();
1972 while ((c
= s
.read()) != null) {
1973 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1974 account
.setProfileKey(c
.getProfileKey().get());
1976 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1977 ContactInfo contact
= account
.getContactStore().getContact(address
);
1978 if (contact
== null) {
1979 contact
= new ContactInfo(address
);
1981 if (c
.getName().isPresent()) {
1982 contact
.name
= c
.getName().get();
1984 if (c
.getColor().isPresent()) {
1985 contact
.color
= c
.getColor().get();
1987 if (c
.getProfileKey().isPresent()) {
1988 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
1990 if (c
.getVerified().isPresent()) {
1991 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1992 account
.getSignalProtocolStore()
1993 .setIdentityTrustLevel(verifiedMessage
.getDestination(),
1994 verifiedMessage
.getIdentityKey(),
1995 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1997 if (c
.getExpirationTimer().isPresent()) {
1998 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
2000 contact
.blocked
= c
.isBlocked();
2001 contact
.inboxPosition
= c
.getInboxPosition().orNull();
2002 contact
.archived
= c
.isArchived();
2003 account
.getContactStore().updateContact(contact
);
2005 if (c
.getAvatar().isPresent()) {
2006 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
2010 } catch (Exception e
) {
2011 e
.printStackTrace();
2013 if (tmpFile
!= null) {
2015 Files
.delete(tmpFile
.toPath());
2016 } catch (IOException e
) {
2017 System
.err
.println("Failed to delete received contacts temp file “"
2025 if (syncMessage
.getVerified().isPresent()) {
2026 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
2027 account
.getSignalProtocolStore()
2028 .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()),
2029 verifiedMessage
.getIdentityKey(),
2030 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2032 if (syncMessage
.getStickerPackOperations().isPresent()) {
2033 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations()
2035 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
2036 if (!m
.getPackId().isPresent()) {
2039 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
2040 if (sticker
== null) {
2041 if (!m
.getPackKey().isPresent()) {
2044 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
2046 sticker
.setInstalled(!m
.getType().isPresent()
2047 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
2048 account
.getStickerStore().updateSticker(sticker
);
2051 if (syncMessage
.getConfiguration().isPresent()) {
2059 private File
getContactAvatarFile(String number
) {
2060 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
2063 private File
retrieveContactAvatarAttachment(
2064 SignalServiceAttachment attachment
, String number
2065 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2066 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2067 if (attachment
.isPointer()) {
2068 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2069 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
2071 SignalServiceAttachmentStream stream
= attachment
.asStream();
2072 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
2076 private File
getGroupAvatarFile(byte[] groupId
) {
2077 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
2080 private File
retrieveGroupAvatarAttachment(
2081 SignalServiceAttachment attachment
, byte[] groupId
2082 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2083 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2084 if (attachment
.isPointer()) {
2085 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2086 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
2088 SignalServiceAttachmentStream stream
= attachment
.asStream();
2089 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
2093 private File
getProfileAvatarFile(SignalServiceAddress address
) {
2094 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
2097 private File
retrieveProfileAvatar(
2098 SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
2099 ) throws IOException
{
2100 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2101 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2102 File outputFile
= getProfileAvatarFile(address
);
2104 File tmpFile
= IOUtils
.createTempFile();
2105 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
,
2108 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2109 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2110 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
2113 Files
.delete(tmpFile
.toPath());
2114 } catch (IOException e
) {
2115 System
.err
.println("Failed to delete received avatar temp file “" + tmpFile
+ "”: " + e
.getMessage());
2121 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
2122 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
2125 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2126 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
2127 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
2130 private File
retrieveAttachment(
2131 SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
2132 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2133 if (storePreview
&& pointer
.getPreview().isPresent()) {
2134 File previewFile
= new File(outputFile
+ ".preview");
2135 try (OutputStream output
= new FileOutputStream(previewFile
)) {
2136 byte[] preview
= pointer
.getPreview().get();
2137 output
.write(preview
, 0, preview
.length
);
2138 } catch (FileNotFoundException e
) {
2139 e
.printStackTrace();
2144 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2146 File tmpFile
= IOUtils
.createTempFile();
2147 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
,
2149 ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
2150 IOUtils
.copyStreamToFile(input
, outputFile
);
2153 Files
.delete(tmpFile
.toPath());
2154 } catch (IOException e
) {
2155 System
.err
.println("Failed to delete received attachment temp file “"
2164 private InputStream
retrieveAttachmentAsStream(
2165 SignalServiceAttachmentPointer pointer
, File tmpFile
2166 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2167 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2168 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
2171 void sendGroups() throws IOException
, UntrustedIdentityException
{
2172 File groupsFile
= IOUtils
.createTempFile();
2175 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
2176 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
2177 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2178 if (record instanceof GroupInfoV1
) {
2179 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
2180 out
.write(new DeviceGroup(groupInfo
.groupId
,
2181 Optional
.fromNullable(groupInfo
.name
),
2182 new ArrayList
<>(groupInfo
.getMembers()),
2183 createGroupAvatarAttachment(groupInfo
.groupId
),
2184 groupInfo
.isMember(account
.getSelfAddress()),
2185 Optional
.of(groupInfo
.messageExpirationTime
),
2186 Optional
.fromNullable(groupInfo
.color
),
2188 Optional
.fromNullable(groupInfo
.inboxPosition
),
2189 groupInfo
.archived
));
2194 if (groupsFile
.exists() && groupsFile
.length() > 0) {
2195 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
2196 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2197 .withStream(groupsFileStream
)
2198 .withContentType("application/octet-stream")
2199 .withLength(groupsFile
.length())
2202 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
2207 Files
.delete(groupsFile
.toPath());
2208 } catch (IOException e
) {
2209 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
2214 public void sendContacts() throws IOException
, UntrustedIdentityException
{
2215 File contactsFile
= IOUtils
.createTempFile();
2218 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
2219 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
2220 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2221 VerifiedMessage verifiedMessage
= null;
2222 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore()
2223 .getIdentity(record.getAddress());
2224 if (currentIdentity
!= null) {
2225 verifiedMessage
= new VerifiedMessage(record.getAddress(),
2226 currentIdentity
.getIdentityKey(),
2227 currentIdentity
.getTrustLevel().toVerifiedState(),
2228 currentIdentity
.getDateAdded().getTime());
2231 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
2232 out
.write(new DeviceContact(record.getAddress(),
2233 Optional
.fromNullable(record.name
),
2234 createContactAvatarAttachment(record.number
),
2235 Optional
.fromNullable(record.color
),
2236 Optional
.fromNullable(verifiedMessage
),
2237 Optional
.fromNullable(profileKey
),
2239 Optional
.of(record.messageExpirationTime
),
2240 Optional
.fromNullable(record.inboxPosition
),
2244 if (account
.getProfileKey() != null) {
2245 // Send our own profile key as well
2246 out
.write(new DeviceContact(account
.getSelfAddress(),
2251 Optional
.of(account
.getProfileKey()),
2259 if (contactsFile
.exists() && contactsFile
.length() > 0) {
2260 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
2261 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2262 .withStream(contactsFileStream
)
2263 .withContentType("application/octet-stream")
2264 .withLength(contactsFile
.length())
2267 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
2272 Files
.delete(contactsFile
.toPath());
2273 } catch (IOException e
) {
2274 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
2279 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2280 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2281 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2282 if (record.blocked
) {
2283 addresses
.add(record.getAddress());
2286 List
<byte[]> groupIds
= new ArrayList
<>();
2287 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2288 if (record.isBlocked()) {
2289 groupIds
.add(record.groupId
);
2292 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2295 private void sendVerifiedMessage(
2296 SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
2297 ) throws IOException
, UntrustedIdentityException
{
2298 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
,
2300 trustLevel
.toVerifiedState(),
2301 System
.currentTimeMillis());
2302 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2305 public List
<ContactInfo
> getContacts() {
2306 return account
.getContactStore().getContacts();
2309 public ContactInfo
getContact(String number
) {
2310 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
2313 public GroupInfo
getGroup(byte[] groupId
) {
2314 return account
.getGroupStore().getGroup(groupId
);
2317 public byte[] getGroupId(GroupMasterKey groupMasterKey
) {
2318 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
2319 return groupSecretParams
.getPublicParams().getGroupIdentifier().serialize();
2322 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
2323 return account
.getSignalProtocolStore().getIdentities();
2326 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
2327 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2331 * Trust this the identity with this fingerprint
2333 * @param name username of the identity
2334 * @param fingerprint Fingerprint
2336 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2337 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2338 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2342 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2343 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2347 account
.getSignalProtocolStore()
2348 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2350 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2351 } catch (IOException
| UntrustedIdentityException e
) {
2352 e
.printStackTrace();
2361 * Trust this the identity with this safety number
2363 * @param name username of the identity
2364 * @param safetyNumber Safety number
2366 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2367 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2368 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2372 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2373 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2377 account
.getSignalProtocolStore()
2378 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2380 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2381 } catch (IOException
| UntrustedIdentityException e
) {
2382 e
.printStackTrace();
2391 * Trust all keys of this identity without verification
2393 * @param name username of the identity
2395 public boolean trustIdentityAllKeys(String name
) {
2396 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2397 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2401 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2402 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2403 account
.getSignalProtocolStore()
2404 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2406 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2407 } catch (IOException
| UntrustedIdentityException e
) {
2408 e
.printStackTrace();
2416 public String
computeSafetyNumber(
2417 SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
2419 return Utils
.computeSafetyNumber(account
.getSelfAddress(),
2420 getIdentityKeyPair().getPublicKey(),
2425 void saveAccount() {
2429 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2430 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
)
2432 : Util
.canonicalizeNumber(identifier
, account
.getUsername());
2433 return resolveSignalServiceAddress(canonicalizedNumber
);
2436 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2437 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2439 return resolveSignalServiceAddress(address
);
2442 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2443 if (address
.matches(account
.getSelfAddress())) {
2444 return account
.getSelfAddress();
2447 return account
.getRecipientStore().resolveServiceAddress(address
);
2451 public void close() throws IOException
{
2452 if (messagePipe
!= null) {
2453 messagePipe
.shutdown();
2457 if (unidentifiedMessagePipe
!= null) {
2458 unidentifiedMessagePipe
.shutdown();
2459 unidentifiedMessagePipe
= null;
2465 public interface ReceiveMessageHandler
{
2467 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);