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 if (m
.isRegistered()) {
254 m
.updateAccountAttributes();
260 private void migrateLegacyConfigs() {
261 if (account
.getProfileKey() == null && isRegistered()) {
262 // Old config file, creating new profile key
263 account
.setProfileKey(KeyUtils
.createProfileKey());
266 // Store profile keys only in profile store
267 for (ContactInfo contact
: account
.getContactStore().getContacts()) {
268 String profileKeyString
= contact
.profileKey
;
269 if (profileKeyString
== null) {
272 final ProfileKey profileKey
;
274 profileKey
= new ProfileKey(Base64
.decode(profileKeyString
));
275 } catch (InvalidInputException
| IOException e
) {
278 contact
.profileKey
= null;
279 account
.getProfileStore().storeProfileKey(contact
.getAddress(), profileKey
);
283 public void checkAccountState() throws IOException
{
284 if (account
.isRegistered()) {
285 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
289 if (account
.getUuid() == null) {
290 account
.setUuid(accountManager
.getOwnUuid());
296 public boolean isRegistered() {
297 return account
.isRegistered();
300 public void register(boolean voiceVerification
, String captcha
) throws IOException
{
301 account
.setPassword(KeyUtils
.createPassword());
303 // Resetting UUID, because registering doesn't work otherwise
304 account
.setUuid(null);
305 accountManager
= createSignalServiceAccountManager();
306 this.groupsV2Api
= accountManager
.getGroupsV2Api();
308 if (voiceVerification
) {
309 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.fromNullable(captcha
), Optional
.absent());
311 accountManager
.requestSmsVerificationCode(false, Optional
.fromNullable(captcha
), Optional
.absent());
314 account
.setRegistered(false);
318 public void updateAccountAttributes() throws IOException
{
319 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities
, discoverableByPhoneNumber
);
322 public void setProfile(String name
, File avatar
) throws IOException
{
323 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
324 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
328 public void unregister() throws IOException
{
329 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
330 // If this is the master device, other users can't send messages to this number anymore.
331 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
332 accountManager
.setGcmId(Optional
.absent());
334 account
.setRegistered(false);
338 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
339 List
<DeviceInfo
> devices
= accountManager
.getDevices();
340 account
.setMultiDevice(devices
.size() > 1);
345 public void removeLinkedDevices(int deviceId
) throws IOException
{
346 accountManager
.removeDevice(deviceId
);
347 List
<DeviceInfo
> devices
= accountManager
.getDevices();
348 account
.setMultiDevice(devices
.size() > 1);
352 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
353 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
355 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
358 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
359 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
360 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
362 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
363 account
.setMultiDevice(true);
367 private List
<PreKeyRecord
> generatePreKeys() {
368 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
370 final int offset
= account
.getPreKeyIdOffset();
371 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
372 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
373 ECKeyPair keyPair
= Curve
.generateKeyPair();
374 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
379 account
.addPreKeys(records
);
385 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
387 ECKeyPair keyPair
= Curve
.generateKeyPair();
388 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
389 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
391 account
.addSignedPreKey(record);
395 } catch (InvalidKeyException e
) {
396 throw new AssertionError(e
);
400 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
401 verificationCode
= verificationCode
.replace("-", "");
402 account
.setSignalingKey(KeyUtils
.createSignalingKey());
403 // TODO make unrestricted unidentified access configurable
404 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, capabilities
, discoverableByPhoneNumber
);
406 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
407 // TODO response.isStorageCapable()
408 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
409 account
.setRegistered(true);
410 account
.setUuid(uuid
);
411 account
.setRegistrationLockPin(pin
);
412 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
418 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
419 if (pin
.isPresent()) {
420 account
.setRegistrationLockPin(pin
.get());
421 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
423 account
.setRegistrationLockPin(null);
424 accountManager
.removeRegistrationLockV1();
429 void refreshPreKeys() throws IOException
{
430 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
431 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
432 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
434 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
437 private SignalServiceMessageReceiver
getMessageReceiver() {
438 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2()
439 ? ClientZkOperations
.create(serviceConfiguration
).getProfileOperations()
441 return new SignalServiceMessageReceiver(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), userAgent
, null, timer
, clientZkProfileOperations
);
444 private SignalServiceMessageSender
getMessageSender() {
445 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2()
446 ? ClientZkOperations
.create(serviceConfiguration
).getProfileOperations()
448 final ExecutorService executor
= null;
449 return new SignalServiceMessageSender(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
450 account
.getDeviceId(), account
.getSignalProtocolStore(), userAgent
, account
.isMultiDevice(), Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent(), clientZkProfileOperations
, executor
, ServiceConfig
.MAX_ENVELOPE_SIZE
);
453 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
454 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
459 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
460 } catch (IOException
| InterruptedException
| ExecutionException
| TimeoutException ignored
) {
464 SignalServiceMessageReceiver receiver
= getMessageReceiver();
466 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
467 } catch (InterruptedException
| ExecutionException
| TimeoutException e
) {
468 throw new IOException("Failed to retrieve profile", e
);
472 private SignalProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
, ProfileKey profileKey
) throws IOException
{
473 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfile(address
);
474 long now
= new Date().getTime();
475 // Profiles are cache for 24h before retrieving them again
476 if (profileEntry
== null || profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000) {
477 SignalProfile profile
= retrieveRecipientProfile(address
, unidentifiedAccess
, profileKey
);
478 account
.getProfileStore().updateProfile(address
, profileKey
, now
, profile
);
481 return profileEntry
.getProfile();
484 private SignalProfile
retrieveRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
, ProfileKey profileKey
) throws IOException
{
485 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
, unidentifiedAccess
);
487 File avatarFile
= null;
489 avatarFile
= encryptedProfile
.getAvatar() == null ?
null : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
490 } catch (Throwable e
) {
491 System
.err
.println("Failed to retrieve profile avatar, ignoring: " + e
.getMessage());
494 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
496 return new SignalProfile(
497 encryptedProfile
.getIdentityKey(),
498 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
500 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
501 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
502 encryptedProfile
.getCapabilities());
503 } catch (InvalidCiphertextException e
) {
508 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
509 File file
= getGroupAvatarFile(groupId
);
510 if (!file
.exists()) {
511 return Optional
.absent();
514 return Optional
.of(Utils
.createAttachment(file
));
517 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
518 File file
= getContactAvatarFile(number
);
519 if (!file
.exists()) {
520 return Optional
.absent();
523 return Optional
.of(Utils
.createAttachment(file
));
526 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
527 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
529 throw new GroupNotFoundException(groupId
);
531 if (!g
.isMember(account
.getSelfAddress())) {
532 throw new NotAGroupMemberException(groupId
, g
.getTitle());
537 public List
<GroupInfo
> getGroups() {
538 return account
.getGroupStore().getGroups();
541 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
543 List
<String
> attachments
,
546 throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
547 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
548 if (attachments
!= null) {
549 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
552 final GroupInfo g
= getGroupForSending(groupId
);
554 setGroupContext(messageBuilder
, g
);
555 messageBuilder
.withExpiration(g
.getMessageExpirationTime());
557 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
560 private void setGroupContext(final SignalServiceDataMessage
.Builder messageBuilder
, final GroupInfo groupInfo
) {
561 if (groupInfo
instanceof GroupInfoV1
) {
562 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
563 .withId(groupInfo
.groupId
)
565 messageBuilder
.asGroupMessage(group
);
567 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) groupInfo
;
568 SignalServiceGroupV2 group
= SignalServiceGroupV2
.newBuilder(groupInfoV2
.getMasterKey())
569 .withRevision(groupInfoV2
.getGroup() == null ?
0 : groupInfoV2
.getGroup().getRevision())
571 messageBuilder
.asGroupMessage(group
);
575 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
576 long targetSentTimestamp
, byte[] groupId
)
577 throws IOException
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
578 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
579 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
580 .withReaction(reaction
);
581 final GroupInfo g
= getGroupForSending(groupId
);
582 setGroupContext(messageBuilder
, g
);
583 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
586 public Pair
<Long
, List
<SendMessageResult
>> sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, NotAGroupMemberException
{
587 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
591 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
592 .asGroupMessage(group
);
594 final GroupInfo g
= getGroupForSending(groupId
);
595 if (g
instanceof GroupInfoV1
) {
596 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
597 groupInfoV1
.removeMember(account
.getSelfAddress());
598 account
.getGroupStore().updateGroup(groupInfoV1
);
600 throw new RuntimeException("TODO Not implemented!");
603 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
606 private Pair
<byte[], List
<SendMessageResult
>> sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
608 if (groupId
== null) {
610 g
= new GroupInfoV1(KeyUtils
.createGroupId());
611 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
613 GroupInfo group
= getGroupForSending(groupId
);
614 if (!(group
instanceof GroupInfoV1
)) {
615 throw new RuntimeException("TODO Not implemented!");
617 g
= (GroupInfoV1
) group
;
624 if (members
!= null) {
625 final Set
<String
> newE164Members
= new HashSet
<>();
626 for (SignalServiceAddress member
: members
) {
627 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
630 newE164Members
.add(member
.getNumber().get());
633 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
634 if (contacts
.size() != newE164Members
.size()) {
635 // Some of the new members are not registered on Signal
636 for (ContactTokenDetails contact
: contacts
) {
637 newE164Members
.remove(contact
.getNumber());
639 throw new IOException("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
642 g
.addMembers(members
);
645 if (avatarFile
!= null) {
646 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
647 File aFile
= getGroupAvatarFile(g
.groupId
);
648 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
651 account
.getGroupStore().updateGroup(g
);
653 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
655 final Pair
<Long
, List
<SendMessageResult
>> result
= sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
656 return new Pair
<>(g
.groupId
, result
.second());
659 Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
661 GroupInfo group
= getGroupForSending(groupId
);
662 if (!(group
instanceof GroupInfoV1
)) {
663 throw new RuntimeException("TODO Not implemented!");
665 g
= (GroupInfoV1
) group
;
667 if (!g
.isMember(recipient
)) {
668 throw new NotAGroupMemberException(groupId
, g
.name
);
671 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
673 // Send group message only to the recipient who requested it
674 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
677 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV1 g
) throws AttachmentInvalidException
{
678 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
681 .withMembers(new ArrayList
<>(g
.getMembers()));
683 File aFile
= getGroupAvatarFile(g
.groupId
);
684 if (aFile
.exists()) {
686 group
.withAvatar(Utils
.createAttachment(aFile
));
687 } catch (IOException e
) {
688 throw new AttachmentInvalidException(aFile
.toString(), e
);
692 return SignalServiceDataMessage
.newBuilder()
693 .asGroupMessage(group
.build())
694 .withExpiration(g
.messageExpirationTime
);
697 Pair
<Long
, List
<SendMessageResult
>> sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
{
698 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
701 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
702 .asGroupMessage(group
.build());
704 // Send group info request message to the recipient who sent us a message with this groupId
705 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
708 void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
709 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
710 Collections
.singletonList(messageId
),
711 System
.currentTimeMillis());
713 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
716 public Pair
<Long
, List
<SendMessageResult
>> sendMessage(String messageText
, List
<String
> attachments
,
717 List
<String
> recipients
)
718 throws IOException
, AttachmentInvalidException
, InvalidNumberException
{
719 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
720 if (attachments
!= null) {
721 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
723 // Upload attachments here, so we only upload once even for multiple recipients
724 SignalServiceMessageSender messageSender
= getMessageSender();
725 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
726 for (SignalServiceAttachment attachment
: attachmentStreams
) {
727 if (attachment
.isStream()) {
728 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
729 } else if (attachment
.isPointer()) {
730 attachmentPointers
.add(attachment
.asPointer());
734 messageBuilder
.withAttachments(attachmentPointers
);
736 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
739 public Pair
<Long
, List
<SendMessageResult
>> sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
740 long targetSentTimestamp
, List
<String
> recipients
)
741 throws IOException
, InvalidNumberException
{
742 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
743 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
744 .withReaction(reaction
);
745 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
748 public Pair
<Long
, List
<SendMessageResult
>> sendEndSessionMessage(List
<String
> recipients
) throws IOException
, InvalidNumberException
{
749 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
750 .asEndSessionMessage();
752 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
754 return sendMessage(messageBuilder
, signalServiceAddresses
);
755 } catch (Exception e
) {
756 for (SignalServiceAddress address
: signalServiceAddresses
) {
757 handleEndSession(address
);
764 public String
getContactName(String number
) throws InvalidNumberException
{
765 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
766 if (contact
== null) {
773 public void setContactName(String number
, String name
) throws InvalidNumberException
{
774 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
775 ContactInfo contact
= account
.getContactStore().getContact(address
);
776 if (contact
== null) {
777 contact
= new ContactInfo(address
);
780 account
.getContactStore().updateContact(contact
);
784 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
785 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
788 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
789 ContactInfo contact
= account
.getContactStore().getContact(address
);
790 if (contact
== null) {
791 contact
= new ContactInfo(address
);
793 contact
.blocked
= blocked
;
794 account
.getContactStore().updateContact(contact
);
798 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
799 GroupInfo group
= getGroup(groupId
);
801 throw new GroupNotFoundException(groupId
);
804 group
.setBlocked(blocked
);
805 account
.getGroupStore().updateGroup(group
);
809 public Pair
<byte[], List
<SendMessageResult
>> updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
810 if (groupId
.length
== 0) {
813 if (name
.isEmpty()) {
816 if (members
.isEmpty()) {
819 if (avatar
.isEmpty()) {
822 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
826 * Change the expiration timer for a contact
828 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
829 ContactInfo contact
= account
.getContactStore().getContact(address
);
830 contact
.messageExpirationTime
= messageExpirationTimer
;
831 account
.getContactStore().updateContact(contact
);
832 sendExpirationTimerUpdate(address
);
836 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
837 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
838 .asExpirationUpdate();
839 sendMessage(messageBuilder
, Collections
.singleton(address
));
843 * Change the expiration timer for a contact
845 public void setExpirationTimer(String number
, int messageExpirationTimer
) throws IOException
, InvalidNumberException
{
846 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
847 setExpirationTimer(address
, messageExpirationTimer
);
851 * Change the expiration timer for a group
853 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
854 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
855 if (g
instanceof GroupInfoV1
) {
856 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
857 groupInfoV1
.messageExpirationTime
= messageExpirationTimer
;
858 account
.getGroupStore().updateGroup(groupInfoV1
);
860 throw new RuntimeException("TODO Not implemented!");
865 * Upload the sticker pack from path.
867 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
868 * @return if successful, returns the URL to install the sticker pack in the signal app
870 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
871 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
873 SignalServiceMessageSender messageSender
= getMessageSender();
875 byte[] packKey
= KeyUtils
.createStickerUploadKey();
876 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
878 Sticker sticker
= new Sticker(Hex
.fromStringCondensed(packId
), packKey
);
879 account
.getStickerStore().updateSticker(sticker
);
883 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
))
885 } catch (URISyntaxException e
) {
886 throw new AssertionError(e
);
890 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
892 String rootPath
= null;
894 final File file
= new File(path
);
895 if (file
.getName().endsWith(".zip")) {
896 zip
= new ZipFile(file
);
897 } else if (file
.getName().equals("manifest.json")) {
898 rootPath
= file
.getParent();
900 throw new StickerPackInvalidException("Could not find manifest.json");
903 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
905 if (pack
.stickers
== null) {
906 throw new StickerPackInvalidException("Must set a 'stickers' field.");
909 if (pack
.stickers
.isEmpty()) {
910 throw new StickerPackInvalidException("Must include stickers.");
913 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
914 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
915 if (sticker
.file
== null) {
916 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
919 Pair
<InputStream
, Long
> data
;
921 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
922 } catch (IOException ignored
) {
923 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
926 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
927 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""), contentType
);
928 stickers
.add(stickerInfo
);
931 StickerInfo cover
= null;
932 if (pack
.cover
!= null) {
933 if (pack
.cover
.file
== null) {
934 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
937 Pair
<InputStream
, Long
> data
;
939 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
940 } catch (IOException ignored
) {
941 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
944 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
945 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""), contentType
);
948 return new SignalServiceStickerManifestUpload(
955 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
956 InputStream inputStream
;
958 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
960 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
962 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
965 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
967 final ZipEntry entry
= zip
.getEntry(subfile
);
968 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
970 final File file
= new File(rootPath
, subfile
);
971 return new Pair
<>(new FileInputStream(file
), file
.length());
975 void requestSyncGroups() throws IOException
{
976 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
977 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
979 sendSyncMessage(message
);
980 } catch (UntrustedIdentityException e
) {
985 void requestSyncContacts() throws IOException
{
986 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
987 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
989 sendSyncMessage(message
);
990 } catch (UntrustedIdentityException e
) {
995 void requestSyncBlocked() throws IOException
{
996 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
997 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
999 sendSyncMessage(message
);
1000 } catch (UntrustedIdentityException e
) {
1001 e
.printStackTrace();
1005 void requestSyncConfiguration() throws IOException
{
1006 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
1007 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1009 sendSyncMessage(message
);
1010 } catch (UntrustedIdentityException e
) {
1011 e
.printStackTrace();
1015 private byte[] getSenderCertificate() {
1016 // TODO support UUID capable sender certificates
1017 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
1020 certificate
= accountManager
.getSenderCertificate();
1021 } catch (IOException e
) {
1022 System
.err
.println("Failed to get sender certificate: " + e
);
1025 // TODO cache for a day
1029 private byte[] getSelfUnidentifiedAccessKey() {
1030 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
1033 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
1034 ProfileKey theirProfileKey
= account
.getProfileStore().getProfileKey(recipient
);
1035 if (theirProfileKey
== null) {
1038 SignalProfile targetProfile
;
1040 targetProfile
= getRecipientProfile(recipient
, Optional
.absent(), theirProfileKey
);
1041 } catch (IOException e
) {
1042 System
.err
.println("Failed to get recipient profile: " + e
);
1046 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1050 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1051 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1054 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1057 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1058 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1059 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1061 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1062 return Optional
.absent();
1066 return Optional
.of(new UnidentifiedAccessPair(
1067 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1068 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1070 } catch (InvalidCertificateException e
) {
1071 return Optional
.absent();
1075 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1076 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1077 for (SignalServiceAddress recipient
: recipients
) {
1078 result
.add(getAccessFor(recipient
));
1083 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1084 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1085 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1086 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1088 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1089 return Optional
.absent();
1093 return Optional
.of(new UnidentifiedAccessPair(
1094 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1095 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1097 } catch (InvalidCertificateException e
) {
1098 return Optional
.absent();
1102 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1103 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1105 if (unidentifiedAccess
.isPresent()) {
1106 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1109 return Optional
.absent();
1112 private void sendSyncMessage(SignalServiceSyncMessage message
)
1113 throws IOException
, UntrustedIdentityException
{
1114 SignalServiceMessageSender messageSender
= getMessageSender();
1116 messageSender
.sendMessage(message
, getAccessForSync());
1117 } catch (UntrustedIdentityException e
) {
1118 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1123 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1124 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1126 for (String number
: numbers
) {
1127 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1129 return signalServiceAddresses
;
1132 private Pair
<Long
, List
<SendMessageResult
>> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1133 throws IOException
{
1134 recipients
= recipients
.stream().map(this::resolveSignalServiceAddress
).collect(Collectors
.toSet());
1135 final long timestamp
= System
.currentTimeMillis();
1136 messageBuilder
.withTimestamp(timestamp
);
1137 if (messagePipe
== null) {
1138 messagePipe
= getMessageReceiver().createMessagePipe();
1140 if (unidentifiedMessagePipe
== null) {
1141 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1143 SignalServiceDataMessage message
= null;
1145 message
= messageBuilder
.build();
1146 if (message
.getGroupContext().isPresent()) {
1148 SignalServiceMessageSender messageSender
= getMessageSender();
1149 final boolean isRecipientUpdate
= false;
1150 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1151 for (SendMessageResult r
: result
) {
1152 if (r
.getIdentityFailure() != null) {
1153 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1156 return new Pair
<>(timestamp
, result
);
1157 } catch (UntrustedIdentityException e
) {
1158 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1159 return new Pair
<>(timestamp
, Collections
.emptyList());
1162 // Send to all individually, so sync messages are sent correctly
1163 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1164 for (SignalServiceAddress address
: recipients
) {
1165 ContactInfo contact
= account
.getContactStore().getContact(address
);
1166 if (contact
!= null) {
1167 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1168 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1170 messageBuilder
.withExpiration(0);
1171 messageBuilder
.withProfileKey(null);
1173 message
= messageBuilder
.build();
1174 if (address
.matches(account
.getSelfAddress())) {
1175 results
.add(sendSelfMessage(message
));
1177 results
.add(sendMessage(address
, message
));
1180 return new Pair
<>(timestamp
, results
);
1183 if (message
!= null && message
.isEndSession()) {
1184 for (SignalServiceAddress recipient
: recipients
) {
1185 handleEndSession(recipient
);
1192 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1193 SignalServiceMessageSender messageSender
= getMessageSender();
1195 SignalServiceAddress recipient
= account
.getSelfAddress();
1197 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1198 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1199 message
.getTimestamp(),
1201 message
.getExpiresInSeconds(),
1202 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1204 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1207 long startTime
= System
.currentTimeMillis();
1208 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1209 return SendMessageResult
.success(recipient
, unidentifiedAccess
.isPresent(), false, System
.currentTimeMillis() - startTime
);
1210 } catch (UntrustedIdentityException e
) {
1211 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1212 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1216 private SendMessageResult
sendMessage(SignalServiceAddress address
, SignalServiceDataMessage message
) throws IOException
{
1217 SignalServiceMessageSender messageSender
= getMessageSender();
1220 return messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1221 } catch (UntrustedIdentityException e
) {
1222 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1223 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1227 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1228 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1230 return cipher
.decrypt(envelope
);
1231 } catch (ProtocolUntrustedIdentityException e
) {
1232 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1233 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1234 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1235 throw identityException
;
1237 throw new AssertionError(e
);
1241 private void handleEndSession(SignalServiceAddress source
) {
1242 account
.getSignalProtocolStore().deleteAllSessions(source
);
1245 private static int currentTimeDays() {
1246 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
1249 private GroupsV2AuthorizationString
getGroupAuthForToday(final GroupSecretParams groupSecretParams
) throws IOException
, VerificationFailedException
{
1250 final int today
= currentTimeDays();
1251 // Returns credentials for the next 7 days
1252 final HashMap
<Integer
, AuthCredentialResponse
> credentials
= groupsV2Api
.getCredentials(today
);
1253 // TODO cache credentials until they expire
1254 AuthCredentialResponse authCredentialResponse
= credentials
.get(today
);
1255 return groupsV2Api
.getGroupsV2AuthorizationString(account
.getUuid(), today
, groupSecretParams
, authCredentialResponse
);
1258 private List
<HandleAction
> handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1259 List
<HandleAction
> actions
= new ArrayList
<>();
1260 if (message
.getGroupContext().isPresent()) {
1261 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1262 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1263 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1264 if (group
== null || group
instanceof GroupInfoV1
) {
1265 GroupInfoV1 groupV1
= (GroupInfoV1
) group
;
1266 switch (groupInfo
.getType()) {
1268 if (groupV1
== null) {
1269 groupV1
= new GroupInfoV1(groupInfo
.getGroupId());
1272 if (groupInfo
.getAvatar().isPresent()) {
1273 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1274 if (avatar
.isPointer()) {
1276 retrieveGroupAvatarAttachment(avatar
.asPointer(), groupV1
.groupId
);
1277 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1278 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1283 if (groupInfo
.getName().isPresent()) {
1284 groupV1
.name
= groupInfo
.getName().get();
1287 if (groupInfo
.getMembers().isPresent()) {
1288 groupV1
.addMembers(groupInfo
.getMembers().get()
1290 .map(this::resolveSignalServiceAddress
)
1291 .collect(Collectors
.toSet()));
1294 account
.getGroupStore().updateGroup(groupV1
);
1298 if (groupV1
== null && !isSync
) {
1299 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1303 if (groupV1
!= null) {
1304 groupV1
.removeMember(source
);
1305 account
.getGroupStore().updateGroup(groupV1
);
1310 if (groupV1
!= null && !isSync
) {
1311 actions
.add(new SendGroupUpdateAction(source
, groupV1
.groupId
));
1316 System
.err
.println("Received a group v1 message for a v2 group: " + group
.getTitle());
1319 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1320 final SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1321 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1323 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
1325 byte[] groupId
= groupSecretParams
.getPublicParams().getGroupIdentifier().serialize();
1326 GroupInfo groupInfo
= account
.getGroupStore().getGroup(groupId
);
1327 if (groupInfo
instanceof GroupInfoV1
) {
1328 // TODO upgrade group
1329 } else if (groupInfo
== null || groupInfo
instanceof GroupInfoV2
) {
1330 GroupInfoV2 groupInfoV2
= groupInfo
== null
1331 ?
new GroupInfoV2(groupId
, groupMasterKey
)
1332 : (GroupInfoV2
) groupInfo
;
1334 if (groupInfoV2
.getGroup() == null || groupInfoV2
.getGroup().getRevision() < groupContext
.getRevision()) {
1335 // TODO check if revision is only 1 behind and a signedGroupChange is available
1337 final GroupsV2AuthorizationString groupsV2AuthorizationString
= getGroupAuthForToday(groupSecretParams
);
1338 final DecryptedGroup group
= groupsV2Api
.getGroup(groupSecretParams
, groupsV2AuthorizationString
);
1339 groupInfoV2
.setGroup(group
);
1340 for (DecryptedMember member
: group
.getMembersList()) {
1341 final SignalServiceAddress address
= resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil
.parseOrThrow(member
.getUuid().toByteArray()), null));
1343 account
.getProfileStore().storeProfileKey(address
, new ProfileKey(member
.getProfileKey().toByteArray()));
1344 } catch (InvalidInputException ignored
) {
1347 } catch (IOException
| VerificationFailedException
| InvalidGroupStateException e
) {
1348 System
.err
.println("Failed to retrieve Group V2 info, ignoring ...");
1350 account
.getGroupStore().updateGroup(groupInfoV2
);
1355 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1356 if (message
.isEndSession()) {
1357 handleEndSession(conversationPartnerAddress
);
1359 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1360 if (message
.getGroupContext().isPresent()) {
1361 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1362 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1363 GroupInfoV1 group
= account
.getGroupStore().getOrCreateGroupV1(groupInfo
.getGroupId());
1364 if (group
!= null) {
1365 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1366 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1367 account
.getGroupStore().updateGroup(group
);
1370 } else if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1371 // disappearing message timer already stored in the DecryptedGroup
1374 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1375 if (contact
== null) {
1376 contact
= new ContactInfo(conversationPartnerAddress
);
1378 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1379 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1380 account
.getContactStore().updateContact(contact
);
1384 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1385 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1386 if (attachment
.isPointer()) {
1388 retrieveAttachment(attachment
.asPointer());
1389 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1390 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1395 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1396 final ProfileKey profileKey
;
1398 profileKey
= new ProfileKey(message
.getProfileKey().get());
1399 } catch (InvalidInputException e
) {
1400 throw new AssertionError(e
);
1402 if (source
.matches(account
.getSelfAddress())) {
1403 this.account
.setProfileKey(profileKey
);
1405 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1407 if (message
.getPreviews().isPresent()) {
1408 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1409 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1410 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1411 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1413 retrieveAttachment(attachment
);
1414 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1415 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1420 if (message
.getSticker().isPresent()) {
1421 final SignalServiceDataMessage
.Sticker messageSticker
= message
.getSticker().get();
1422 Sticker sticker
= account
.getStickerStore().getSticker(messageSticker
.getPackId());
1423 if (sticker
== null) {
1424 sticker
= new Sticker(messageSticker
.getPackId(), messageSticker
.getPackKey());
1425 account
.getStickerStore().updateSticker(sticker
);
1431 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1432 final File cachePath
= new File(getMessageCachePath());
1433 if (!cachePath
.exists()) {
1436 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1437 if (!dir
.isDirectory()) {
1438 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1442 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1443 if (!fileEntry
.isFile()) {
1446 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1448 // Try to delete directory if empty
1453 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1454 SignalServiceEnvelope envelope
;
1456 envelope
= Utils
.loadEnvelope(fileEntry
);
1457 if (envelope
== null) {
1460 } catch (IOException e
) {
1461 e
.printStackTrace();
1464 SignalServiceContent content
= null;
1465 if (!envelope
.isReceipt()) {
1467 content
= decryptMessage(envelope
);
1468 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1470 } catch (Exception er
) {
1471 // All other errors are not recoverable, so delete the cached message
1473 Files
.delete(fileEntry
.toPath());
1474 } catch (IOException e
) {
1475 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1479 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1480 for (HandleAction action
: actions
) {
1482 action
.execute(this);
1483 } catch (Throwable e
) {
1484 e
.printStackTrace();
1489 handler
.handleMessage(envelope
, content
, null);
1491 Files
.delete(fileEntry
.toPath());
1492 } catch (IOException e
) {
1493 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1497 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1498 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1499 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1501 Set
<HandleAction
> queuedActions
= null;
1503 if (messagePipe
== null) {
1504 messagePipe
= messageReceiver
.createMessagePipe();
1507 boolean hasCaughtUpWithOldMessages
= false;
1510 SignalServiceEnvelope envelope
;
1511 SignalServiceContent content
= null;
1512 Exception exception
= null;
1513 final long now
= new Date().getTime();
1515 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1516 // store message on disk, before acknowledging receipt to the server
1518 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1519 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1520 Utils
.storeEnvelope(envelope1
, cacheFile
);
1521 } catch (IOException e
) {
1522 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1525 if (result
.isPresent()) {
1526 envelope
= result
.get();
1528 // Received indicator that server queue is empty
1529 hasCaughtUpWithOldMessages
= true;
1531 if (queuedActions
!= null) {
1532 for (HandleAction action
: queuedActions
) {
1534 action
.execute(this);
1535 } catch (Throwable e
) {
1536 e
.printStackTrace();
1540 queuedActions
.clear();
1541 queuedActions
= null;
1544 // Continue to wait another timeout for new messages
1547 } catch (TimeoutException e
) {
1548 if (returnOnTimeout
)
1551 } catch (InvalidVersionException e
) {
1552 System
.err
.println("Ignoring error: " + e
.getMessage());
1556 if (envelope
.hasSource()) {
1557 // Store uuid if we don't have it already
1558 SignalServiceAddress source
= envelope
.getSourceAddress();
1559 resolveSignalServiceAddress(source
);
1561 if (!envelope
.isReceipt()) {
1563 content
= decryptMessage(envelope
);
1564 } catch (Exception e
) {
1567 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1568 if (hasCaughtUpWithOldMessages
) {
1569 for (HandleAction action
: actions
) {
1571 action
.execute(this);
1572 } catch (Throwable e
) {
1573 e
.printStackTrace();
1577 if (queuedActions
== null) {
1578 queuedActions
= new HashSet
<>();
1580 queuedActions
.addAll(actions
);
1584 if (!isMessageBlocked(envelope
, content
)) {
1585 handler
.handleMessage(envelope
, content
, exception
);
1587 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1588 File cacheFile
= null;
1590 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1591 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1592 Files
.delete(cacheFile
.toPath());
1593 // Try to delete directory if empty
1594 new File(getMessageCachePath()).delete();
1595 } catch (IOException e
) {
1596 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1602 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1603 SignalServiceAddress source
;
1604 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1605 source
= envelope
.getSourceAddress();
1606 } else if (content
!= null) {
1607 source
= content
.getSender();
1611 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1612 if (sourceContact
!= null && sourceContact
.blocked
) {
1616 if (content
!= null && content
.getDataMessage().isPresent()) {
1617 SignalServiceDataMessage message
= content
.getDataMessage().get();
1618 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1619 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1620 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1621 return groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.isBlocked();
1627 private List
<HandleAction
> handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1628 List
<HandleAction
> actions
= new ArrayList
<>();
1629 if (content
!= null) {
1630 SignalServiceAddress sender
;
1631 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1632 sender
= envelope
.getSourceAddress();
1634 sender
= content
.getSender();
1636 // Store uuid if we don't have it already
1637 resolveSignalServiceAddress(sender
);
1639 if (content
.getDataMessage().isPresent()) {
1640 SignalServiceDataMessage message
= content
.getDataMessage().get();
1642 if (content
.isNeedsReceipt()) {
1643 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1646 actions
.addAll(handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
));
1648 if (content
.getSyncMessage().isPresent()) {
1649 account
.setMultiDevice(true);
1650 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1651 if (syncMessage
.getSent().isPresent()) {
1652 SentTranscriptMessage message
= syncMessage
.getSent().get();
1653 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
));
1655 if (syncMessage
.getRequest().isPresent()) {
1656 RequestMessage rm
= syncMessage
.getRequest().get();
1657 if (rm
.isContactsRequest()) {
1658 actions
.add(SendSyncContactsAction
.create());
1660 if (rm
.isGroupsRequest()) {
1661 actions
.add(SendSyncGroupsAction
.create());
1663 if (rm
.isBlockedListRequest()) {
1664 actions
.add(SendSyncBlockedListAction
.create());
1666 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
1668 if (syncMessage
.getGroups().isPresent()) {
1669 File tmpFile
= null;
1671 tmpFile
= IOUtils
.createTempFile();
1672 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1673 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1675 while ((g
= s
.read()) != null) {
1676 GroupInfoV1 syncGroup
= account
.getGroupStore().getOrCreateGroupV1(g
.getId());
1677 if (syncGroup
!= null) {
1678 if (g
.getName().isPresent()) {
1679 syncGroup
.name
= g
.getName().get();
1681 syncGroup
.addMembers(g
.getMembers()
1683 .map(this::resolveSignalServiceAddress
)
1684 .collect(Collectors
.toSet()));
1685 if (!g
.isActive()) {
1686 syncGroup
.removeMember(account
.getSelfAddress());
1688 // Add ourself to the member set as it's marked as active
1689 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1691 syncGroup
.blocked
= g
.isBlocked();
1692 if (g
.getColor().isPresent()) {
1693 syncGroup
.color
= g
.getColor().get();
1696 if (g
.getAvatar().isPresent()) {
1697 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1699 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1700 syncGroup
.archived
= g
.isArchived();
1701 account
.getGroupStore().updateGroup(syncGroup
);
1705 } catch (Exception e
) {
1706 e
.printStackTrace();
1708 if (tmpFile
!= null) {
1710 Files
.delete(tmpFile
.toPath());
1711 } catch (IOException e
) {
1712 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1717 if (syncMessage
.getBlockedList().isPresent()) {
1718 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1719 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1720 setContactBlocked(resolveSignalServiceAddress(address
), true);
1722 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1724 setGroupBlocked(groupId
, true);
1725 } catch (GroupNotFoundException e
) {
1726 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1730 if (syncMessage
.getContacts().isPresent()) {
1731 File tmpFile
= null;
1733 tmpFile
= IOUtils
.createTempFile();
1734 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1735 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1736 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1737 if (contactsMessage
.isComplete()) {
1738 account
.getContactStore().clear();
1741 while ((c
= s
.read()) != null) {
1742 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1743 account
.setProfileKey(c
.getProfileKey().get());
1745 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1746 ContactInfo contact
= account
.getContactStore().getContact(address
);
1747 if (contact
== null) {
1748 contact
= new ContactInfo(address
);
1750 if (c
.getName().isPresent()) {
1751 contact
.name
= c
.getName().get();
1753 if (c
.getColor().isPresent()) {
1754 contact
.color
= c
.getColor().get();
1756 if (c
.getProfileKey().isPresent()) {
1757 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
1759 if (c
.getVerified().isPresent()) {
1760 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1761 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1763 if (c
.getExpirationTimer().isPresent()) {
1764 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1766 contact
.blocked
= c
.isBlocked();
1767 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1768 contact
.archived
= c
.isArchived();
1769 account
.getContactStore().updateContact(contact
);
1771 if (c
.getAvatar().isPresent()) {
1772 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1776 } catch (Exception e
) {
1777 e
.printStackTrace();
1779 if (tmpFile
!= null) {
1781 Files
.delete(tmpFile
.toPath());
1782 } catch (IOException e
) {
1783 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1788 if (syncMessage
.getVerified().isPresent()) {
1789 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1790 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1792 if (syncMessage
.getStickerPackOperations().isPresent()) {
1793 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations().get();
1794 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
1795 if (!m
.getPackId().isPresent()) {
1798 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
1799 if (sticker
== null) {
1800 if (!m
.getPackKey().isPresent()) {
1803 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
1805 sticker
.setInstalled(!m
.getType().isPresent() || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
1806 account
.getStickerStore().updateSticker(sticker
);
1809 if (syncMessage
.getConfiguration().isPresent()) {
1817 private File
getContactAvatarFile(String number
) {
1818 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1821 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1822 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1823 if (attachment
.isPointer()) {
1824 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1825 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1827 SignalServiceAttachmentStream stream
= attachment
.asStream();
1828 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1832 private File
getGroupAvatarFile(byte[] groupId
) {
1833 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1836 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1837 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1838 if (attachment
.isPointer()) {
1839 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1840 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1842 SignalServiceAttachmentStream stream
= attachment
.asStream();
1843 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1847 private File
getProfileAvatarFile(SignalServiceAddress address
) {
1848 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
1851 private File
retrieveProfileAvatar(SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
) throws IOException
{
1852 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1853 SignalServiceMessageReceiver receiver
= getMessageReceiver();
1854 File outputFile
= getProfileAvatarFile(address
);
1856 File tmpFile
= IOUtils
.createTempFile();
1857 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
, tmpFile
, profileKey
, ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
1858 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
1859 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
1862 Files
.delete(tmpFile
.toPath());
1863 } catch (IOException e
) {
1864 System
.err
.println("Failed to delete received avatar temp file “" + tmpFile
+ "”: " + e
.getMessage());
1870 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1871 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1874 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1875 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1876 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1879 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1880 if (storePreview
&& pointer
.getPreview().isPresent()) {
1881 File previewFile
= new File(outputFile
+ ".preview");
1882 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1883 byte[] preview
= pointer
.getPreview().get();
1884 output
.write(preview
, 0, preview
.length
);
1885 } catch (FileNotFoundException e
) {
1886 e
.printStackTrace();
1891 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1893 File tmpFile
= IOUtils
.createTempFile();
1894 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1895 IOUtils
.copyStreamToFile(input
, outputFile
);
1898 Files
.delete(tmpFile
.toPath());
1899 } catch (IOException e
) {
1900 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1906 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1907 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1908 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1911 void sendGroups() throws IOException
, UntrustedIdentityException
{
1912 File groupsFile
= IOUtils
.createTempFile();
1915 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1916 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1917 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1918 if (record instanceof GroupInfoV1
) {
1919 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
1920 out
.write(new DeviceGroup(groupInfo
.groupId
, Optional
.fromNullable(groupInfo
.name
),
1921 new ArrayList
<>(groupInfo
.getMembers()), createGroupAvatarAttachment(groupInfo
.groupId
),
1922 groupInfo
.isMember(account
.getSelfAddress()), Optional
.of(groupInfo
.messageExpirationTime
),
1923 Optional
.fromNullable(groupInfo
.color
), groupInfo
.blocked
, Optional
.fromNullable(groupInfo
.inboxPosition
), groupInfo
.archived
));
1928 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1929 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1930 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1931 .withStream(groupsFileStream
)
1932 .withContentType("application/octet-stream")
1933 .withLength(groupsFile
.length())
1936 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1941 Files
.delete(groupsFile
.toPath());
1942 } catch (IOException e
) {
1943 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1948 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1949 File contactsFile
= IOUtils
.createTempFile();
1952 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1953 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1954 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1955 VerifiedMessage verifiedMessage
= null;
1956 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1957 if (currentIdentity
!= null) {
1958 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1961 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
1962 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1963 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1964 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1965 Optional
.of(record.messageExpirationTime
),
1966 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1969 if (account
.getProfileKey() != null) {
1970 // Send our own profile key as well
1971 out
.write(new DeviceContact(account
.getSelfAddress(),
1972 Optional
.absent(), Optional
.absent(),
1973 Optional
.absent(), Optional
.absent(),
1974 Optional
.of(account
.getProfileKey()),
1975 false, Optional
.absent(), Optional
.absent(), false));
1979 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1980 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1981 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1982 .withStream(contactsFileStream
)
1983 .withContentType("application/octet-stream")
1984 .withLength(contactsFile
.length())
1987 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1992 Files
.delete(contactsFile
.toPath());
1993 } catch (IOException e
) {
1994 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1999 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2000 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2001 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2002 if (record.blocked
) {
2003 addresses
.add(record.getAddress());
2006 List
<byte[]> groupIds
= new ArrayList
<>();
2007 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2008 if (record.isBlocked()) {
2009 groupIds
.add(record.groupId
);
2012 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2015 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
2016 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
2017 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2020 public List
<ContactInfo
> getContacts() {
2021 return account
.getContactStore().getContacts();
2024 public ContactInfo
getContact(String number
) {
2025 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
2028 public GroupInfo
getGroup(byte[] groupId
) {
2029 return account
.getGroupStore().getGroup(groupId
);
2032 public byte[] getGroupId(GroupMasterKey groupMasterKey
) {
2033 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
2034 return groupSecretParams
.getPublicParams().getGroupIdentifier().serialize();
2037 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
2038 return account
.getSignalProtocolStore().getIdentities();
2041 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
2042 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2046 * Trust this the identity with this fingerprint
2048 * @param name username of the identity
2049 * @param fingerprint Fingerprint
2051 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2052 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2053 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2057 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2058 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2062 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2064 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2065 } catch (IOException
| UntrustedIdentityException e
) {
2066 e
.printStackTrace();
2075 * Trust this the identity with this safety number
2077 * @param name username of the identity
2078 * @param safetyNumber Safety number
2080 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2081 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2082 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2086 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2087 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2091 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2093 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2094 } catch (IOException
| UntrustedIdentityException e
) {
2095 e
.printStackTrace();
2104 * Trust all keys of this identity without verification
2106 * @param name username of the identity
2108 public boolean trustIdentityAllKeys(String name
) {
2109 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2110 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2114 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2115 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2116 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2118 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2119 } catch (IOException
| UntrustedIdentityException e
) {
2120 e
.printStackTrace();
2128 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
2129 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
2132 void saveAccount() {
2136 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2137 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2138 return resolveSignalServiceAddress(canonicalizedNumber
);
2141 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2142 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2144 return resolveSignalServiceAddress(address
);
2147 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2148 if (address
.matches(account
.getSelfAddress())) {
2149 return account
.getSelfAddress();
2152 return account
.getRecipientStore().resolveServiceAddress(address
);
2156 public void close() throws IOException
{
2157 if (messagePipe
!= null) {
2158 messagePipe
.shutdown();
2162 if (unidentifiedMessagePipe
!= null) {
2163 unidentifiedMessagePipe
.shutdown();
2164 unidentifiedMessagePipe
= null;
2170 public interface ReceiveMessageHandler
{
2172 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);