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
.storage
.SignalAccount
;
22 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
23 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
24 import org
.asamk
.signal
.storage
.groups
.GroupInfoV1
;
25 import org
.asamk
.signal
.storage
.groups
.GroupInfoV2
;
26 import org
.asamk
.signal
.storage
.profiles
.SignalProfile
;
27 import org
.asamk
.signal
.storage
.profiles
.SignalProfileEntry
;
28 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
29 import org
.asamk
.signal
.storage
.stickers
.Sticker
;
30 import org
.asamk
.signal
.util
.IOUtils
;
31 import org
.asamk
.signal
.util
.Util
;
32 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
33 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
34 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
35 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
36 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
37 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
38 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
39 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
40 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
41 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
42 import org
.signal
.libsignal
.metadata
.SelfSendException
;
43 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
44 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroup
;
45 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedMember
;
46 import org
.signal
.zkgroup
.InvalidInputException
;
47 import org
.signal
.zkgroup
.VerificationFailedException
;
48 import org
.signal
.zkgroup
.auth
.AuthCredentialResponse
;
49 import org
.signal
.zkgroup
.groups
.GroupMasterKey
;
50 import org
.signal
.zkgroup
.groups
.GroupSecretParams
;
51 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
52 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
53 import org
.whispersystems
.libsignal
.IdentityKey
;
54 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
55 import org
.whispersystems
.libsignal
.InvalidKeyException
;
56 import org
.whispersystems
.libsignal
.InvalidMessageException
;
57 import org
.whispersystems
.libsignal
.InvalidVersionException
;
58 import org
.whispersystems
.libsignal
.ecc
.Curve
;
59 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
60 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
61 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
62 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
63 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
64 import org
.whispersystems
.libsignal
.util
.Medium
;
65 import org
.whispersystems
.libsignal
.util
.Pair
;
66 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
67 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
68 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
69 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
70 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
71 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
72 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
73 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
74 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
75 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
76 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
77 import org
.whispersystems
.signalservice
.api
.groupsv2
.ClientZkOperations
;
78 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Api
;
79 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2AuthorizationString
;
80 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
81 import org
.whispersystems
.signalservice
.api
.groupsv2
.InvalidGroupStateException
;
82 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
83 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
84 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
85 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
86 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
87 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
88 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
89 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
90 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
91 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroupV2
;
92 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
93 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
94 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
95 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
96 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
97 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
98 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
99 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
100 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
101 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
102 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
103 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
104 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
105 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
106 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
107 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.StickerPackOperationMessage
;
108 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
109 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
110 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
111 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
112 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
113 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
114 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
115 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
116 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
117 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
118 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
119 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
120 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
121 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
122 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
123 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
124 import org
.whispersystems
.util
.Base64
;
126 import java
.io
.Closeable
;
128 import java
.io
.FileInputStream
;
129 import java
.io
.FileNotFoundException
;
130 import java
.io
.FileOutputStream
;
131 import java
.io
.IOException
;
132 import java
.io
.InputStream
;
133 import java
.io
.OutputStream
;
135 import java
.net
.URISyntaxException
;
136 import java
.net
.URLEncoder
;
137 import java
.nio
.charset
.StandardCharsets
;
138 import java
.nio
.file
.Files
;
139 import java
.nio
.file
.Paths
;
140 import java
.nio
.file
.StandardCopyOption
;
141 import java
.util
.ArrayList
;
142 import java
.util
.Arrays
;
143 import java
.util
.Collection
;
144 import java
.util
.Collections
;
145 import java
.util
.Date
;
146 import java
.util
.HashMap
;
147 import java
.util
.HashSet
;
148 import java
.util
.List
;
149 import java
.util
.Locale
;
150 import java
.util
.Objects
;
151 import java
.util
.Set
;
152 import java
.util
.UUID
;
153 import java
.util
.concurrent
.ExecutionException
;
154 import java
.util
.concurrent
.ExecutorService
;
155 import java
.util
.concurrent
.TimeUnit
;
156 import java
.util
.concurrent
.TimeoutException
;
157 import java
.util
.stream
.Collectors
;
158 import java
.util
.zip
.ZipEntry
;
159 import java
.util
.zip
.ZipFile
;
161 import static org
.asamk
.signal
.manager
.ServiceConfig
.capabilities
;
163 public class Manager
implements Closeable
{
165 private final SleepTimer timer
= new UptimeSleepTimer();
166 private final SignalServiceConfiguration serviceConfiguration
;
167 private final String userAgent
;
169 private final SignalAccount account
;
170 private final PathConfig pathConfig
;
171 private SignalServiceAccountManager accountManager
;
172 private GroupsV2Api groupsV2Api
;
173 private SignalServiceMessagePipe messagePipe
= null;
174 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
175 private final boolean discoverableByPhoneNumber
= true;
177 public Manager(SignalAccount account
, PathConfig pathConfig
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) {
178 this.account
= account
;
179 this.pathConfig
= pathConfig
;
180 this.serviceConfiguration
= serviceConfiguration
;
181 this.userAgent
= userAgent
;
182 this.accountManager
= createSignalServiceAccountManager();
183 this.groupsV2Api
= accountManager
.getGroupsV2Api();
185 this.account
.setResolver(this::resolveSignalServiceAddress
);
188 public String
getUsername() {
189 return account
.getUsername();
192 public SignalServiceAddress
getSelfAddress() {
193 return account
.getSelfAddress();
196 private SignalServiceAccountManager
createSignalServiceAccountManager() {
197 GroupsV2Operations groupsV2Operations
= capabilities
.isGv2()
198 ?
new GroupsV2Operations(ClientZkOperations
.create(serviceConfiguration
))
201 return new SignalServiceAccountManager(serviceConfiguration
,
202 new DynamicCredentialsProvider(account
.getUuid(), account
.getUsername(), account
.getPassword(), null, account
.getDeviceId()),
208 private IdentityKeyPair
getIdentityKeyPair() {
209 return account
.getSignalProtocolStore().getIdentityKeyPair();
212 public int getDeviceId() {
213 return account
.getDeviceId();
216 private String
getMessageCachePath() {
217 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
220 private String
getMessageCachePath(String sender
) {
221 if (sender
== null || sender
.isEmpty()) {
222 return getMessageCachePath();
225 return getMessageCachePath() + "/" + sender
.replace("/", "_");
228 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
229 String cachePath
= getMessageCachePath(sender
);
230 IOUtils
.createPrivateDirectories(cachePath
);
231 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
234 public static Manager
init(String username
, String settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) throws IOException
{
235 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
237 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
238 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
239 int registrationId
= KeyHelper
.generateRegistrationId(false);
241 ProfileKey profileKey
= KeyUtils
.createProfileKey();
242 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(), username
, identityKey
, registrationId
, profileKey
);
245 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
248 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
250 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
252 m
.migrateLegacyConfigs();
253 m
.updateAccountAttributes();
258 private void migrateLegacyConfigs() {
259 if (account
.getProfileKey() == null) {
260 // Old config file, creating new profile key
261 account
.setProfileKey(KeyUtils
.createProfileKey());
264 // Store profile keys only in profile store
265 for (ContactInfo contact
: account
.getContactStore().getContacts()) {
266 String profileKeyString
= contact
.profileKey
;
267 if (profileKeyString
== null) {
270 final ProfileKey profileKey
;
272 profileKey
= new ProfileKey(Base64
.decode(profileKeyString
));
273 } catch (InvalidInputException
| IOException e
) {
276 contact
.profileKey
= null;
277 account
.getProfileStore().storeProfileKey(contact
.getAddress(), profileKey
);
281 public void checkAccountState() throws IOException
{
282 if (account
.isRegistered()) {
283 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
287 if (account
.getUuid() == null) {
288 account
.setUuid(accountManager
.getOwnUuid());
294 public boolean isRegistered() {
295 return account
.isRegistered();
298 public void register(boolean voiceVerification
, String captcha
) throws IOException
{
299 account
.setPassword(KeyUtils
.createPassword());
301 // Resetting UUID, because registering doesn't work otherwise
302 account
.setUuid(null);
303 accountManager
= createSignalServiceAccountManager();
304 this.groupsV2Api
= accountManager
.getGroupsV2Api();
306 if (voiceVerification
) {
307 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.fromNullable(captcha
), Optional
.absent());
309 accountManager
.requestSmsVerificationCode(false, Optional
.fromNullable(captcha
), Optional
.absent());
312 account
.setRegistered(false);
316 public void updateAccountAttributes() throws IOException
{
317 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities
, discoverableByPhoneNumber
);
320 public void setProfile(String name
, File avatar
) throws IOException
{
321 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
322 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
326 public void unregister() throws IOException
{
327 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
328 // If this is the master device, other users can't send messages to this number anymore.
329 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
330 accountManager
.setGcmId(Optional
.absent());
332 account
.setRegistered(false);
336 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
337 List
<DeviceInfo
> devices
= accountManager
.getDevices();
338 account
.setMultiDevice(devices
.size() > 1);
343 public void removeLinkedDevices(int deviceId
) throws IOException
{
344 accountManager
.removeDevice(deviceId
);
345 List
<DeviceInfo
> devices
= accountManager
.getDevices();
346 account
.setMultiDevice(devices
.size() > 1);
350 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
351 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
353 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
356 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
357 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
358 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
360 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
361 account
.setMultiDevice(true);
365 private List
<PreKeyRecord
> generatePreKeys() {
366 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
368 final int offset
= account
.getPreKeyIdOffset();
369 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
370 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
371 ECKeyPair keyPair
= Curve
.generateKeyPair();
372 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
377 account
.addPreKeys(records
);
383 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
385 ECKeyPair keyPair
= Curve
.generateKeyPair();
386 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
387 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
389 account
.addSignedPreKey(record);
393 } catch (InvalidKeyException e
) {
394 throw new AssertionError(e
);
398 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
399 verificationCode
= verificationCode
.replace("-", "");
400 account
.setSignalingKey(KeyUtils
.createSignalingKey());
401 // TODO make unrestricted unidentified access configurable
402 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, capabilities
, discoverableByPhoneNumber
);
404 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
405 // TODO response.isStorageCapable()
406 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
407 account
.setRegistered(true);
408 account
.setUuid(uuid
);
409 account
.setRegistrationLockPin(pin
);
410 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
416 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
417 if (pin
.isPresent()) {
418 account
.setRegistrationLockPin(pin
.get());
419 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
421 account
.setRegistrationLockPin(null);
422 accountManager
.removeRegistrationLockV1();
427 void refreshPreKeys() throws IOException
{
428 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
429 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
430 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
432 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
435 private SignalServiceMessageReceiver
getMessageReceiver() {
436 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2()
437 ? ClientZkOperations
.create(serviceConfiguration
).getProfileOperations()
439 return new SignalServiceMessageReceiver(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), userAgent
, null, timer
, clientZkProfileOperations
);
442 private SignalServiceMessageSender
getMessageSender() {
443 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2()
444 ? ClientZkOperations
.create(serviceConfiguration
).getProfileOperations()
446 final ExecutorService executor
= null;
447 return new SignalServiceMessageSender(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
448 account
.getDeviceId(), account
.getSignalProtocolStore(), userAgent
, account
.isMultiDevice(), Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent(), clientZkProfileOperations
, executor
, ServiceConfig
.MAX_ENVELOPE_SIZE
);
451 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
452 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
457 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
458 } catch (IOException
| InterruptedException
| ExecutionException
| TimeoutException ignored
) {
462 SignalServiceMessageReceiver receiver
= getMessageReceiver();
464 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
465 } catch (InterruptedException
| ExecutionException
| TimeoutException e
) {
466 throw new IOException("Failed to retrieve profile", e
);
470 private SignalProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
, ProfileKey profileKey
) throws IOException
{
471 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfile(address
);
472 long now
= new Date().getTime();
473 // Profiles are cache for 24h before retrieving them again
474 if (profileEntry
== null || profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000) {
475 SignalProfile profile
= retrieveRecipientProfile(address
, unidentifiedAccess
, profileKey
);
476 account
.getProfileStore().updateProfile(address
, profileKey
, now
, profile
);
479 return profileEntry
.getProfile();
482 private SignalProfile
retrieveRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
, ProfileKey profileKey
) throws IOException
{
483 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
, unidentifiedAccess
);
485 File avatarFile
= null;
487 avatarFile
= encryptedProfile
.getAvatar() == null ?
null : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
488 } catch (Throwable e
) {
489 System
.err
.println("Failed to retrieve profile avatar, ignoring: " + e
.getMessage());
492 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
494 return new SignalProfile(
495 encryptedProfile
.getIdentityKey(),
496 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
498 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
499 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
500 encryptedProfile
.getCapabilities());
501 } catch (InvalidCiphertextException e
) {
506 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
507 File file
= getGroupAvatarFile(groupId
);
508 if (!file
.exists()) {
509 return Optional
.absent();
512 return Optional
.of(Utils
.createAttachment(file
));
515 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
516 File file
= getContactAvatarFile(number
);
517 if (!file
.exists()) {
518 return Optional
.absent();
521 return Optional
.of(Utils
.createAttachment(file
));
524 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
525 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
527 throw new GroupNotFoundException(groupId
);
529 if (!g
.isMember(account
.getSelfAddress())) {
530 throw new NotAGroupMemberException(groupId
, g
.getTitle());
535 public List
<GroupInfo
> getGroups() {
536 return account
.getGroupStore().getGroups();
539 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
541 List
<String
> attachments
,
544 throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
545 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
546 if (attachments
!= null) {
547 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
550 final GroupInfo g
= getGroupForSending(groupId
);
552 setGroupContext(messageBuilder
, g
);
553 messageBuilder
.withExpiration(g
.getMessageExpirationTime());
555 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
558 private void setGroupContext(final SignalServiceDataMessage
.Builder messageBuilder
, final GroupInfo groupInfo
) {
559 if (groupInfo
instanceof GroupInfoV1
) {
560 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
561 .withId(groupInfo
.groupId
)
563 messageBuilder
.asGroupMessage(group
);
565 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) groupInfo
;
566 SignalServiceGroupV2 group
= SignalServiceGroupV2
.newBuilder(groupInfoV2
.getMasterKey())
567 .withRevision(groupInfoV2
.getGroup() == null ?
0 : groupInfoV2
.getGroup().getRevision())
569 messageBuilder
.asGroupMessage(group
);
573 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
574 long targetSentTimestamp
, byte[] groupId
)
575 throws IOException
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
576 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
577 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
578 .withReaction(reaction
);
579 final GroupInfo g
= getGroupForSending(groupId
);
580 setGroupContext(messageBuilder
, g
);
581 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
584 public Pair
<Long
, List
<SendMessageResult
>> sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, NotAGroupMemberException
{
585 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
589 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
590 .asGroupMessage(group
);
592 final GroupInfo g
= getGroupForSending(groupId
);
593 if (g
instanceof GroupInfoV1
) {
594 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
595 groupInfoV1
.removeMember(account
.getSelfAddress());
596 account
.getGroupStore().updateGroup(groupInfoV1
);
598 throw new RuntimeException("TODO Not implemented!");
601 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
604 private Pair
<byte[], List
<SendMessageResult
>> sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
606 if (groupId
== null) {
608 g
= new GroupInfoV1(KeyUtils
.createGroupId());
609 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
611 GroupInfo group
= getGroupForSending(groupId
);
612 if (!(group
instanceof GroupInfoV1
)) {
613 throw new RuntimeException("TODO Not implemented!");
615 g
= (GroupInfoV1
) group
;
622 if (members
!= null) {
623 final Set
<String
> newE164Members
= new HashSet
<>();
624 for (SignalServiceAddress member
: members
) {
625 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
628 newE164Members
.add(member
.getNumber().get());
631 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
632 if (contacts
.size() != newE164Members
.size()) {
633 // Some of the new members are not registered on Signal
634 for (ContactTokenDetails contact
: contacts
) {
635 newE164Members
.remove(contact
.getNumber());
637 throw new IOException("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
640 g
.addMembers(members
);
643 if (avatarFile
!= null) {
644 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
645 File aFile
= getGroupAvatarFile(g
.groupId
);
646 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
649 account
.getGroupStore().updateGroup(g
);
651 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
653 final Pair
<Long
, List
<SendMessageResult
>> result
= sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
654 return new Pair
<>(g
.groupId
, result
.second());
657 Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
659 GroupInfo group
= getGroupForSending(groupId
);
660 if (!(group
instanceof GroupInfoV1
)) {
661 throw new RuntimeException("TODO Not implemented!");
663 g
= (GroupInfoV1
) group
;
665 if (!g
.isMember(recipient
)) {
666 throw new NotAGroupMemberException(groupId
, g
.name
);
669 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
671 // Send group message only to the recipient who requested it
672 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
675 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV1 g
) throws AttachmentInvalidException
{
676 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
679 .withMembers(new ArrayList
<>(g
.getMembers()));
681 File aFile
= getGroupAvatarFile(g
.groupId
);
682 if (aFile
.exists()) {
684 group
.withAvatar(Utils
.createAttachment(aFile
));
685 } catch (IOException e
) {
686 throw new AttachmentInvalidException(aFile
.toString(), e
);
690 return SignalServiceDataMessage
.newBuilder()
691 .asGroupMessage(group
.build())
692 .withExpiration(g
.messageExpirationTime
);
695 Pair
<Long
, List
<SendMessageResult
>> sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
{
696 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
699 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
700 .asGroupMessage(group
.build());
702 // Send group info request message to the recipient who sent us a message with this groupId
703 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
706 void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
707 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
708 Collections
.singletonList(messageId
),
709 System
.currentTimeMillis());
711 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
714 public Pair
<Long
, List
<SendMessageResult
>> sendMessage(String messageText
, List
<String
> attachments
,
715 List
<String
> recipients
)
716 throws IOException
, AttachmentInvalidException
, InvalidNumberException
{
717 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
718 if (attachments
!= null) {
719 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
721 // Upload attachments here, so we only upload once even for multiple recipients
722 SignalServiceMessageSender messageSender
= getMessageSender();
723 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
724 for (SignalServiceAttachment attachment
: attachmentStreams
) {
725 if (attachment
.isStream()) {
726 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
727 } else if (attachment
.isPointer()) {
728 attachmentPointers
.add(attachment
.asPointer());
732 messageBuilder
.withAttachments(attachmentPointers
);
734 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
737 public Pair
<Long
, List
<SendMessageResult
>> sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
738 long targetSentTimestamp
, List
<String
> recipients
)
739 throws IOException
, InvalidNumberException
{
740 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
741 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
742 .withReaction(reaction
);
743 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
746 public Pair
<Long
, List
<SendMessageResult
>> sendEndSessionMessage(List
<String
> recipients
) throws IOException
, InvalidNumberException
{
747 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
748 .asEndSessionMessage();
750 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
752 return sendMessage(messageBuilder
, signalServiceAddresses
);
753 } catch (Exception e
) {
754 for (SignalServiceAddress address
: signalServiceAddresses
) {
755 handleEndSession(address
);
762 public String
getContactName(String number
) throws InvalidNumberException
{
763 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
764 if (contact
== null) {
771 public void setContactName(String number
, String name
) throws InvalidNumberException
{
772 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
773 ContactInfo contact
= account
.getContactStore().getContact(address
);
774 if (contact
== null) {
775 contact
= new ContactInfo(address
);
778 account
.getContactStore().updateContact(contact
);
782 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
783 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
786 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
787 ContactInfo contact
= account
.getContactStore().getContact(address
);
788 if (contact
== null) {
789 contact
= new ContactInfo(address
);
791 contact
.blocked
= blocked
;
792 account
.getContactStore().updateContact(contact
);
796 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
797 GroupInfo group
= getGroup(groupId
);
799 throw new GroupNotFoundException(groupId
);
802 group
.setBlocked(blocked
);
803 account
.getGroupStore().updateGroup(group
);
807 public Pair
<byte[], List
<SendMessageResult
>> updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
808 if (groupId
.length
== 0) {
811 if (name
.isEmpty()) {
814 if (members
.isEmpty()) {
817 if (avatar
.isEmpty()) {
820 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
824 * Change the expiration timer for a contact
826 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
827 ContactInfo contact
= account
.getContactStore().getContact(address
);
828 contact
.messageExpirationTime
= messageExpirationTimer
;
829 account
.getContactStore().updateContact(contact
);
830 sendExpirationTimerUpdate(address
);
834 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
835 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
836 .asExpirationUpdate();
837 sendMessage(messageBuilder
, Collections
.singleton(address
));
841 * Change the expiration timer for a contact
843 public void setExpirationTimer(String number
, int messageExpirationTimer
) throws IOException
, InvalidNumberException
{
844 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
845 setExpirationTimer(address
, messageExpirationTimer
);
849 * Change the expiration timer for a group
851 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
852 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
853 if (g
instanceof GroupInfoV1
) {
854 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
855 groupInfoV1
.messageExpirationTime
= messageExpirationTimer
;
856 account
.getGroupStore().updateGroup(groupInfoV1
);
858 throw new RuntimeException("TODO Not implemented!");
863 * Upload the sticker pack from path.
865 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
866 * @return if successful, returns the URL to install the sticker pack in the signal app
868 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
869 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
871 SignalServiceMessageSender messageSender
= getMessageSender();
873 byte[] packKey
= KeyUtils
.createStickerUploadKey();
874 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
876 Sticker sticker
= new Sticker(Hex
.fromStringCondensed(packId
), packKey
);
877 account
.getStickerStore().updateSticker(sticker
);
881 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, StandardCharsets
.UTF_8
) + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), StandardCharsets
.UTF_8
))
883 } catch (URISyntaxException e
) {
884 throw new AssertionError(e
);
888 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
890 String rootPath
= null;
892 final File file
= new File(path
);
893 if (file
.getName().endsWith(".zip")) {
894 zip
= new ZipFile(file
);
895 } else if (file
.getName().equals("manifest.json")) {
896 rootPath
= file
.getParent();
898 throw new StickerPackInvalidException("Could not find manifest.json");
901 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
903 if (pack
.stickers
== null) {
904 throw new StickerPackInvalidException("Must set a 'stickers' field.");
907 if (pack
.stickers
.isEmpty()) {
908 throw new StickerPackInvalidException("Must include stickers.");
911 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
912 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
913 if (sticker
.file
== null) {
914 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
917 Pair
<InputStream
, Long
> data
;
919 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
920 } catch (IOException ignored
) {
921 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
924 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
925 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""), contentType
);
926 stickers
.add(stickerInfo
);
929 StickerInfo cover
= null;
930 if (pack
.cover
!= null) {
931 if (pack
.cover
.file
== null) {
932 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
935 Pair
<InputStream
, Long
> data
;
937 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
938 } catch (IOException ignored
) {
939 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
942 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
943 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""), contentType
);
946 return new SignalServiceStickerManifestUpload(
953 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
954 InputStream inputStream
;
956 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
958 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
960 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
963 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
965 final ZipEntry entry
= zip
.getEntry(subfile
);
966 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
968 final File file
= new File(rootPath
, subfile
);
969 return new Pair
<>(new FileInputStream(file
), file
.length());
973 void requestSyncGroups() throws IOException
{
974 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
975 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
977 sendSyncMessage(message
);
978 } catch (UntrustedIdentityException e
) {
983 void requestSyncContacts() throws IOException
{
984 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
985 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
987 sendSyncMessage(message
);
988 } catch (UntrustedIdentityException e
) {
993 void requestSyncBlocked() throws IOException
{
994 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
995 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
997 sendSyncMessage(message
);
998 } catch (UntrustedIdentityException e
) {
1003 void requestSyncConfiguration() throws IOException
{
1004 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
1005 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1007 sendSyncMessage(message
);
1008 } catch (UntrustedIdentityException e
) {
1009 e
.printStackTrace();
1013 private byte[] getSenderCertificate() {
1014 // TODO support UUID capable sender certificates
1015 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
1018 certificate
= accountManager
.getSenderCertificate();
1019 } catch (IOException e
) {
1020 System
.err
.println("Failed to get sender certificate: " + e
);
1023 // TODO cache for a day
1027 private byte[] getSelfUnidentifiedAccessKey() {
1028 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
1031 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
1032 ProfileKey theirProfileKey
= account
.getProfileStore().getProfileKey(recipient
);
1033 if (theirProfileKey
== null) {
1036 SignalProfile targetProfile
;
1038 targetProfile
= getRecipientProfile(recipient
, Optional
.absent(), theirProfileKey
);
1039 } catch (IOException e
) {
1040 System
.err
.println("Failed to get recipient profile: " + e
);
1044 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1048 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1049 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1052 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1055 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1056 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1057 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1059 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1060 return Optional
.absent();
1064 return Optional
.of(new UnidentifiedAccessPair(
1065 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1066 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1068 } catch (InvalidCertificateException e
) {
1069 return Optional
.absent();
1073 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1074 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1075 for (SignalServiceAddress recipient
: recipients
) {
1076 result
.add(getAccessFor(recipient
));
1081 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1082 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1083 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1084 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1086 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1087 return Optional
.absent();
1091 return Optional
.of(new UnidentifiedAccessPair(
1092 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1093 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1095 } catch (InvalidCertificateException e
) {
1096 return Optional
.absent();
1100 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1101 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1103 if (unidentifiedAccess
.isPresent()) {
1104 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1107 return Optional
.absent();
1110 private void sendSyncMessage(SignalServiceSyncMessage message
)
1111 throws IOException
, UntrustedIdentityException
{
1112 SignalServiceMessageSender messageSender
= getMessageSender();
1114 messageSender
.sendMessage(message
, getAccessForSync());
1115 } catch (UntrustedIdentityException e
) {
1116 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1121 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1122 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1124 for (String number
: numbers
) {
1125 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1127 return signalServiceAddresses
;
1130 private Pair
<Long
, List
<SendMessageResult
>> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1131 throws IOException
{
1132 recipients
= recipients
.stream().map(this::resolveSignalServiceAddress
).collect(Collectors
.toSet());
1133 final long timestamp
= System
.currentTimeMillis();
1134 messageBuilder
.withTimestamp(timestamp
);
1135 if (messagePipe
== null) {
1136 messagePipe
= getMessageReceiver().createMessagePipe();
1138 if (unidentifiedMessagePipe
== null) {
1139 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1141 SignalServiceDataMessage message
= null;
1143 message
= messageBuilder
.build();
1144 if (message
.getGroupContext().isPresent()) {
1146 SignalServiceMessageSender messageSender
= getMessageSender();
1147 final boolean isRecipientUpdate
= false;
1148 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1149 for (SendMessageResult r
: result
) {
1150 if (r
.getIdentityFailure() != null) {
1151 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1154 return new Pair
<>(timestamp
, result
);
1155 } catch (UntrustedIdentityException e
) {
1156 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1157 return new Pair
<>(timestamp
, Collections
.emptyList());
1160 // Send to all individually, so sync messages are sent correctly
1161 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1162 for (SignalServiceAddress address
: recipients
) {
1163 ContactInfo contact
= account
.getContactStore().getContact(address
);
1164 if (contact
!= null) {
1165 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1166 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1168 messageBuilder
.withExpiration(0);
1169 messageBuilder
.withProfileKey(null);
1171 message
= messageBuilder
.build();
1172 if (address
.matches(account
.getSelfAddress())) {
1173 results
.add(sendSelfMessage(message
));
1175 results
.add(sendMessage(address
, message
));
1178 return new Pair
<>(timestamp
, results
);
1181 if (message
!= null && message
.isEndSession()) {
1182 for (SignalServiceAddress recipient
: recipients
) {
1183 handleEndSession(recipient
);
1190 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1191 SignalServiceMessageSender messageSender
= getMessageSender();
1193 SignalServiceAddress recipient
= account
.getSelfAddress();
1195 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1196 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1197 message
.getTimestamp(),
1199 message
.getExpiresInSeconds(),
1200 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1202 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1205 long startTime
= System
.currentTimeMillis();
1206 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1207 return SendMessageResult
.success(recipient
, unidentifiedAccess
.isPresent(), false, System
.currentTimeMillis() - startTime
);
1208 } catch (UntrustedIdentityException e
) {
1209 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1210 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1214 private SendMessageResult
sendMessage(SignalServiceAddress address
, SignalServiceDataMessage message
) throws IOException
{
1215 SignalServiceMessageSender messageSender
= getMessageSender();
1218 return messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1219 } catch (UntrustedIdentityException e
) {
1220 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1221 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1225 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1226 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1228 return cipher
.decrypt(envelope
);
1229 } catch (ProtocolUntrustedIdentityException e
) {
1230 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1231 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1232 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1233 throw identityException
;
1235 throw new AssertionError(e
);
1239 private void handleEndSession(SignalServiceAddress source
) {
1240 account
.getSignalProtocolStore().deleteAllSessions(source
);
1243 private static int currentTimeDays() {
1244 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
1247 private GroupsV2AuthorizationString
getGroupAuthForToday(final GroupSecretParams groupSecretParams
) throws IOException
, VerificationFailedException
{
1248 final int today
= currentTimeDays();
1249 // Returns credentials for the next 7 days
1250 final HashMap
<Integer
, AuthCredentialResponse
> credentials
= groupsV2Api
.getCredentials(today
);
1251 // TODO cache credentials until they expire
1252 AuthCredentialResponse authCredentialResponse
= credentials
.get(today
);
1253 return groupsV2Api
.getGroupsV2AuthorizationString(account
.getUuid(), today
, groupSecretParams
, authCredentialResponse
);
1256 private List
<HandleAction
> handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1257 List
<HandleAction
> actions
= new ArrayList
<>();
1258 if (message
.getGroupContext().isPresent()) {
1259 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1260 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1261 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1262 if (group
== null || group
instanceof GroupInfoV1
) {
1263 GroupInfoV1 groupV1
= (GroupInfoV1
) group
;
1264 switch (groupInfo
.getType()) {
1266 if (groupV1
== null) {
1267 groupV1
= new GroupInfoV1(groupInfo
.getGroupId());
1270 if (groupInfo
.getAvatar().isPresent()) {
1271 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1272 if (avatar
.isPointer()) {
1274 retrieveGroupAvatarAttachment(avatar
.asPointer(), groupV1
.groupId
);
1275 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1276 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1281 if (groupInfo
.getName().isPresent()) {
1282 groupV1
.name
= groupInfo
.getName().get();
1285 if (groupInfo
.getMembers().isPresent()) {
1286 groupV1
.addMembers(groupInfo
.getMembers().get()
1288 .map(this::resolveSignalServiceAddress
)
1289 .collect(Collectors
.toSet()));
1292 account
.getGroupStore().updateGroup(groupV1
);
1296 if (groupV1
== null && !isSync
) {
1297 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1301 if (groupV1
!= null) {
1302 groupV1
.removeMember(source
);
1303 account
.getGroupStore().updateGroup(groupV1
);
1308 if (groupV1
!= null && !isSync
) {
1309 actions
.add(new SendGroupUpdateAction(source
, groupV1
.groupId
));
1314 System
.err
.println("Received a group v1 message for a v2 group: " + group
.getTitle());
1317 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1318 final SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1319 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1321 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
1323 byte[] groupId
= groupSecretParams
.getPublicParams().getGroupIdentifier().serialize();
1324 GroupInfo groupInfo
= account
.getGroupStore().getGroup(groupId
);
1325 if (groupInfo
instanceof GroupInfoV1
) {
1326 // TODO upgrade group
1327 } else if (groupInfo
== null || groupInfo
instanceof GroupInfoV2
) {
1328 GroupInfoV2 groupInfoV2
= groupInfo
== null
1329 ?
new GroupInfoV2(groupId
, groupMasterKey
)
1330 : (GroupInfoV2
) groupInfo
;
1332 if (groupInfoV2
.getGroup() == null || groupInfoV2
.getGroup().getRevision() < groupContext
.getRevision()) {
1333 // TODO check if revision is only 1 behind and a signedGroupChange is available
1335 final GroupsV2AuthorizationString groupsV2AuthorizationString
= getGroupAuthForToday(groupSecretParams
);
1336 final DecryptedGroup group
= groupsV2Api
.getGroup(groupSecretParams
, groupsV2AuthorizationString
);
1337 groupInfoV2
.setGroup(group
);
1338 for (DecryptedMember member
: group
.getMembersList()) {
1339 final SignalServiceAddress address
= resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil
.parseOrThrow(member
.getUuid().toByteArray()), null));
1341 account
.getProfileStore().storeProfileKey(address
, new ProfileKey(member
.getProfileKey().toByteArray()));
1342 } catch (InvalidInputException ignored
) {
1345 } catch (IOException
| VerificationFailedException
| InvalidGroupStateException e
) {
1346 System
.err
.println("Failed to retrieve Group V2 info, ignoring ...");
1348 account
.getGroupStore().updateGroup(groupInfoV2
);
1353 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1354 if (message
.isEndSession()) {
1355 handleEndSession(conversationPartnerAddress
);
1357 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1358 if (message
.getGroupContext().isPresent()) {
1359 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1360 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1361 GroupInfoV1 group
= account
.getGroupStore().getOrCreateGroupV1(groupInfo
.getGroupId());
1362 if (group
!= null) {
1363 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1364 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1365 account
.getGroupStore().updateGroup(group
);
1368 } else if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1369 // disappearing message timer already stored in the DecryptedGroup
1372 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1373 if (contact
== null) {
1374 contact
= new ContactInfo(conversationPartnerAddress
);
1376 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1377 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1378 account
.getContactStore().updateContact(contact
);
1382 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1383 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1384 if (attachment
.isPointer()) {
1386 retrieveAttachment(attachment
.asPointer());
1387 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1388 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1393 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1394 final ProfileKey profileKey
;
1396 profileKey
= new ProfileKey(message
.getProfileKey().get());
1397 } catch (InvalidInputException e
) {
1398 throw new AssertionError(e
);
1400 if (source
.matches(account
.getSelfAddress())) {
1401 this.account
.setProfileKey(profileKey
);
1403 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1405 if (message
.getPreviews().isPresent()) {
1406 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1407 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1408 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1409 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1411 retrieveAttachment(attachment
);
1412 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1413 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1418 if (message
.getSticker().isPresent()) {
1419 final SignalServiceDataMessage
.Sticker messageSticker
= message
.getSticker().get();
1420 Sticker sticker
= account
.getStickerStore().getSticker(messageSticker
.getPackId());
1421 if (sticker
== null) {
1422 sticker
= new Sticker(messageSticker
.getPackId(), messageSticker
.getPackKey());
1423 account
.getStickerStore().updateSticker(sticker
);
1429 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1430 final File cachePath
= new File(getMessageCachePath());
1431 if (!cachePath
.exists()) {
1434 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1435 if (!dir
.isDirectory()) {
1436 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1440 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1441 if (!fileEntry
.isFile()) {
1444 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1446 // Try to delete directory if empty
1451 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1452 SignalServiceEnvelope envelope
;
1454 envelope
= Utils
.loadEnvelope(fileEntry
);
1455 if (envelope
== null) {
1458 } catch (IOException e
) {
1459 e
.printStackTrace();
1462 SignalServiceContent content
= null;
1463 if (!envelope
.isReceipt()) {
1465 content
= decryptMessage(envelope
);
1466 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1468 } catch (Exception er
) {
1469 // All other errors are not recoverable, so delete the cached message
1471 Files
.delete(fileEntry
.toPath());
1472 } catch (IOException e
) {
1473 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1477 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1478 for (HandleAction action
: actions
) {
1480 action
.execute(this);
1481 } catch (Throwable e
) {
1482 e
.printStackTrace();
1487 handler
.handleMessage(envelope
, content
, null);
1489 Files
.delete(fileEntry
.toPath());
1490 } catch (IOException e
) {
1491 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1495 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1496 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1497 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1499 Set
<HandleAction
> queuedActions
= null;
1501 if (messagePipe
== null) {
1502 messagePipe
= messageReceiver
.createMessagePipe();
1505 boolean hasCaughtUpWithOldMessages
= false;
1508 SignalServiceEnvelope envelope
;
1509 SignalServiceContent content
= null;
1510 Exception exception
= null;
1511 final long now
= new Date().getTime();
1513 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1514 // store message on disk, before acknowledging receipt to the server
1516 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1517 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1518 Utils
.storeEnvelope(envelope1
, cacheFile
);
1519 } catch (IOException e
) {
1520 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1523 if (result
.isPresent()) {
1524 envelope
= result
.get();
1526 // Received indicator that server queue is empty
1527 hasCaughtUpWithOldMessages
= true;
1529 if (queuedActions
!= null) {
1530 for (HandleAction action
: queuedActions
) {
1532 action
.execute(this);
1533 } catch (Throwable e
) {
1534 e
.printStackTrace();
1538 queuedActions
.clear();
1539 queuedActions
= null;
1542 // Continue to wait another timeout for new messages
1545 } catch (TimeoutException e
) {
1546 if (returnOnTimeout
)
1549 } catch (InvalidVersionException e
) {
1550 System
.err
.println("Ignoring error: " + e
.getMessage());
1554 if (envelope
.hasSource()) {
1555 // Store uuid if we don't have it already
1556 SignalServiceAddress source
= envelope
.getSourceAddress();
1557 resolveSignalServiceAddress(source
);
1559 if (!envelope
.isReceipt()) {
1561 content
= decryptMessage(envelope
);
1562 } catch (Exception e
) {
1565 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1566 if (hasCaughtUpWithOldMessages
) {
1567 for (HandleAction action
: actions
) {
1569 action
.execute(this);
1570 } catch (Throwable e
) {
1571 e
.printStackTrace();
1575 if (queuedActions
== null) {
1576 queuedActions
= new HashSet
<>();
1578 queuedActions
.addAll(actions
);
1582 if (!isMessageBlocked(envelope
, content
)) {
1583 handler
.handleMessage(envelope
, content
, exception
);
1585 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1586 File cacheFile
= null;
1588 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1589 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1590 Files
.delete(cacheFile
.toPath());
1591 // Try to delete directory if empty
1592 new File(getMessageCachePath()).delete();
1593 } catch (IOException e
) {
1594 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1600 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1601 SignalServiceAddress source
;
1602 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1603 source
= envelope
.getSourceAddress();
1604 } else if (content
!= null) {
1605 source
= content
.getSender();
1609 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1610 if (sourceContact
!= null && sourceContact
.blocked
) {
1614 if (content
!= null && content
.getDataMessage().isPresent()) {
1615 SignalServiceDataMessage message
= content
.getDataMessage().get();
1616 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1617 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1618 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1619 return groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.isBlocked();
1625 private List
<HandleAction
> handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1626 List
<HandleAction
> actions
= new ArrayList
<>();
1627 if (content
!= null) {
1628 SignalServiceAddress sender
;
1629 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1630 sender
= envelope
.getSourceAddress();
1632 sender
= content
.getSender();
1634 // Store uuid if we don't have it already
1635 resolveSignalServiceAddress(sender
);
1637 if (content
.getDataMessage().isPresent()) {
1638 SignalServiceDataMessage message
= content
.getDataMessage().get();
1640 if (content
.isNeedsReceipt()) {
1641 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1644 actions
.addAll(handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
));
1646 if (content
.getSyncMessage().isPresent()) {
1647 account
.setMultiDevice(true);
1648 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1649 if (syncMessage
.getSent().isPresent()) {
1650 SentTranscriptMessage message
= syncMessage
.getSent().get();
1651 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
));
1653 if (syncMessage
.getRequest().isPresent()) {
1654 RequestMessage rm
= syncMessage
.getRequest().get();
1655 if (rm
.isContactsRequest()) {
1656 actions
.add(SendSyncContactsAction
.create());
1658 if (rm
.isGroupsRequest()) {
1659 actions
.add(SendSyncGroupsAction
.create());
1661 if (rm
.isBlockedListRequest()) {
1662 actions
.add(SendSyncBlockedListAction
.create());
1664 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
1666 if (syncMessage
.getGroups().isPresent()) {
1667 File tmpFile
= null;
1669 tmpFile
= IOUtils
.createTempFile();
1670 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1671 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1673 while ((g
= s
.read()) != null) {
1674 GroupInfoV1 syncGroup
= account
.getGroupStore().getOrCreateGroupV1(g
.getId());
1675 if (syncGroup
!= null) {
1676 if (g
.getName().isPresent()) {
1677 syncGroup
.name
= g
.getName().get();
1679 syncGroup
.addMembers(g
.getMembers()
1681 .map(this::resolveSignalServiceAddress
)
1682 .collect(Collectors
.toSet()));
1683 if (!g
.isActive()) {
1684 syncGroup
.removeMember(account
.getSelfAddress());
1686 // Add ourself to the member set as it's marked as active
1687 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1689 syncGroup
.blocked
= g
.isBlocked();
1690 if (g
.getColor().isPresent()) {
1691 syncGroup
.color
= g
.getColor().get();
1694 if (g
.getAvatar().isPresent()) {
1695 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1697 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1698 syncGroup
.archived
= g
.isArchived();
1699 account
.getGroupStore().updateGroup(syncGroup
);
1703 } catch (Exception e
) {
1704 e
.printStackTrace();
1706 if (tmpFile
!= null) {
1708 Files
.delete(tmpFile
.toPath());
1709 } catch (IOException e
) {
1710 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1715 if (syncMessage
.getBlockedList().isPresent()) {
1716 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1717 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1718 setContactBlocked(resolveSignalServiceAddress(address
), true);
1720 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1722 setGroupBlocked(groupId
, true);
1723 } catch (GroupNotFoundException e
) {
1724 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1728 if (syncMessage
.getContacts().isPresent()) {
1729 File tmpFile
= null;
1731 tmpFile
= IOUtils
.createTempFile();
1732 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1733 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1734 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1735 if (contactsMessage
.isComplete()) {
1736 account
.getContactStore().clear();
1739 while ((c
= s
.read()) != null) {
1740 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1741 account
.setProfileKey(c
.getProfileKey().get());
1743 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1744 ContactInfo contact
= account
.getContactStore().getContact(address
);
1745 if (contact
== null) {
1746 contact
= new ContactInfo(address
);
1748 if (c
.getName().isPresent()) {
1749 contact
.name
= c
.getName().get();
1751 if (c
.getColor().isPresent()) {
1752 contact
.color
= c
.getColor().get();
1754 if (c
.getProfileKey().isPresent()) {
1755 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
1757 if (c
.getVerified().isPresent()) {
1758 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1759 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1761 if (c
.getExpirationTimer().isPresent()) {
1762 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1764 contact
.blocked
= c
.isBlocked();
1765 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1766 contact
.archived
= c
.isArchived();
1767 account
.getContactStore().updateContact(contact
);
1769 if (c
.getAvatar().isPresent()) {
1770 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1774 } catch (Exception e
) {
1775 e
.printStackTrace();
1777 if (tmpFile
!= null) {
1779 Files
.delete(tmpFile
.toPath());
1780 } catch (IOException e
) {
1781 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1786 if (syncMessage
.getVerified().isPresent()) {
1787 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1788 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1790 if (syncMessage
.getStickerPackOperations().isPresent()) {
1791 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations().get();
1792 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
1793 if (!m
.getPackId().isPresent()) {
1796 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
1797 if (sticker
== null) {
1798 if (!m
.getPackKey().isPresent()) {
1801 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
1803 sticker
.setInstalled(!m
.getType().isPresent() || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
1804 account
.getStickerStore().updateSticker(sticker
);
1807 if (syncMessage
.getConfiguration().isPresent()) {
1815 private File
getContactAvatarFile(String number
) {
1816 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1819 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1820 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1821 if (attachment
.isPointer()) {
1822 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1823 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1825 SignalServiceAttachmentStream stream
= attachment
.asStream();
1826 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1830 private File
getGroupAvatarFile(byte[] groupId
) {
1831 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1834 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1835 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1836 if (attachment
.isPointer()) {
1837 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1838 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1840 SignalServiceAttachmentStream stream
= attachment
.asStream();
1841 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1845 private File
getProfileAvatarFile(SignalServiceAddress address
) {
1846 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
1849 private File
retrieveProfileAvatar(SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
) throws IOException
{
1850 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1851 SignalServiceMessageReceiver receiver
= getMessageReceiver();
1852 File outputFile
= getProfileAvatarFile(address
);
1854 File tmpFile
= IOUtils
.createTempFile();
1855 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
, tmpFile
, profileKey
, ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
1856 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
1857 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
1860 Files
.delete(tmpFile
.toPath());
1861 } catch (IOException e
) {
1862 System
.err
.println("Failed to delete received avatar temp file “" + tmpFile
+ "”: " + e
.getMessage());
1868 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1869 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1872 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1873 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1874 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1877 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1878 if (storePreview
&& pointer
.getPreview().isPresent()) {
1879 File previewFile
= new File(outputFile
+ ".preview");
1880 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1881 byte[] preview
= pointer
.getPreview().get();
1882 output
.write(preview
, 0, preview
.length
);
1883 } catch (FileNotFoundException e
) {
1884 e
.printStackTrace();
1889 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1891 File tmpFile
= IOUtils
.createTempFile();
1892 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1893 IOUtils
.copyStreamToFile(input
, outputFile
);
1896 Files
.delete(tmpFile
.toPath());
1897 } catch (IOException e
) {
1898 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1904 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1905 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1906 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1909 void sendGroups() throws IOException
, UntrustedIdentityException
{
1910 File groupsFile
= IOUtils
.createTempFile();
1913 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1914 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1915 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1916 if (record instanceof GroupInfoV1
) {
1917 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
1918 out
.write(new DeviceGroup(groupInfo
.groupId
, Optional
.fromNullable(groupInfo
.name
),
1919 new ArrayList
<>(groupInfo
.getMembers()), createGroupAvatarAttachment(groupInfo
.groupId
),
1920 groupInfo
.isMember(account
.getSelfAddress()), Optional
.of(groupInfo
.messageExpirationTime
),
1921 Optional
.fromNullable(groupInfo
.color
), groupInfo
.blocked
, Optional
.fromNullable(groupInfo
.inboxPosition
), groupInfo
.archived
));
1926 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1927 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1928 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1929 .withStream(groupsFileStream
)
1930 .withContentType("application/octet-stream")
1931 .withLength(groupsFile
.length())
1934 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1939 Files
.delete(groupsFile
.toPath());
1940 } catch (IOException e
) {
1941 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1946 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1947 File contactsFile
= IOUtils
.createTempFile();
1950 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1951 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1952 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1953 VerifiedMessage verifiedMessage
= null;
1954 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1955 if (currentIdentity
!= null) {
1956 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1959 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
1960 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1961 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1962 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1963 Optional
.of(record.messageExpirationTime
),
1964 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1967 if (account
.getProfileKey() != null) {
1968 // Send our own profile key as well
1969 out
.write(new DeviceContact(account
.getSelfAddress(),
1970 Optional
.absent(), Optional
.absent(),
1971 Optional
.absent(), Optional
.absent(),
1972 Optional
.of(account
.getProfileKey()),
1973 false, Optional
.absent(), Optional
.absent(), false));
1977 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1978 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1979 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1980 .withStream(contactsFileStream
)
1981 .withContentType("application/octet-stream")
1982 .withLength(contactsFile
.length())
1985 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1990 Files
.delete(contactsFile
.toPath());
1991 } catch (IOException e
) {
1992 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1997 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1998 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1999 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2000 if (record.blocked
) {
2001 addresses
.add(record.getAddress());
2004 List
<byte[]> groupIds
= new ArrayList
<>();
2005 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2006 if (record.isBlocked()) {
2007 groupIds
.add(record.groupId
);
2010 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2013 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
2014 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
2015 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2018 public List
<ContactInfo
> getContacts() {
2019 return account
.getContactStore().getContacts();
2022 public ContactInfo
getContact(String number
) {
2023 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
2026 public GroupInfo
getGroup(byte[] groupId
) {
2027 return account
.getGroupStore().getGroup(groupId
);
2030 public byte[] getGroupId(GroupMasterKey groupMasterKey
) {
2031 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
2032 return groupSecretParams
.getPublicParams().getGroupIdentifier().serialize();
2035 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
2036 return account
.getSignalProtocolStore().getIdentities();
2039 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
2040 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2044 * Trust this the identity with this fingerprint
2046 * @param name username of the identity
2047 * @param fingerprint Fingerprint
2049 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2050 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2051 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2055 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2056 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2060 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2062 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2063 } catch (IOException
| UntrustedIdentityException e
) {
2064 e
.printStackTrace();
2073 * Trust this the identity with this safety number
2075 * @param name username of the identity
2076 * @param safetyNumber Safety number
2078 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2079 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2080 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2084 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2085 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2089 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2091 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2092 } catch (IOException
| UntrustedIdentityException e
) {
2093 e
.printStackTrace();
2102 * Trust all keys of this identity without verification
2104 * @param name username of the identity
2106 public boolean trustIdentityAllKeys(String name
) {
2107 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2108 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2112 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2113 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2114 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2116 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2117 } catch (IOException
| UntrustedIdentityException e
) {
2118 e
.printStackTrace();
2126 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
2127 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
2130 void saveAccount() {
2134 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2135 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2136 return resolveSignalServiceAddress(canonicalizedNumber
);
2139 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2140 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2142 return resolveSignalServiceAddress(address
);
2145 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2146 if (address
.matches(account
.getSelfAddress())) {
2147 return account
.getSelfAddress();
2150 return account
.getRecipientStore().resolveServiceAddress(address
);
2154 public void close() throws IOException
{
2155 if (messagePipe
!= null) {
2156 messagePipe
.shutdown();
2160 if (unidentifiedMessagePipe
!= null) {
2161 unidentifiedMessagePipe
.shutdown();
2162 unidentifiedMessagePipe
= null;
2168 public interface ReceiveMessageHandler
{
2170 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);