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 org
.asamk
.Signal
;
20 import org
.asamk
.signal
.AttachmentInvalidException
;
21 import org
.asamk
.signal
.GroupNotFoundException
;
22 import org
.asamk
.signal
.NotAGroupMemberException
;
23 import org
.asamk
.signal
.TrustLevel
;
24 import org
.asamk
.signal
.UserAlreadyExists
;
25 import org
.asamk
.signal
.storage
.SignalAccount
;
26 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
27 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
28 import org
.asamk
.signal
.storage
.groups
.JsonGroupStore
;
29 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
30 import org
.asamk
.signal
.storage
.threads
.ThreadInfo
;
31 import org
.asamk
.signal
.util
.IOUtils
;
32 import org
.asamk
.signal
.util
.Util
;
33 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
34 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
35 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
36 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
37 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
38 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
39 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
40 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
41 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
42 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
43 import org
.signal
.libsignal
.metadata
.SelfSendException
;
44 import org
.signal
.zkgroup
.InvalidInputException
;
45 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
46 import org
.whispersystems
.libsignal
.IdentityKey
;
47 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
48 import org
.whispersystems
.libsignal
.InvalidKeyException
;
49 import org
.whispersystems
.libsignal
.InvalidMessageException
;
50 import org
.whispersystems
.libsignal
.InvalidVersionException
;
51 import org
.whispersystems
.libsignal
.ecc
.Curve
;
52 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
53 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
54 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
55 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
56 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
57 import org
.whispersystems
.libsignal
.util
.Medium
;
58 import org
.whispersystems
.libsignal
.util
.Pair
;
59 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
60 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
61 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
62 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
63 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
64 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
65 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
66 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
67 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
68 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
69 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
70 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
71 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
72 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
73 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
74 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
76 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
77 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
78 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
79 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
80 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
81 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
82 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
83 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
84 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
85 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
89 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
90 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
91 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
92 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.AuthorizationFailedException
;
93 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
94 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
95 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
96 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
97 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
98 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
99 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
100 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
101 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
102 import org
.whispersystems
.util
.Base64
;
105 import java
.io
.FileInputStream
;
106 import java
.io
.FileNotFoundException
;
107 import java
.io
.FileOutputStream
;
108 import java
.io
.IOException
;
109 import java
.io
.InputStream
;
110 import java
.io
.OutputStream
;
112 import java
.nio
.file
.Files
;
113 import java
.nio
.file
.Paths
;
114 import java
.nio
.file
.StandardCopyOption
;
115 import java
.util
.ArrayList
;
116 import java
.util
.Arrays
;
117 import java
.util
.Collection
;
118 import java
.util
.Collections
;
119 import java
.util
.Date
;
120 import java
.util
.HashSet
;
121 import java
.util
.LinkedList
;
122 import java
.util
.List
;
123 import java
.util
.Locale
;
124 import java
.util
.Map
;
125 import java
.util
.Objects
;
126 import java
.util
.Set
;
127 import java
.util
.concurrent
.TimeUnit
;
128 import java
.util
.concurrent
.TimeoutException
;
130 public class Manager
implements Signal
{
132 private static final SignalServiceProfile
.Capabilities capabilities
= new SignalServiceProfile
.Capabilities(false, false);
134 private final String settingsPath
;
135 private final String dataPath
;
136 private final String attachmentsPath
;
137 private final String avatarsPath
;
138 private final SleepTimer timer
= new UptimeSleepTimer();
140 private SignalAccount account
;
141 private String username
;
142 private SignalServiceAccountManager accountManager
;
143 private SignalServiceMessagePipe messagePipe
= null;
144 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
146 public Manager(String username
, String settingsPath
) {
147 this.username
= username
;
148 this.settingsPath
= settingsPath
;
149 this.dataPath
= this.settingsPath
+ "/data";
150 this.attachmentsPath
= this.settingsPath
+ "/attachments";
151 this.avatarsPath
= this.settingsPath
+ "/avatars";
155 public String
getUsername() {
159 private SignalServiceAddress
getSelfAddress() {
160 return new SignalServiceAddress(null, username
);
163 private SignalServiceAccountManager
getSignalServiceAccountManager() {
164 return new SignalServiceAccountManager(BaseConfig
.serviceConfiguration
, null, account
.getUsername(), account
.getPassword(), account
.getDeviceId(), BaseConfig
.USER_AGENT
, timer
);
167 private IdentityKey
getIdentity() {
168 return account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey();
171 public int getDeviceId() {
172 return account
.getDeviceId();
175 private String
getMessageCachePath() {
176 return this.dataPath
+ "/" + username
+ ".d/msg-cache";
179 private String
getMessageCachePath(String sender
) {
180 return getMessageCachePath() + "/" + sender
.replace("/", "_");
183 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
184 String cachePath
= getMessageCachePath(sender
);
185 IOUtils
.createPrivateDirectories(cachePath
);
186 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
189 public boolean userHasKeys() {
190 return account
!= null && account
.getSignalProtocolStore() != null;
193 public void init() throws IOException
{
194 if (!SignalAccount
.userExists(dataPath
, username
)) {
197 account
= SignalAccount
.load(dataPath
, username
);
199 migrateLegacyConfigs();
201 accountManager
= getSignalServiceAccountManager();
203 if (account
.isRegistered() && accountManager
.getPreKeysCount() < BaseConfig
.PREKEY_MINIMUM_COUNT
) {
207 } catch (AuthorizationFailedException e
) {
208 System
.err
.println("Authorization failed, was the number registered elsewhere?");
213 private void migrateLegacyConfigs() {
214 // Copy group avatars that were previously stored in the attachments folder
215 // to the new avatar folder
216 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
217 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
218 File avatarFile
= getGroupAvatarFile(g
.groupId
);
219 File attachmentFile
= getAttachmentFile(g
.getAvatarId());
220 if (!avatarFile
.exists() && attachmentFile
.exists()) {
222 IOUtils
.createPrivateDirectories(avatarsPath
);
223 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
224 } catch (Exception e
) {
229 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
232 if (account
.getProfileKey() == null) {
233 // Old config file, creating new profile key
234 account
.setProfileKey(KeyUtils
.createProfileKey());
239 private void createNewIdentity() throws IOException
{
240 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
241 int registrationId
= KeyHelper
.generateRegistrationId(false);
242 if (username
== null) {
243 account
= SignalAccount
.createTemporaryAccount(identityKey
, registrationId
);
245 ProfileKey profileKey
= KeyUtils
.createProfileKey();
246 account
= SignalAccount
.create(dataPath
, username
, identityKey
, registrationId
, profileKey
);
251 public boolean isRegistered() {
252 return account
!= null && account
.isRegistered();
255 public void register(boolean voiceVerification
) throws IOException
{
256 if (account
== null) {
259 account
.setPassword(KeyUtils
.createPassword());
260 accountManager
= getSignalServiceAccountManager();
262 if (voiceVerification
) {
263 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
265 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
268 account
.setRegistered(false);
272 public void updateAccountAttributes() throws IOException
{
273 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities
);
276 public void setProfileName(String name
) throws IOException
{
277 accountManager
.setProfileName(account
.getProfileKey(), name
);
280 public void setProfileAvatar(File avatar
) throws IOException
{
281 final StreamDetails streamDetails
= Utils
.createStreamDetailsFromFile(avatar
);
282 accountManager
.setProfileAvatar(account
.getProfileKey(), streamDetails
);
283 streamDetails
.getStream().close();
286 public void removeProfileAvatar() throws IOException
{
287 accountManager
.setProfileAvatar(account
.getProfileKey(), null);
290 public void unregister() throws IOException
{
291 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
292 // If this is the master device, other users can't send messages to this number anymore.
293 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
294 accountManager
.setGcmId(Optional
.absent());
296 account
.setRegistered(false);
300 public String
getDeviceLinkUri() throws TimeoutException
, IOException
{
301 if (account
== null) {
304 account
.setPassword(KeyUtils
.createPassword());
305 accountManager
= getSignalServiceAccountManager();
306 String uuid
= accountManager
.getNewDeviceUuid();
308 return Utils
.createDeviceLinkUri(new Utils
.DeviceLinkInfo(uuid
, getIdentity().getPublicKey()));
311 public void finishDeviceLink(String deviceName
) throws IOException
, InvalidKeyException
, TimeoutException
, UserAlreadyExists
{
312 account
.setSignalingKey(KeyUtils
.createSignalingKey());
313 SignalServiceAccountManager
.NewDeviceRegistrationReturn ret
= accountManager
.finishNewDeviceRegistration(account
.getSignalProtocolStore().getIdentityKeyPair(), account
.getSignalingKey(), false, true, account
.getSignalProtocolStore().getLocalRegistrationId(), deviceName
);
315 username
= ret
.getNumber();
316 // TODO do this check before actually registering
317 if (SignalAccount
.userExists(dataPath
, username
)) {
318 throw new UserAlreadyExists(username
, SignalAccount
.getFileName(dataPath
, username
));
321 // Create new account with the synced identity
322 byte[] profileKeyBytes
= ret
.getProfileKey();
323 ProfileKey profileKey
;
324 if (profileKeyBytes
== null) {
325 profileKey
= KeyUtils
.createProfileKey();
328 profileKey
= new ProfileKey(profileKeyBytes
);
329 } catch (InvalidInputException e
) {
330 throw new IOException("Received invalid profileKey", e
);
333 account
= SignalAccount
.createLinkedAccount(dataPath
, username
, account
.getPassword(), ret
.getDeviceId(), ret
.getIdentity(), account
.getSignalProtocolStore().getLocalRegistrationId(), account
.getSignalingKey(), profileKey
);
338 requestSyncContacts();
339 requestSyncBlocked();
340 requestSyncConfiguration();
345 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
346 List
<DeviceInfo
> devices
= accountManager
.getDevices();
347 account
.setMultiDevice(devices
.size() > 1);
352 public void removeLinkedDevices(int deviceId
) throws IOException
{
353 accountManager
.removeDevice(deviceId
);
354 List
<DeviceInfo
> devices
= accountManager
.getDevices();
355 account
.setMultiDevice(devices
.size() > 1);
359 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
360 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
362 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
365 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
366 IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
367 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
369 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
370 account
.setMultiDevice(true);
374 private List
<PreKeyRecord
> generatePreKeys() {
375 List
<PreKeyRecord
> records
= new ArrayList
<>(BaseConfig
.PREKEY_BATCH_SIZE
);
377 final int offset
= account
.getPreKeyIdOffset();
378 for (int i
= 0; i
< BaseConfig
.PREKEY_BATCH_SIZE
; i
++) {
379 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
380 ECKeyPair keyPair
= Curve
.generateKeyPair();
381 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
386 account
.addPreKeys(records
);
392 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
394 ECKeyPair keyPair
= Curve
.generateKeyPair();
395 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
396 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
398 account
.addSignedPreKey(record);
402 } catch (InvalidKeyException e
) {
403 throw new AssertionError(e
);
407 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
408 verificationCode
= verificationCode
.replace("-", "");
409 account
.setSignalingKey(KeyUtils
.createSignalingKey());
410 // TODO make unrestricted unidentified access configurable
411 accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, capabilities
);
413 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
414 account
.setRegistered(true);
415 account
.setRegistrationLockPin(pin
);
421 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
422 if (pin
.isPresent()) {
423 account
.setRegistrationLockPin(pin
.get());
424 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
426 account
.setRegistrationLockPin(null);
427 accountManager
.removeV1Pin();
432 private void refreshPreKeys() throws IOException
{
433 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
434 final IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
435 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
437 accountManager
.setPreKeys(getIdentity(), signedPreKeyRecord
, oneTimePreKeys
);
440 private SignalServiceMessageReceiver
getMessageReceiver() {
441 return new SignalServiceMessageReceiver(BaseConfig
.serviceConfiguration
, null, username
, account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), BaseConfig
.USER_AGENT
, null, timer
);
444 private SignalServiceMessageSender
getMessageSender() {
445 return new SignalServiceMessageSender(BaseConfig
.serviceConfiguration
, null, username
, account
.getPassword(),
446 account
.getDeviceId(), account
.getSignalProtocolStore(), BaseConfig
.USER_AGENT
, account
.isMultiDevice(), Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent());
449 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
450 File file
= getGroupAvatarFile(groupId
);
451 if (!file
.exists()) {
452 return Optional
.absent();
455 return Optional
.of(Utils
.createAttachment(file
));
458 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
459 File file
= getContactAvatarFile(number
);
460 if (!file
.exists()) {
461 return Optional
.absent();
464 return Optional
.of(Utils
.createAttachment(file
));
467 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
468 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
470 throw new GroupNotFoundException(groupId
);
472 for (String member
: g
.members
) {
473 if (member
.equals(this.username
)) {
477 throw new NotAGroupMemberException(groupId
, g
.name
);
480 public List
<GroupInfo
> getGroups() {
481 return account
.getGroupStore().getGroups();
485 public void sendGroupMessage(String messageText
, List
<String
> attachments
,
487 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
488 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
489 if (attachments
!= null) {
490 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
492 if (groupId
!= null) {
493 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
496 messageBuilder
.asGroupMessage(group
);
498 ThreadInfo thread
= account
.getThreadStore().getThread(Base64
.encodeBytes(groupId
));
499 if (thread
!= null) {
500 messageBuilder
.withExpiration(thread
.messageExpirationTime
);
503 final GroupInfo g
= getGroupForSending(groupId
);
505 // Don't send group message to ourself
506 final List
<String
> membersSend
= new ArrayList
<>(g
.members
);
507 membersSend
.remove(this.username
);
508 sendMessageLegacy(messageBuilder
, membersSend
);
511 public void sendGroupMessageReaction(String emoji
, boolean remove
, SignalServiceAddress targetAuthor
,
512 long targetSentTimestamp
, byte[] groupId
)
513 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
{
514 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
);
515 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
516 .withReaction(reaction
)
517 .withProfileKey(account
.getProfileKey().serialize());
518 if (groupId
!= null) {
519 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
522 messageBuilder
.asGroupMessage(group
);
524 final GroupInfo g
= getGroupForSending(groupId
);
525 // Don't send group message to ourself
526 final List
<String
> membersSend
= new ArrayList
<>(g
.members
);
527 membersSend
.remove(this.username
);
528 sendMessageLegacy(messageBuilder
, membersSend
);
531 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
{
532 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
536 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
537 .asGroupMessage(group
);
539 final GroupInfo g
= getGroupForSending(groupId
);
540 g
.members
.remove(this.username
);
541 account
.getGroupStore().updateGroup(g
);
543 sendMessageLegacy(messageBuilder
, g
.members
);
546 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<String
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
548 if (groupId
== null) {
550 g
= new GroupInfo(KeyUtils
.createGroupId());
551 g
.members
.add(username
);
553 g
= getGroupForSending(groupId
);
560 if (members
!= null) {
561 Set
<String
> newMembers
= new HashSet
<>();
562 for (String member
: members
) {
564 member
= Utils
.canonicalizeNumber(member
, username
);
565 } catch (InvalidNumberException e
) {
566 System
.err
.println("Failed to add member \"" + member
+ "\" to group: " + e
.getMessage());
567 System
.err
.println("Aborting…");
570 if (g
.members
.contains(member
)) {
573 newMembers
.add(member
);
574 g
.members
.add(member
);
576 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newMembers
);
577 if (contacts
.size() != newMembers
.size()) {
578 // Some of the new members are not registered on Signal
579 for (ContactTokenDetails contact
: contacts
) {
580 newMembers
.remove(contact
.getNumber());
582 System
.err
.println("Failed to add members " + Util
.join(", ", newMembers
) + " to group: Not registered on Signal");
583 System
.err
.println("Aborting…");
588 if (avatarFile
!= null) {
589 IOUtils
.createPrivateDirectories(avatarsPath
);
590 File aFile
= getGroupAvatarFile(g
.groupId
);
591 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
594 account
.getGroupStore().updateGroup(g
);
596 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
598 // Don't send group message to ourself
599 final List
<String
> membersSend
= new ArrayList
<>(g
.members
);
600 membersSend
.remove(this.username
);
601 sendMessageLegacy(messageBuilder
, membersSend
);
605 private void sendUpdateGroupMessage(byte[] groupId
, String recipient
) throws IOException
, EncapsulatedExceptions
{
606 if (groupId
== null) {
609 GroupInfo g
= getGroupForSending(groupId
);
611 if (!g
.members
.contains(recipient
)) {
615 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
617 // Send group message only to the recipient who requested it
618 final List
<String
> membersSend
= new ArrayList
<>();
619 membersSend
.add(recipient
);
620 sendMessageLegacy(messageBuilder
, membersSend
);
623 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) {
624 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
627 .withMembers(new ArrayList
<>(g
.getMembers()));
629 File aFile
= getGroupAvatarFile(g
.groupId
);
630 if (aFile
.exists()) {
632 group
.withAvatar(Utils
.createAttachment(aFile
));
633 } catch (IOException e
) {
634 throw new AttachmentInvalidException(aFile
.toString(), e
);
638 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
639 .asGroupMessage(group
.build());
641 ThreadInfo thread
= account
.getThreadStore().getThread(Base64
.encodeBytes(g
.groupId
));
642 if (thread
!= null) {
643 messageBuilder
.withExpiration(thread
.messageExpirationTime
);
646 return messageBuilder
;
649 private void sendGroupInfoRequest(byte[] groupId
, String recipient
) throws IOException
, EncapsulatedExceptions
{
650 if (groupId
== null) {
654 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
657 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
658 .asGroupMessage(group
.build());
660 ThreadInfo thread
= account
.getThreadStore().getThread(Base64
.encodeBytes(groupId
));
661 if (thread
!= null) {
662 messageBuilder
.withExpiration(thread
.messageExpirationTime
);
665 // Send group info request message to the recipient who sent us a message with this groupId
666 final List
<String
> membersSend
= new ArrayList
<>();
667 membersSend
.add(recipient
);
668 sendMessageLegacy(messageBuilder
, membersSend
);
672 public void sendMessage(String message
, List
<String
> attachments
, String recipient
)
673 throws EncapsulatedExceptions
, AttachmentInvalidException
, IOException
{
674 List
<String
> recipients
= new ArrayList
<>(1);
675 recipients
.add(recipient
);
676 sendMessage(message
, attachments
, recipients
);
680 public void sendMessage(String messageText
, List
<String
> attachments
,
681 List
<String
> recipients
)
682 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
{
683 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
684 if (attachments
!= null) {
685 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
687 // Upload attachments here, so we only upload once even for multiple recipients
688 SignalServiceMessageSender messageSender
= getMessageSender();
689 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
690 for (SignalServiceAttachment attachment
: attachmentStreams
) {
691 if (attachment
.isStream()) {
692 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
693 } else if (attachment
.isPointer()) {
694 attachmentPointers
.add(attachment
.asPointer());
698 messageBuilder
.withAttachments(attachmentPointers
);
700 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
701 sendMessageLegacy(messageBuilder
, recipients
);
704 public void sendMessageReaction(String emoji
, boolean remove
, SignalServiceAddress targetAuthor
,
705 long targetSentTimestamp
, List
<String
> recipients
)
706 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
{
707 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
);
708 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
709 .withReaction(reaction
)
710 .withProfileKey(account
.getProfileKey().serialize());
711 sendMessageLegacy(messageBuilder
, recipients
);
715 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
{
716 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
717 .asEndSessionMessage();
719 sendMessageLegacy(messageBuilder
, recipients
);
723 public String
getContactName(String number
) throws InvalidNumberException
{
724 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, username
);
725 ContactInfo contact
= account
.getContactStore().getContact(canonicalizedNumber
);
726 if (contact
== null) {
734 public void setContactName(String number
, String name
) throws InvalidNumberException
{
735 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, username
);
736 ContactInfo contact
= account
.getContactStore().getContact(canonicalizedNumber
);
737 if (contact
== null) {
738 contact
= new ContactInfo();
739 contact
.number
= canonicalizedNumber
;
740 System
.err
.println("Add contact " + canonicalizedNumber
+ " named " + name
);
742 System
.err
.println("Updating contact " + canonicalizedNumber
+ " name " + contact
.name
+ " -> " + name
);
745 account
.getContactStore().updateContact(contact
);
750 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
751 number
= Utils
.canonicalizeNumber(number
, username
);
752 ContactInfo contact
= account
.getContactStore().getContact(number
);
753 if (contact
== null) {
754 contact
= new ContactInfo();
755 contact
.number
= number
;
756 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + number
);
758 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + number
);
760 contact
.blocked
= blocked
;
761 account
.getContactStore().updateContact(contact
);
766 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
767 GroupInfo group
= getGroup(groupId
);
769 throw new GroupNotFoundException(groupId
);
771 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
772 group
.blocked
= blocked
;
773 account
.getGroupStore().updateGroup(group
);
779 public List
<byte[]> getGroupIds() {
780 List
<GroupInfo
> groups
= getGroups();
781 List
<byte[]> ids
= new ArrayList
<>(groups
.size());
782 for (GroupInfo group
: groups
) {
783 ids
.add(group
.groupId
);
789 public String
getGroupName(byte[] groupId
) {
790 GroupInfo group
= getGroup(groupId
);
799 public List
<String
> getGroupMembers(byte[] groupId
) {
800 GroupInfo group
= getGroup(groupId
);
802 return new ArrayList
<>();
804 return new ArrayList
<>(group
.members
);
809 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
810 if (groupId
.length
== 0) {
813 if (name
.isEmpty()) {
816 if (members
.size() == 0) {
819 if (avatar
.isEmpty()) {
822 return sendUpdateGroupMessage(groupId
, name
, members
, avatar
);
826 * Change the expiration timer for a thread (number of groupId)
828 * @param numberOrGroupId
829 * @param messageExpirationTimer
831 public void setExpirationTimer(String numberOrGroupId
, int messageExpirationTimer
) {
832 ThreadInfo thread
= account
.getThreadStore().getThread(numberOrGroupId
);
833 thread
.messageExpirationTime
= messageExpirationTimer
;
834 account
.getThreadStore().updateThread(thread
);
837 private void requestSyncGroups() throws IOException
{
838 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
839 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
841 sendSyncMessage(message
);
842 } catch (UntrustedIdentityException e
) {
847 private void requestSyncContacts() throws IOException
{
848 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
849 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
851 sendSyncMessage(message
);
852 } catch (UntrustedIdentityException e
) {
857 private void requestSyncBlocked() throws IOException
{
858 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
859 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
861 sendSyncMessage(message
);
862 } catch (UntrustedIdentityException e
) {
867 private void requestSyncConfiguration() throws IOException
{
868 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
869 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
871 sendSyncMessage(message
);
872 } catch (UntrustedIdentityException e
) {
877 private byte[] getSelfUnidentifiedAccessKey() {
878 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
881 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
886 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
888 return Optional
.absent();
891 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
892 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
893 for (SignalServiceAddress recipient
: recipients
) {
894 result
.add(Optional
.absent());
899 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
901 return Optional
.absent();
904 private void sendSyncMessage(SignalServiceSyncMessage message
)
905 throws IOException
, UntrustedIdentityException
{
906 SignalServiceMessageSender messageSender
= getMessageSender();
908 messageSender
.sendMessage(message
, getAccessForSync());
909 } catch (UntrustedIdentityException e
) {
910 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
916 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
918 private void sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<String
> recipients
)
919 throws EncapsulatedExceptions
, IOException
{
920 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
922 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
923 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
924 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
926 for (SendMessageResult result
: results
) {
927 if (result
.isUnregisteredFailure()) {
928 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getNumber().get(), null));
929 } else if (result
.isNetworkFailure()) {
930 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getNumber().get(), null));
931 } else if (result
.getIdentityFailure() != null) {
932 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getNumber().get(), result
.getIdentityFailure().getIdentityKey()));
935 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
936 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
940 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<String
> recipients
)
942 Set
<SignalServiceAddress
> recipientsTS
= Utils
.getSignalServiceAddresses(recipients
, username
);
943 if (recipientsTS
== null) {
945 return Collections
.emptyList();
948 SignalServiceDataMessage message
= null;
950 SignalServiceMessageSender messageSender
= getMessageSender();
952 message
= messageBuilder
.build();
953 if (message
.getGroupInfo().isPresent()) {
955 final boolean isRecipientUpdate
= false;
956 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipientsTS
), getAccessFor(recipientsTS
), isRecipientUpdate
, message
);
957 for (SendMessageResult r
: result
) {
958 if (r
.getIdentityFailure() != null) {
959 account
.getSignalProtocolStore().saveIdentity(r
.getAddress().getNumber().get(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
963 } catch (UntrustedIdentityException e
) {
964 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
965 return Collections
.emptyList();
967 } else if (recipientsTS
.size() == 1 && recipientsTS
.contains(getSelfAddress())) {
968 SignalServiceAddress recipient
= getSelfAddress();
969 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
970 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
971 message
.getTimestamp(),
973 message
.getExpiresInSeconds(),
974 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
976 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
978 List
<SendMessageResult
> results
= new ArrayList
<>(recipientsTS
.size());
980 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
981 } catch (UntrustedIdentityException e
) {
982 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
983 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
987 // Send to all individually, so sync messages are sent correctly
988 List
<SendMessageResult
> results
= new ArrayList
<>(recipientsTS
.size());
989 for (SignalServiceAddress address
: recipientsTS
) {
990 ThreadInfo thread
= account
.getThreadStore().getThread(address
.getNumber().get());
991 if (thread
!= null) {
992 messageBuilder
.withExpiration(thread
.messageExpirationTime
);
994 messageBuilder
.withExpiration(0);
996 message
= messageBuilder
.build();
998 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1000 } catch (UntrustedIdentityException e
) {
1001 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1002 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1008 if (message
!= null && message
.isEndSession()) {
1009 for (SignalServiceAddress recipient
: recipientsTS
) {
1010 handleEndSession(recipient
.getNumber().get());
1017 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, ProtocolUntrustedIdentityException
, SelfSendException
, UnsupportedDataMessageException
{
1018 SignalServiceCipher cipher
= new SignalServiceCipher(getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1020 return cipher
.decrypt(envelope
);
1021 } catch (ProtocolUntrustedIdentityException e
) {
1022 // TODO We don't get the new untrusted identity from ProtocolUntrustedIdentityException anymore ... we need to get it from somewhere else
1023 // account.getSignalProtocolStore().saveIdentity(e.getSender(), e.getUntrustedIdentity(), TrustLevel.UNTRUSTED);
1028 private void handleEndSession(String source
) {
1029 account
.getSignalProtocolStore().deleteAllSessions(source
);
1032 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, String source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1034 if (message
.getGroupInfo().isPresent()) {
1035 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1036 threadId
= Base64
.encodeBytes(groupInfo
.getGroupId());
1037 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1038 switch (groupInfo
.getType()) {
1040 if (group
== null) {
1041 group
= new GroupInfo(groupInfo
.getGroupId());
1044 if (groupInfo
.getAvatar().isPresent()) {
1045 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1046 if (avatar
.isPointer()) {
1048 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1049 } catch (IOException
| InvalidMessageException e
) {
1050 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getId() + "): " + e
.getMessage());
1055 if (groupInfo
.getName().isPresent()) {
1056 group
.name
= groupInfo
.getName().get();
1059 if (groupInfo
.getMembers().isPresent()) {
1060 group
.addMembers(groupInfo
.getMembers().get());
1063 account
.getGroupStore().updateGroup(group
);
1066 if (group
== null) {
1068 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1069 } catch (IOException
| EncapsulatedExceptions e
) {
1070 e
.printStackTrace();
1075 if (group
== null) {
1077 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1078 } catch (IOException
| EncapsulatedExceptions e
) {
1079 e
.printStackTrace();
1082 group
.members
.remove(source
);
1083 account
.getGroupStore().updateGroup(group
);
1087 if (group
!= null) {
1089 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1090 } catch (IOException
| EncapsulatedExceptions e
) {
1091 e
.printStackTrace();
1092 } catch (NotAGroupMemberException e
) {
1093 // We have left this group, so don't send a group update message
1100 threadId
= destination
.getNumber().get();
1105 if (message
.isEndSession()) {
1106 handleEndSession(isSync ? destination
.getNumber().get() : source
);
1108 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1109 ThreadInfo thread
= account
.getThreadStore().getThread(threadId
);
1110 if (thread
== null) {
1111 thread
= new ThreadInfo();
1112 thread
.id
= threadId
;
1114 if (thread
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1115 thread
.messageExpirationTime
= message
.getExpiresInSeconds();
1116 account
.getThreadStore().updateThread(thread
);
1119 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1120 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1121 if (attachment
.isPointer()) {
1123 retrieveAttachment(attachment
.asPointer());
1124 } catch (IOException
| InvalidMessageException e
) {
1125 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getId() + "): " + e
.getMessage());
1130 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1131 if (source
.equals(username
)) {
1133 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1134 } catch (InvalidInputException ignored
) {
1137 ContactInfo contact
= account
.getContactStore().getContact(source
);
1138 if (contact
== null) {
1139 contact
= new ContactInfo();
1140 contact
.number
= source
;
1142 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1144 if (message
.getPreviews().isPresent()) {
1145 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1146 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1147 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1148 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1150 retrieveAttachment(attachment
);
1151 } catch (IOException
| InvalidMessageException e
) {
1152 System
.err
.println("Failed to retrieve attachment (" + attachment
.getId() + "): " + e
.getMessage());
1159 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1160 final File cachePath
= new File(getMessageCachePath());
1161 if (!cachePath
.exists()) {
1164 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1165 if (!dir
.isDirectory()) {
1169 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1170 if (!fileEntry
.isFile()) {
1173 SignalServiceEnvelope envelope
;
1175 envelope
= Utils
.loadEnvelope(fileEntry
);
1176 if (envelope
== null) {
1179 } catch (IOException e
) {
1180 e
.printStackTrace();
1183 SignalServiceContent content
= null;
1184 if (!envelope
.isReceipt()) {
1186 content
= decryptMessage(envelope
);
1187 } catch (Exception e
) {
1190 handleMessage(envelope
, content
, ignoreAttachments
);
1193 handler
.handleMessage(envelope
, content
, null);
1195 Files
.delete(fileEntry
.toPath());
1196 } catch (IOException e
) {
1197 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1200 // Try to delete directory if empty
1205 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1206 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1207 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1210 if (messagePipe
== null) {
1211 messagePipe
= messageReceiver
.createMessagePipe();
1215 SignalServiceEnvelope envelope
;
1216 SignalServiceContent content
= null;
1217 Exception exception
= null;
1218 final long now
= new Date().getTime();
1220 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1221 // store message on disk, before acknowledging receipt to the server
1223 File cacheFile
= getMessageCacheFile(envelope1
.getSourceE164().get(), now
, envelope1
.getTimestamp());
1224 Utils
.storeEnvelope(envelope1
, cacheFile
);
1225 } catch (IOException e
) {
1226 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1229 } catch (TimeoutException e
) {
1230 if (returnOnTimeout
)
1233 } catch (InvalidVersionException e
) {
1234 System
.err
.println("Ignoring error: " + e
.getMessage());
1237 if (!envelope
.isReceipt()) {
1239 content
= decryptMessage(envelope
);
1240 } catch (Exception e
) {
1243 handleMessage(envelope
, content
, ignoreAttachments
);
1246 if (!isMessageBlocked(envelope
, content
)) {
1247 handler
.handleMessage(envelope
, content
, exception
);
1249 if (!(exception
instanceof ProtocolUntrustedIdentityException
)) {
1250 File cacheFile
= null;
1252 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1253 Files
.delete(cacheFile
.toPath());
1254 // Try to delete directory if empty
1255 new File(getMessageCachePath()).delete();
1256 } catch (IOException e
) {
1257 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1262 if (messagePipe
!= null) {
1263 messagePipe
.shutdown();
1269 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1270 SignalServiceAddress source
;
1271 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1272 source
= envelope
.getSourceAddress();
1273 } else if (content
!= null) {
1274 source
= content
.getSender();
1278 ContactInfo sourceContact
= getContact(source
.getNumber().get());
1279 if (sourceContact
!= null && sourceContact
.blocked
) {
1283 if (content
!= null && content
.getDataMessage().isPresent()) {
1284 SignalServiceDataMessage message
= content
.getDataMessage().get();
1285 if (message
.getGroupInfo().isPresent()) {
1286 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1287 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1288 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1296 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1297 if (content
!= null) {
1298 SignalServiceAddress sender
;
1299 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1300 sender
= envelope
.getSourceAddress();
1302 sender
= content
.getSender();
1304 if (content
.getDataMessage().isPresent()) {
1305 SignalServiceDataMessage message
= content
.getDataMessage().get();
1306 handleSignalServiceDataMessage(message
, false, sender
.getNumber().get(), getSelfAddress(), ignoreAttachments
);
1308 if (content
.getSyncMessage().isPresent()) {
1309 account
.setMultiDevice(true);
1310 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1311 if (syncMessage
.getSent().isPresent()) {
1312 SentTranscriptMessage message
= syncMessage
.getSent().get();
1313 handleSignalServiceDataMessage(message
.getMessage(), true, sender
.getNumber().get(), message
.getDestination().orNull(), ignoreAttachments
);
1315 if (syncMessage
.getRequest().isPresent()) {
1316 RequestMessage rm
= syncMessage
.getRequest().get();
1317 if (rm
.isContactsRequest()) {
1320 } catch (UntrustedIdentityException
| IOException e
) {
1321 e
.printStackTrace();
1324 if (rm
.isGroupsRequest()) {
1327 } catch (UntrustedIdentityException
| IOException e
) {
1328 e
.printStackTrace();
1331 if (rm
.isBlockedListRequest()) {
1334 } catch (UntrustedIdentityException
| IOException e
) {
1335 e
.printStackTrace();
1338 // TODO Handle rm.isConfigurationRequest();
1340 if (syncMessage
.getGroups().isPresent()) {
1341 File tmpFile
= null;
1343 tmpFile
= IOUtils
.createTempFile();
1344 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1345 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1347 while ((g
= s
.read()) != null) {
1348 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1349 if (syncGroup
== null) {
1350 syncGroup
= new GroupInfo(g
.getId());
1352 if (g
.getName().isPresent()) {
1353 syncGroup
.name
= g
.getName().get();
1355 syncGroup
.addMembers(g
.getMembers());
1356 syncGroup
.active
= g
.isActive();
1357 syncGroup
.blocked
= g
.isBlocked();
1358 if (g
.getColor().isPresent()) {
1359 syncGroup
.color
= g
.getColor().get();
1362 if (g
.getAvatar().isPresent()) {
1363 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1365 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1366 syncGroup
.archived
= g
.isArchived();
1367 account
.getGroupStore().updateGroup(syncGroup
);
1370 } catch (Exception e
) {
1371 e
.printStackTrace();
1373 if (tmpFile
!= null) {
1375 Files
.delete(tmpFile
.toPath());
1376 } catch (IOException e
) {
1377 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1382 if (syncMessage
.getBlockedList().isPresent()) {
1383 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1384 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1385 if (address
.getNumber().isPresent()) {
1387 setContactBlocked(address
.getNumber().get(), true);
1388 } catch (InvalidNumberException e
) {
1389 e
.printStackTrace();
1393 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1395 setGroupBlocked(groupId
, true);
1396 } catch (GroupNotFoundException e
) {
1397 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1401 if (syncMessage
.getContacts().isPresent()) {
1402 File tmpFile
= null;
1404 tmpFile
= IOUtils
.createTempFile();
1405 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1406 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1407 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1408 if (contactsMessage
.isComplete()) {
1409 account
.getContactStore().clear();
1412 while ((c
= s
.read()) != null) {
1413 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1414 account
.setProfileKey(c
.getProfileKey().get());
1416 ContactInfo contact
= account
.getContactStore().getContact(c
.getAddress().getNumber().get());
1417 if (contact
== null) {
1418 contact
= new ContactInfo();
1419 contact
.number
= c
.getAddress().getNumber().get();
1421 if (c
.getName().isPresent()) {
1422 contact
.name
= c
.getName().get();
1424 if (c
.getColor().isPresent()) {
1425 contact
.color
= c
.getColor().get();
1427 if (c
.getProfileKey().isPresent()) {
1428 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1430 if (c
.getVerified().isPresent()) {
1431 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1432 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1434 if (c
.getExpirationTimer().isPresent()) {
1435 ThreadInfo thread
= account
.getThreadStore().getThread(c
.getAddress().getNumber().get());
1436 if (thread
== null) {
1437 thread
= new ThreadInfo();
1438 thread
.id
= c
.getAddress().getNumber().get();
1440 thread
.messageExpirationTime
= c
.getExpirationTimer().get();
1441 account
.getThreadStore().updateThread(thread
);
1443 contact
.blocked
= c
.isBlocked();
1444 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1445 contact
.archived
= c
.isArchived();
1446 account
.getContactStore().updateContact(contact
);
1448 if (c
.getAvatar().isPresent()) {
1449 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1453 } catch (Exception e
) {
1454 e
.printStackTrace();
1456 if (tmpFile
!= null) {
1458 Files
.delete(tmpFile
.toPath());
1459 } catch (IOException e
) {
1460 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1465 if (syncMessage
.getVerified().isPresent()) {
1466 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1467 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1469 if (syncMessage
.getConfiguration().isPresent()) {
1476 private File
getContactAvatarFile(String number
) {
1477 return new File(avatarsPath
, "contact-" + number
);
1480 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
{
1481 IOUtils
.createPrivateDirectories(avatarsPath
);
1482 if (attachment
.isPointer()) {
1483 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1484 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1486 SignalServiceAttachmentStream stream
= attachment
.asStream();
1487 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1491 private File
getGroupAvatarFile(byte[] groupId
) {
1492 return new File(avatarsPath
, "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1495 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
{
1496 IOUtils
.createPrivateDirectories(avatarsPath
);
1497 if (attachment
.isPointer()) {
1498 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1499 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1501 SignalServiceAttachmentStream stream
= attachment
.asStream();
1502 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1506 public File
getAttachmentFile(long attachmentId
) {
1507 return new File(attachmentsPath
, attachmentId
+ "");
1510 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
{
1511 IOUtils
.createPrivateDirectories(attachmentsPath
);
1512 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getId()), true);
1515 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
{
1516 if (storePreview
&& pointer
.getPreview().isPresent()) {
1517 File previewFile
= new File(outputFile
+ ".preview");
1518 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1519 byte[] preview
= pointer
.getPreview().get();
1520 output
.write(preview
, 0, preview
.length
);
1521 } catch (FileNotFoundException e
) {
1522 e
.printStackTrace();
1527 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1529 File tmpFile
= IOUtils
.createTempFile();
1530 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
)) {
1531 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1532 byte[] buffer
= new byte[4096];
1535 while ((read
= input
.read(buffer
)) != -1) {
1536 output
.write(buffer
, 0, read
);
1538 } catch (FileNotFoundException e
) {
1539 e
.printStackTrace();
1544 Files
.delete(tmpFile
.toPath());
1545 } catch (IOException e
) {
1546 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1552 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
{
1553 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1554 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
);
1558 public boolean isRemote() {
1562 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1563 File groupsFile
= IOUtils
.createTempFile();
1566 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1567 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1568 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1569 ThreadInfo info
= account
.getThreadStore().getThread(Base64
.encodeBytes(record.groupId
));
1570 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1571 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1572 record.active
, Optional
.fromNullable(info
!= null ? info
.messageExpirationTime
: null),
1573 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1577 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1578 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1579 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1580 .withStream(groupsFileStream
)
1581 .withContentType("application/octet-stream")
1582 .withLength(groupsFile
.length())
1585 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1590 Files
.delete(groupsFile
.toPath());
1591 } catch (IOException e
) {
1592 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1597 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1598 File contactsFile
= IOUtils
.createTempFile();
1601 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1602 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1603 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1604 VerifiedMessage verifiedMessage
= null;
1605 ThreadInfo info
= account
.getThreadStore().getThread(record.number
);
1606 if (getIdentities().containsKey(record.number
)) {
1607 JsonIdentityKeyStore
.Identity currentIdentity
= null;
1608 for (JsonIdentityKeyStore
.Identity id
: getIdentities().get(record.number
)) {
1609 if (currentIdentity
== null || id
.getDateAdded().after(currentIdentity
.getDateAdded())) {
1610 currentIdentity
= id
;
1613 if (currentIdentity
!= null) {
1614 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1618 ProfileKey profileKey
= null;
1620 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1621 } catch (InvalidInputException ignored
) {
1623 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1624 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1625 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1626 Optional
.fromNullable(info
!= null ? info
.messageExpirationTime
: null),
1627 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1630 if (account
.getProfileKey() != null) {
1631 // Send our own profile key as well
1632 out
.write(new DeviceContact(account
.getSelfAddress(),
1633 Optional
.absent(), Optional
.absent(),
1634 Optional
.absent(), Optional
.absent(),
1635 Optional
.of(account
.getProfileKey()),
1636 false, Optional
.absent(), Optional
.absent(), false));
1640 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1641 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1642 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1643 .withStream(contactsFileStream
)
1644 .withContentType("application/octet-stream")
1645 .withLength(contactsFile
.length())
1648 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1653 Files
.delete(contactsFile
.toPath());
1654 } catch (IOException e
) {
1655 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1660 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1661 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1662 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1663 if (record.blocked
) {
1664 addresses
.add(record.getAddress());
1667 List
<byte[]> groupIds
= new ArrayList
<>();
1668 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1669 if (record.blocked
) {
1670 groupIds
.add(record.groupId
);
1673 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1676 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1677 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1678 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1681 public List
<ContactInfo
> getContacts() {
1682 return account
.getContactStore().getContacts();
1685 public ContactInfo
getContact(String number
) {
1686 return account
.getContactStore().getContact(number
);
1689 public GroupInfo
getGroup(byte[] groupId
) {
1690 return account
.getGroupStore().getGroup(groupId
);
1693 public Map
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities() {
1694 return account
.getSignalProtocolStore().getIdentities();
1697 public Pair
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities(String number
) throws InvalidNumberException
{
1698 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, username
);
1699 return new Pair
<>(canonicalizedNumber
, account
.getSignalProtocolStore().getIdentities(canonicalizedNumber
));
1703 * Trust this the identity with this fingerprint
1705 * @param name username of the identity
1706 * @param fingerprint Fingerprint
1708 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) {
1709 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1713 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1714 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1718 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1720 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1721 } catch (IOException
| UntrustedIdentityException e
) {
1722 e
.printStackTrace();
1731 * Trust this the identity with this safety number
1733 * @param name username of the identity
1734 * @param safetyNumber Safety number
1736 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) {
1737 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1741 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1742 if (!safetyNumber
.equals(computeSafetyNumber(name
, id
.getIdentityKey()))) {
1746 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1748 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1749 } catch (IOException
| UntrustedIdentityException e
) {
1750 e
.printStackTrace();
1759 * Trust all keys of this identity without verification
1761 * @param name username of the identity
1763 public boolean trustIdentityAllKeys(String name
) {
1764 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1768 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1769 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1770 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1772 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1773 } catch (IOException
| UntrustedIdentityException e
) {
1774 e
.printStackTrace();
1782 public String
computeSafetyNumber(String theirUsername
, IdentityKey theirIdentityKey
) {
1783 return Utils
.computeSafetyNumber(username
, getIdentity(), theirUsername
, theirIdentityKey
);
1786 public interface ReceiveMessageHandler
{
1788 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);