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