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
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
45 import org
.signal
.zkgroup
.InvalidInputException
;
46 import org
.signal
.zkgroup
.VerificationFailedException
;
47 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
48 import org
.whispersystems
.libsignal
.IdentityKey
;
49 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
50 import org
.whispersystems
.libsignal
.InvalidKeyException
;
51 import org
.whispersystems
.libsignal
.InvalidMessageException
;
52 import org
.whispersystems
.libsignal
.InvalidVersionException
;
53 import org
.whispersystems
.libsignal
.ecc
.Curve
;
54 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
55 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
56 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
57 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
58 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
59 import org
.whispersystems
.libsignal
.util
.Medium
;
60 import org
.whispersystems
.libsignal
.util
.Pair
;
61 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
62 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
63 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
64 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
65 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
66 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
67 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
68 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
69 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
70 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
71 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
72 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
73 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
74 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
79 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
80 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
81 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
82 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
83 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
84 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
85 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
92 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
93 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
94 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
95 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
96 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.AuthorizationFailedException
;
97 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
98 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
99 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
100 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
101 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
102 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
103 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
104 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
105 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
106 import org
.whispersystems
.util
.Base64
;
109 import java
.io
.FileInputStream
;
110 import java
.io
.FileNotFoundException
;
111 import java
.io
.FileOutputStream
;
112 import java
.io
.IOException
;
113 import java
.io
.InputStream
;
114 import java
.io
.OutputStream
;
116 import java
.nio
.file
.Files
;
117 import java
.nio
.file
.Paths
;
118 import java
.nio
.file
.StandardCopyOption
;
119 import java
.util
.ArrayList
;
120 import java
.util
.Arrays
;
121 import java
.util
.Collection
;
122 import java
.util
.Collections
;
123 import java
.util
.Date
;
124 import java
.util
.HashSet
;
125 import java
.util
.LinkedList
;
126 import java
.util
.List
;
127 import java
.util
.Locale
;
128 import java
.util
.Map
;
129 import java
.util
.Objects
;
130 import java
.util
.Set
;
131 import java
.util
.concurrent
.TimeUnit
;
132 import java
.util
.concurrent
.TimeoutException
;
134 public class Manager
implements Signal
{
136 private static final SignalServiceProfile
.Capabilities capabilities
= new SignalServiceProfile
.Capabilities(false, false);
138 private final String settingsPath
;
139 private final String dataPath
;
140 private final String attachmentsPath
;
141 private final String avatarsPath
;
142 private final SleepTimer timer
= new UptimeSleepTimer();
144 private SignalAccount account
;
145 private String username
;
146 private SignalServiceAccountManager accountManager
;
147 private SignalServiceMessagePipe messagePipe
= null;
148 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
150 public Manager(String username
, String settingsPath
) {
151 this.username
= username
;
152 this.settingsPath
= settingsPath
;
153 this.dataPath
= this.settingsPath
+ "/data";
154 this.attachmentsPath
= this.settingsPath
+ "/attachments";
155 this.avatarsPath
= this.settingsPath
+ "/avatars";
159 public String
getUsername() {
163 private SignalServiceAddress
getSelfAddress() {
164 return new SignalServiceAddress(null, username
);
167 private SignalServiceAccountManager
getSignalServiceAccountManager() {
168 return new SignalServiceAccountManager(BaseConfig
.serviceConfiguration
, null, account
.getUsername(), account
.getPassword(), account
.getDeviceId(), BaseConfig
.USER_AGENT
, timer
);
171 private IdentityKey
getIdentity() {
172 return account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey();
175 public int getDeviceId() {
176 return account
.getDeviceId();
179 private String
getMessageCachePath() {
180 return this.dataPath
+ "/" + username
+ ".d/msg-cache";
183 private String
getMessageCachePath(String sender
) {
184 return getMessageCachePath() + "/" + sender
.replace("/", "_");
187 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
188 String cachePath
= getMessageCachePath(sender
);
189 IOUtils
.createPrivateDirectories(cachePath
);
190 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
193 public boolean userHasKeys() {
194 return account
!= null && account
.getSignalProtocolStore() != null;
197 public void init() throws IOException
{
198 if (!SignalAccount
.userExists(dataPath
, username
)) {
201 account
= SignalAccount
.load(dataPath
, username
);
203 migrateLegacyConfigs();
205 accountManager
= getSignalServiceAccountManager();
207 if (account
.isRegistered() && accountManager
.getPreKeysCount() < BaseConfig
.PREKEY_MINIMUM_COUNT
) {
211 } catch (AuthorizationFailedException e
) {
212 System
.err
.println("Authorization failed, was the number registered elsewhere?");
217 private void migrateLegacyConfigs() {
218 // Copy group avatars that were previously stored in the attachments folder
219 // to the new avatar folder
220 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
221 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
222 File avatarFile
= getGroupAvatarFile(g
.groupId
);
223 File attachmentFile
= getAttachmentFile(g
.getAvatarId());
224 if (!avatarFile
.exists() && attachmentFile
.exists()) {
226 IOUtils
.createPrivateDirectories(avatarsPath
);
227 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
228 } catch (Exception e
) {
233 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
236 if (account
.getProfileKey() == null) {
237 // Old config file, creating new profile key
238 account
.setProfileKey(KeyUtils
.createProfileKey());
243 private void createNewIdentity() throws IOException
{
244 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
245 int registrationId
= KeyHelper
.generateRegistrationId(false);
246 if (username
== null) {
247 account
= SignalAccount
.createTemporaryAccount(identityKey
, registrationId
);
249 ProfileKey profileKey
= KeyUtils
.createProfileKey();
250 account
= SignalAccount
.create(dataPath
, username
, identityKey
, registrationId
, profileKey
);
255 public boolean isRegistered() {
256 return account
!= null && account
.isRegistered();
259 public void register(boolean voiceVerification
) throws IOException
{
260 if (account
== null) {
263 account
.setPassword(KeyUtils
.createPassword());
264 accountManager
= getSignalServiceAccountManager();
266 if (voiceVerification
) {
267 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
269 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
272 account
.setRegistered(false);
276 public void updateAccountAttributes() throws IOException
{
277 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities
);
280 public void setProfileName(String name
) throws IOException
{
281 accountManager
.setProfileName(account
.getProfileKey(), name
);
284 public void setProfileAvatar(File avatar
) throws IOException
{
285 final StreamDetails streamDetails
= Utils
.createStreamDetailsFromFile(avatar
);
286 accountManager
.setProfileAvatar(account
.getProfileKey(), streamDetails
);
287 streamDetails
.getStream().close();
290 public void removeProfileAvatar() throws IOException
{
291 accountManager
.setProfileAvatar(account
.getProfileKey(), null);
294 public void unregister() throws IOException
{
295 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
296 // If this is the master device, other users can't send messages to this number anymore.
297 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
298 accountManager
.setGcmId(Optional
.absent());
300 account
.setRegistered(false);
304 public String
getDeviceLinkUri() throws TimeoutException
, IOException
{
305 if (account
== null) {
308 account
.setPassword(KeyUtils
.createPassword());
309 accountManager
= getSignalServiceAccountManager();
310 String uuid
= accountManager
.getNewDeviceUuid();
312 return Utils
.createDeviceLinkUri(new Utils
.DeviceLinkInfo(uuid
, getIdentity().getPublicKey()));
315 public void finishDeviceLink(String deviceName
) throws IOException
, InvalidKeyException
, TimeoutException
, UserAlreadyExists
{
316 account
.setSignalingKey(KeyUtils
.createSignalingKey());
317 SignalServiceAccountManager
.NewDeviceRegistrationReturn ret
= accountManager
.finishNewDeviceRegistration(account
.getSignalProtocolStore().getIdentityKeyPair(), account
.getSignalingKey(), false, true, account
.getSignalProtocolStore().getLocalRegistrationId(), deviceName
);
319 username
= ret
.getNumber();
320 // TODO do this check before actually registering
321 if (SignalAccount
.userExists(dataPath
, username
)) {
322 throw new UserAlreadyExists(username
, SignalAccount
.getFileName(dataPath
, username
));
325 // Create new account with the synced identity
326 byte[] profileKeyBytes
= ret
.getProfileKey();
327 ProfileKey profileKey
;
328 if (profileKeyBytes
== null) {
329 profileKey
= KeyUtils
.createProfileKey();
332 profileKey
= new ProfileKey(profileKeyBytes
);
333 } catch (InvalidInputException e
) {
334 throw new IOException("Received invalid profileKey", e
);
337 account
= SignalAccount
.createLinkedAccount(dataPath
, username
, account
.getPassword(), ret
.getDeviceId(), ret
.getIdentity(), account
.getSignalProtocolStore().getLocalRegistrationId(), account
.getSignalingKey(), profileKey
);
342 requestSyncContacts();
343 requestSyncBlocked();
344 requestSyncConfiguration();
349 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
350 List
<DeviceInfo
> devices
= accountManager
.getDevices();
351 account
.setMultiDevice(devices
.size() > 1);
356 public void removeLinkedDevices(int deviceId
) throws IOException
{
357 accountManager
.removeDevice(deviceId
);
358 List
<DeviceInfo
> devices
= accountManager
.getDevices();
359 account
.setMultiDevice(devices
.size() > 1);
363 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
364 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
366 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
369 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
370 IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
371 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
373 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
374 account
.setMultiDevice(true);
378 private List
<PreKeyRecord
> generatePreKeys() {
379 List
<PreKeyRecord
> records
= new ArrayList
<>(BaseConfig
.PREKEY_BATCH_SIZE
);
381 final int offset
= account
.getPreKeyIdOffset();
382 for (int i
= 0; i
< BaseConfig
.PREKEY_BATCH_SIZE
; i
++) {
383 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
384 ECKeyPair keyPair
= Curve
.generateKeyPair();
385 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
390 account
.addPreKeys(records
);
396 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
398 ECKeyPair keyPair
= Curve
.generateKeyPair();
399 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
400 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
402 account
.addSignedPreKey(record);
406 } catch (InvalidKeyException e
) {
407 throw new AssertionError(e
);
411 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
412 verificationCode
= verificationCode
.replace("-", "");
413 account
.setSignalingKey(KeyUtils
.createSignalingKey());
414 // TODO make unrestricted unidentified access configurable
415 accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, capabilities
);
417 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
418 account
.setRegistered(true);
419 account
.setRegistrationLockPin(pin
);
425 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
426 if (pin
.isPresent()) {
427 account
.setRegistrationLockPin(pin
.get());
428 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
430 account
.setRegistrationLockPin(null);
431 accountManager
.removeV1Pin();
436 private void refreshPreKeys() throws IOException
{
437 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
438 final IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
439 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
441 accountManager
.setPreKeys(getIdentity(), signedPreKeyRecord
, oneTimePreKeys
);
444 private SignalServiceMessageReceiver
getMessageReceiver() {
445 return new SignalServiceMessageReceiver(BaseConfig
.serviceConfiguration
, null, username
, account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), BaseConfig
.USER_AGENT
, null, timer
);
448 private SignalServiceMessageSender
getMessageSender() {
449 return new SignalServiceMessageSender(BaseConfig
.serviceConfiguration
, null, username
, account
.getPassword(),
450 account
.getDeviceId(), account
.getSignalProtocolStore(), BaseConfig
.USER_AGENT
, account
.isMultiDevice(), Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent());
453 private SignalServiceProfile
getRecipientProfile(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
).getProfile();
460 } catch (IOException ignored
) {
464 SignalServiceMessageReceiver receiver
= getMessageReceiver();
466 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
467 } catch (VerificationFailedException e
) {
468 throw new AssertionError(e
);
472 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
473 File file
= getGroupAvatarFile(groupId
);
474 if (!file
.exists()) {
475 return Optional
.absent();
478 return Optional
.of(Utils
.createAttachment(file
));
481 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
482 File file
= getContactAvatarFile(number
);
483 if (!file
.exists()) {
484 return Optional
.absent();
487 return Optional
.of(Utils
.createAttachment(file
));
490 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
491 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
493 throw new GroupNotFoundException(groupId
);
495 for (String member
: g
.members
) {
496 if (member
.equals(this.username
)) {
500 throw new NotAGroupMemberException(groupId
, g
.name
);
503 public List
<GroupInfo
> getGroups() {
504 return account
.getGroupStore().getGroups();
508 public void sendGroupMessage(String messageText
, List
<String
> attachments
,
510 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
511 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
512 if (attachments
!= null) {
513 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
515 if (groupId
!= null) {
516 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
519 messageBuilder
.asGroupMessage(group
);
521 ThreadInfo thread
= account
.getThreadStore().getThread(Base64
.encodeBytes(groupId
));
522 if (thread
!= null) {
523 messageBuilder
.withExpiration(thread
.messageExpirationTime
);
526 final GroupInfo g
= getGroupForSending(groupId
);
528 // Don't send group message to ourself
529 final List
<String
> membersSend
= new ArrayList
<>(g
.members
);
530 membersSend
.remove(this.username
);
531 sendMessageLegacy(messageBuilder
, membersSend
);
534 public void sendGroupMessageReaction(String emoji
, boolean remove
, SignalServiceAddress targetAuthor
,
535 long targetSentTimestamp
, byte[] groupId
)
536 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
{
537 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
);
538 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
539 .withReaction(reaction
)
540 .withProfileKey(account
.getProfileKey().serialize());
541 if (groupId
!= null) {
542 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
545 messageBuilder
.asGroupMessage(group
);
547 final GroupInfo g
= getGroupForSending(groupId
);
548 // Don't send group message to ourself
549 final List
<String
> membersSend
= new ArrayList
<>(g
.members
);
550 membersSend
.remove(this.username
);
551 sendMessageLegacy(messageBuilder
, membersSend
);
554 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
{
555 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
559 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
560 .asGroupMessage(group
);
562 final GroupInfo g
= getGroupForSending(groupId
);
563 g
.members
.remove(this.username
);
564 account
.getGroupStore().updateGroup(g
);
566 sendMessageLegacy(messageBuilder
, g
.members
);
569 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<String
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
571 if (groupId
== null) {
573 g
= new GroupInfo(KeyUtils
.createGroupId());
574 g
.members
.add(username
);
576 g
= getGroupForSending(groupId
);
583 if (members
!= null) {
584 Set
<String
> newMembers
= new HashSet
<>();
585 for (String member
: members
) {
587 member
= Utils
.canonicalizeNumber(member
, username
);
588 } catch (InvalidNumberException e
) {
589 System
.err
.println("Failed to add member \"" + member
+ "\" to group: " + e
.getMessage());
590 System
.err
.println("Aborting…");
593 if (g
.members
.contains(member
)) {
596 newMembers
.add(member
);
597 g
.members
.add(member
);
599 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newMembers
);
600 if (contacts
.size() != newMembers
.size()) {
601 // Some of the new members are not registered on Signal
602 for (ContactTokenDetails contact
: contacts
) {
603 newMembers
.remove(contact
.getNumber());
605 System
.err
.println("Failed to add members " + Util
.join(", ", newMembers
) + " to group: Not registered on Signal");
606 System
.err
.println("Aborting…");
611 if (avatarFile
!= null) {
612 IOUtils
.createPrivateDirectories(avatarsPath
);
613 File aFile
= getGroupAvatarFile(g
.groupId
);
614 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
617 account
.getGroupStore().updateGroup(g
);
619 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
621 // Don't send group message to ourself
622 final List
<String
> membersSend
= new ArrayList
<>(g
.members
);
623 membersSend
.remove(this.username
);
624 sendMessageLegacy(messageBuilder
, membersSend
);
628 private void sendUpdateGroupMessage(byte[] groupId
, String recipient
) throws IOException
, EncapsulatedExceptions
{
629 if (groupId
== null) {
632 GroupInfo g
= getGroupForSending(groupId
);
634 if (!g
.members
.contains(recipient
)) {
638 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
640 // Send group message only to the recipient who requested it
641 final List
<String
> membersSend
= new ArrayList
<>();
642 membersSend
.add(recipient
);
643 sendMessageLegacy(messageBuilder
, membersSend
);
646 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) {
647 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
650 .withMembers(new ArrayList
<>(g
.getMembers()));
652 File aFile
= getGroupAvatarFile(g
.groupId
);
653 if (aFile
.exists()) {
655 group
.withAvatar(Utils
.createAttachment(aFile
));
656 } catch (IOException e
) {
657 throw new AttachmentInvalidException(aFile
.toString(), e
);
661 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
662 .asGroupMessage(group
.build());
664 ThreadInfo thread
= account
.getThreadStore().getThread(Base64
.encodeBytes(g
.groupId
));
665 if (thread
!= null) {
666 messageBuilder
.withExpiration(thread
.messageExpirationTime
);
669 return messageBuilder
;
672 private void sendGroupInfoRequest(byte[] groupId
, String recipient
) throws IOException
, EncapsulatedExceptions
{
673 if (groupId
== null) {
677 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
680 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
681 .asGroupMessage(group
.build());
683 ThreadInfo thread
= account
.getThreadStore().getThread(Base64
.encodeBytes(groupId
));
684 if (thread
!= null) {
685 messageBuilder
.withExpiration(thread
.messageExpirationTime
);
688 // Send group info request message to the recipient who sent us a message with this groupId
689 final List
<String
> membersSend
= new ArrayList
<>();
690 membersSend
.add(recipient
);
691 sendMessageLegacy(messageBuilder
, membersSend
);
695 public void sendMessage(String message
, List
<String
> attachments
, String recipient
)
696 throws EncapsulatedExceptions
, AttachmentInvalidException
, IOException
{
697 List
<String
> recipients
= new ArrayList
<>(1);
698 recipients
.add(recipient
);
699 sendMessage(message
, attachments
, recipients
);
703 public void sendMessage(String messageText
, List
<String
> attachments
,
704 List
<String
> recipients
)
705 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
{
706 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
707 if (attachments
!= null) {
708 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
710 // Upload attachments here, so we only upload once even for multiple recipients
711 SignalServiceMessageSender messageSender
= getMessageSender();
712 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
713 for (SignalServiceAttachment attachment
: attachmentStreams
) {
714 if (attachment
.isStream()) {
715 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
716 } else if (attachment
.isPointer()) {
717 attachmentPointers
.add(attachment
.asPointer());
721 messageBuilder
.withAttachments(attachmentPointers
);
723 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
724 sendMessageLegacy(messageBuilder
, recipients
);
727 public void sendMessageReaction(String emoji
, boolean remove
, SignalServiceAddress targetAuthor
,
728 long targetSentTimestamp
, List
<String
> recipients
)
729 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
{
730 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
);
731 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
732 .withReaction(reaction
)
733 .withProfileKey(account
.getProfileKey().serialize());
734 sendMessageLegacy(messageBuilder
, recipients
);
738 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
{
739 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
740 .asEndSessionMessage();
742 sendMessageLegacy(messageBuilder
, recipients
);
746 public String
getContactName(String number
) throws InvalidNumberException
{
747 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, username
);
748 ContactInfo contact
= account
.getContactStore().getContact(canonicalizedNumber
);
749 if (contact
== null) {
757 public void setContactName(String number
, String name
) throws InvalidNumberException
{
758 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, username
);
759 ContactInfo contact
= account
.getContactStore().getContact(canonicalizedNumber
);
760 if (contact
== null) {
761 contact
= new ContactInfo();
762 contact
.number
= canonicalizedNumber
;
763 System
.err
.println("Add contact " + canonicalizedNumber
+ " named " + name
);
765 System
.err
.println("Updating contact " + canonicalizedNumber
+ " name " + contact
.name
+ " -> " + name
);
768 account
.getContactStore().updateContact(contact
);
773 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
774 number
= Utils
.canonicalizeNumber(number
, username
);
775 ContactInfo contact
= account
.getContactStore().getContact(number
);
776 if (contact
== null) {
777 contact
= new ContactInfo();
778 contact
.number
= number
;
779 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + number
);
781 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + number
);
783 contact
.blocked
= blocked
;
784 account
.getContactStore().updateContact(contact
);
789 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
790 GroupInfo group
= getGroup(groupId
);
792 throw new GroupNotFoundException(groupId
);
794 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
795 group
.blocked
= blocked
;
796 account
.getGroupStore().updateGroup(group
);
802 public List
<byte[]> getGroupIds() {
803 List
<GroupInfo
> groups
= getGroups();
804 List
<byte[]> ids
= new ArrayList
<>(groups
.size());
805 for (GroupInfo group
: groups
) {
806 ids
.add(group
.groupId
);
812 public String
getGroupName(byte[] groupId
) {
813 GroupInfo group
= getGroup(groupId
);
822 public List
<String
> getGroupMembers(byte[] groupId
) {
823 GroupInfo group
= getGroup(groupId
);
825 return new ArrayList
<>();
827 return new ArrayList
<>(group
.members
);
832 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
833 if (groupId
.length
== 0) {
836 if (name
.isEmpty()) {
839 if (members
.size() == 0) {
842 if (avatar
.isEmpty()) {
845 return sendUpdateGroupMessage(groupId
, name
, members
, avatar
);
849 * Change the expiration timer for a thread (number of groupId)
851 * @param numberOrGroupId
852 * @param messageExpirationTimer
854 public void setExpirationTimer(String numberOrGroupId
, int messageExpirationTimer
) {
855 ThreadInfo thread
= account
.getThreadStore().getThread(numberOrGroupId
);
856 thread
.messageExpirationTime
= messageExpirationTimer
;
857 account
.getThreadStore().updateThread(thread
);
860 private void requestSyncGroups() throws IOException
{
861 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
862 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
864 sendSyncMessage(message
);
865 } catch (UntrustedIdentityException e
) {
870 private void requestSyncContacts() throws IOException
{
871 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
872 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
874 sendSyncMessage(message
);
875 } catch (UntrustedIdentityException e
) {
880 private void requestSyncBlocked() throws IOException
{
881 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
882 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
884 sendSyncMessage(message
);
885 } catch (UntrustedIdentityException e
) {
890 private void requestSyncConfiguration() throws IOException
{
891 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
892 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
894 sendSyncMessage(message
);
895 } catch (UntrustedIdentityException e
) {
900 private byte[] getSenderCertificate() throws IOException
{
901 byte[] certificate
= accountManager
.getSenderCertificate();
902 // TODO cache for a day
906 private byte[] getSelfUnidentifiedAccessKey() {
907 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
910 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
911 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
913 return new SignalProfile(
914 encryptedProfile
.getIdentityKey(),
915 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
916 encryptedProfile
.getAvatar(),
917 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
918 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
920 } catch (InvalidCiphertextException e
) {
925 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) throws IOException
{
926 ContactInfo contact
= account
.getContactStore().getContact(recipient
.getNumber().get());
927 if (contact
== null || contact
.profileKey
== null) {
930 ProfileKey theirProfileKey
;
932 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
933 } catch (InvalidInputException e
) {
934 throw new AssertionError(e
);
936 SignalProfile targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
938 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
942 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
943 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
946 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
949 private Optional
<UnidentifiedAccessPair
> getAccessForSync() throws IOException
{
950 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
951 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
953 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
954 return Optional
.absent();
958 return Optional
.of(new UnidentifiedAccessPair(
959 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
960 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
962 } catch (InvalidCertificateException e
) {
963 return Optional
.absent();
967 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) throws IOException
{
968 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
969 for (SignalServiceAddress recipient
: recipients
) {
970 result
.add(getAccessFor(recipient
));
975 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) throws IOException
{
976 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
977 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
978 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
980 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
981 return Optional
.absent();
985 return Optional
.of(new UnidentifiedAccessPair(
986 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
987 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
989 } catch (InvalidCertificateException e
) {
990 return Optional
.absent();
994 private void sendSyncMessage(SignalServiceSyncMessage message
)
995 throws IOException
, UntrustedIdentityException
{
996 SignalServiceMessageSender messageSender
= getMessageSender();
998 messageSender
.sendMessage(message
, getAccessForSync());
999 } catch (UntrustedIdentityException e
) {
1000 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1006 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1008 private void sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<String
> recipients
)
1009 throws EncapsulatedExceptions
, IOException
{
1010 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1012 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1013 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1014 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1016 for (SendMessageResult result
: results
) {
1017 if (result
.isUnregisteredFailure()) {
1018 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getNumber().get(), null));
1019 } else if (result
.isNetworkFailure()) {
1020 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getNumber().get(), null));
1021 } else if (result
.getIdentityFailure() != null) {
1022 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getNumber().get(), result
.getIdentityFailure().getIdentityKey()));
1025 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1026 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1030 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<String
> recipients
)
1031 throws IOException
{
1032 Set
<SignalServiceAddress
> recipientsTS
= Utils
.getSignalServiceAddresses(recipients
, username
);
1033 if (recipientsTS
== null) {
1035 return Collections
.emptyList();
1038 if (messagePipe
== null) {
1039 messagePipe
= getMessageReceiver().createMessagePipe();
1041 if (unidentifiedMessagePipe
== null) {
1042 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1044 SignalServiceDataMessage message
= null;
1046 SignalServiceMessageSender messageSender
= getMessageSender();
1048 message
= messageBuilder
.build();
1049 if (message
.getGroupInfo().isPresent()) {
1051 final boolean isRecipientUpdate
= false;
1052 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipientsTS
), getAccessFor(recipientsTS
), isRecipientUpdate
, message
);
1053 for (SendMessageResult r
: result
) {
1054 if (r
.getIdentityFailure() != null) {
1055 account
.getSignalProtocolStore().saveIdentity(r
.getAddress().getNumber().get(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1059 } catch (UntrustedIdentityException e
) {
1060 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1061 return Collections
.emptyList();
1063 } else if (recipientsTS
.size() == 1 && recipientsTS
.contains(getSelfAddress())) {
1064 SignalServiceAddress recipient
= getSelfAddress();
1065 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1066 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1067 message
.getTimestamp(),
1069 message
.getExpiresInSeconds(),
1070 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1072 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1074 List
<SendMessageResult
> results
= new ArrayList
<>(recipientsTS
.size());
1076 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1077 } catch (UntrustedIdentityException e
) {
1078 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1079 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1083 // Send to all individually, so sync messages are sent correctly
1084 List
<SendMessageResult
> results
= new ArrayList
<>(recipientsTS
.size());
1085 for (SignalServiceAddress address
: recipientsTS
) {
1086 ThreadInfo thread
= account
.getThreadStore().getThread(address
.getNumber().get());
1087 if (thread
!= null) {
1088 messageBuilder
.withExpiration(thread
.messageExpirationTime
);
1090 messageBuilder
.withExpiration(0);
1092 message
= messageBuilder
.build();
1094 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1095 results
.add(result
);
1096 } catch (UntrustedIdentityException e
) {
1097 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1098 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1104 if (message
!= null && message
.isEndSession()) {
1105 for (SignalServiceAddress recipient
: recipientsTS
) {
1106 handleEndSession(recipient
.getNumber().get());
1113 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, ProtocolUntrustedIdentityException
, SelfSendException
, UnsupportedDataMessageException
{
1114 SignalServiceCipher cipher
= new SignalServiceCipher(getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1116 return cipher
.decrypt(envelope
);
1117 } catch (ProtocolUntrustedIdentityException e
) {
1118 // TODO We don't get the new untrusted identity from ProtocolUntrustedIdentityException anymore ... we need to get it from somewhere else
1119 // account.getSignalProtocolStore().saveIdentity(e.getSender(), e.getUntrustedIdentity(), TrustLevel.UNTRUSTED);
1124 private void handleEndSession(String source
) {
1125 account
.getSignalProtocolStore().deleteAllSessions(source
);
1128 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, String source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1130 if (message
.getGroupInfo().isPresent()) {
1131 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1132 threadId
= Base64
.encodeBytes(groupInfo
.getGroupId());
1133 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1134 switch (groupInfo
.getType()) {
1136 if (group
== null) {
1137 group
= new GroupInfo(groupInfo
.getGroupId());
1140 if (groupInfo
.getAvatar().isPresent()) {
1141 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1142 if (avatar
.isPointer()) {
1144 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1145 } catch (IOException
| InvalidMessageException e
) {
1146 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getId() + "): " + e
.getMessage());
1151 if (groupInfo
.getName().isPresent()) {
1152 group
.name
= groupInfo
.getName().get();
1155 if (groupInfo
.getMembers().isPresent()) {
1156 group
.addMembers(groupInfo
.getMembers().get());
1159 account
.getGroupStore().updateGroup(group
);
1162 if (group
== null) {
1164 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1165 } catch (IOException
| EncapsulatedExceptions e
) {
1166 e
.printStackTrace();
1171 if (group
== null) {
1173 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1174 } catch (IOException
| EncapsulatedExceptions e
) {
1175 e
.printStackTrace();
1178 group
.members
.remove(source
);
1179 account
.getGroupStore().updateGroup(group
);
1183 if (group
!= null) {
1185 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1186 } catch (IOException
| EncapsulatedExceptions e
) {
1187 e
.printStackTrace();
1188 } catch (NotAGroupMemberException e
) {
1189 // We have left this group, so don't send a group update message
1196 threadId
= destination
.getNumber().get();
1201 if (message
.isEndSession()) {
1202 handleEndSession(isSync ? destination
.getNumber().get() : source
);
1204 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1205 ThreadInfo thread
= account
.getThreadStore().getThread(threadId
);
1206 if (thread
== null) {
1207 thread
= new ThreadInfo();
1208 thread
.id
= threadId
;
1210 if (thread
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1211 thread
.messageExpirationTime
= message
.getExpiresInSeconds();
1212 account
.getThreadStore().updateThread(thread
);
1215 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1216 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1217 if (attachment
.isPointer()) {
1219 retrieveAttachment(attachment
.asPointer());
1220 } catch (IOException
| InvalidMessageException e
) {
1221 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getId() + "): " + e
.getMessage());
1226 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1227 if (source
.equals(username
)) {
1229 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1230 } catch (InvalidInputException ignored
) {
1233 ContactInfo contact
= account
.getContactStore().getContact(source
);
1234 if (contact
== null) {
1235 contact
= new ContactInfo();
1236 contact
.number
= source
;
1238 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1239 account
.getContactStore().updateContact(contact
);
1241 if (message
.getPreviews().isPresent()) {
1242 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1243 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1244 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1245 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1247 retrieveAttachment(attachment
);
1248 } catch (IOException
| InvalidMessageException e
) {
1249 System
.err
.println("Failed to retrieve attachment (" + attachment
.getId() + "): " + e
.getMessage());
1256 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1257 final File cachePath
= new File(getMessageCachePath());
1258 if (!cachePath
.exists()) {
1261 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1262 if (!dir
.isDirectory()) {
1266 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1267 if (!fileEntry
.isFile()) {
1270 SignalServiceEnvelope envelope
;
1272 envelope
= Utils
.loadEnvelope(fileEntry
);
1273 if (envelope
== null) {
1276 } catch (IOException e
) {
1277 e
.printStackTrace();
1280 SignalServiceContent content
= null;
1281 if (!envelope
.isReceipt()) {
1283 content
= decryptMessage(envelope
);
1284 } catch (Exception e
) {
1287 handleMessage(envelope
, content
, ignoreAttachments
);
1290 handler
.handleMessage(envelope
, content
, null);
1292 Files
.delete(fileEntry
.toPath());
1293 } catch (IOException e
) {
1294 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1297 // Try to delete directory if empty
1302 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1303 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1304 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1307 if (messagePipe
== null) {
1308 messagePipe
= messageReceiver
.createMessagePipe();
1312 SignalServiceEnvelope envelope
;
1313 SignalServiceContent content
= null;
1314 Exception exception
= null;
1315 final long now
= new Date().getTime();
1317 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1318 // store message on disk, before acknowledging receipt to the server
1320 File cacheFile
= getMessageCacheFile(envelope1
.getSourceE164().get(), now
, envelope1
.getTimestamp());
1321 Utils
.storeEnvelope(envelope1
, cacheFile
);
1322 } catch (IOException e
) {
1323 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1326 } catch (TimeoutException e
) {
1327 if (returnOnTimeout
)
1330 } catch (InvalidVersionException e
) {
1331 System
.err
.println("Ignoring error: " + e
.getMessage());
1334 if (!envelope
.isReceipt()) {
1336 content
= decryptMessage(envelope
);
1337 } catch (Exception e
) {
1340 handleMessage(envelope
, content
, ignoreAttachments
);
1343 if (!isMessageBlocked(envelope
, content
)) {
1344 handler
.handleMessage(envelope
, content
, exception
);
1346 if (!(exception
instanceof ProtocolUntrustedIdentityException
)) {
1347 File cacheFile
= null;
1349 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1350 Files
.delete(cacheFile
.toPath());
1351 // Try to delete directory if empty
1352 new File(getMessageCachePath()).delete();
1353 } catch (IOException e
) {
1354 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1359 if (messagePipe
!= null) {
1360 messagePipe
.shutdown();
1366 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1367 SignalServiceAddress source
;
1368 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1369 source
= envelope
.getSourceAddress();
1370 } else if (content
!= null) {
1371 source
= content
.getSender();
1375 ContactInfo sourceContact
= getContact(source
.getNumber().get());
1376 if (sourceContact
!= null && sourceContact
.blocked
) {
1380 if (content
!= null && content
.getDataMessage().isPresent()) {
1381 SignalServiceDataMessage message
= content
.getDataMessage().get();
1382 if (message
.getGroupInfo().isPresent()) {
1383 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1384 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1385 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1393 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1394 if (content
!= null) {
1395 SignalServiceAddress sender
;
1396 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1397 sender
= envelope
.getSourceAddress();
1399 sender
= content
.getSender();
1401 if (content
.getDataMessage().isPresent()) {
1402 SignalServiceDataMessage message
= content
.getDataMessage().get();
1403 handleSignalServiceDataMessage(message
, false, sender
.getNumber().get(), getSelfAddress(), ignoreAttachments
);
1405 if (content
.getSyncMessage().isPresent()) {
1406 account
.setMultiDevice(true);
1407 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1408 if (syncMessage
.getSent().isPresent()) {
1409 SentTranscriptMessage message
= syncMessage
.getSent().get();
1410 handleSignalServiceDataMessage(message
.getMessage(), true, sender
.getNumber().get(), message
.getDestination().orNull(), ignoreAttachments
);
1412 if (syncMessage
.getRequest().isPresent()) {
1413 RequestMessage rm
= syncMessage
.getRequest().get();
1414 if (rm
.isContactsRequest()) {
1417 } catch (UntrustedIdentityException
| IOException e
) {
1418 e
.printStackTrace();
1421 if (rm
.isGroupsRequest()) {
1424 } catch (UntrustedIdentityException
| IOException e
) {
1425 e
.printStackTrace();
1428 if (rm
.isBlockedListRequest()) {
1431 } catch (UntrustedIdentityException
| IOException e
) {
1432 e
.printStackTrace();
1435 // TODO Handle rm.isConfigurationRequest();
1437 if (syncMessage
.getGroups().isPresent()) {
1438 File tmpFile
= null;
1440 tmpFile
= IOUtils
.createTempFile();
1441 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1442 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1444 while ((g
= s
.read()) != null) {
1445 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1446 if (syncGroup
== null) {
1447 syncGroup
= new GroupInfo(g
.getId());
1449 if (g
.getName().isPresent()) {
1450 syncGroup
.name
= g
.getName().get();
1452 syncGroup
.addMembers(g
.getMembers());
1453 if (!g
.isActive()) {
1454 syncGroup
.members
.remove(username
);
1456 syncGroup
.blocked
= g
.isBlocked();
1457 if (g
.getColor().isPresent()) {
1458 syncGroup
.color
= g
.getColor().get();
1461 if (g
.getAvatar().isPresent()) {
1462 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1464 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1465 syncGroup
.archived
= g
.isArchived();
1466 account
.getGroupStore().updateGroup(syncGroup
);
1469 } catch (Exception e
) {
1470 e
.printStackTrace();
1472 if (tmpFile
!= null) {
1474 Files
.delete(tmpFile
.toPath());
1475 } catch (IOException e
) {
1476 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1481 if (syncMessage
.getBlockedList().isPresent()) {
1482 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1483 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1484 if (address
.getNumber().isPresent()) {
1486 setContactBlocked(address
.getNumber().get(), true);
1487 } catch (InvalidNumberException e
) {
1488 e
.printStackTrace();
1492 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1494 setGroupBlocked(groupId
, true);
1495 } catch (GroupNotFoundException e
) {
1496 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1500 if (syncMessage
.getContacts().isPresent()) {
1501 File tmpFile
= null;
1503 tmpFile
= IOUtils
.createTempFile();
1504 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1505 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1506 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1507 if (contactsMessage
.isComplete()) {
1508 account
.getContactStore().clear();
1511 while ((c
= s
.read()) != null) {
1512 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1513 account
.setProfileKey(c
.getProfileKey().get());
1515 ContactInfo contact
= account
.getContactStore().getContact(c
.getAddress().getNumber().get());
1516 if (contact
== null) {
1517 contact
= new ContactInfo();
1518 contact
.number
= c
.getAddress().getNumber().get();
1520 if (c
.getName().isPresent()) {
1521 contact
.name
= c
.getName().get();
1523 if (c
.getColor().isPresent()) {
1524 contact
.color
= c
.getColor().get();
1526 if (c
.getProfileKey().isPresent()) {
1527 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1529 if (c
.getVerified().isPresent()) {
1530 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1531 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1533 if (c
.getExpirationTimer().isPresent()) {
1534 ThreadInfo thread
= account
.getThreadStore().getThread(c
.getAddress().getNumber().get());
1535 if (thread
== null) {
1536 thread
= new ThreadInfo();
1537 thread
.id
= c
.getAddress().getNumber().get();
1539 thread
.messageExpirationTime
= c
.getExpirationTimer().get();
1540 account
.getThreadStore().updateThread(thread
);
1542 contact
.blocked
= c
.isBlocked();
1543 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1544 contact
.archived
= c
.isArchived();
1545 account
.getContactStore().updateContact(contact
);
1547 if (c
.getAvatar().isPresent()) {
1548 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1552 } catch (Exception e
) {
1553 e
.printStackTrace();
1555 if (tmpFile
!= null) {
1557 Files
.delete(tmpFile
.toPath());
1558 } catch (IOException e
) {
1559 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1564 if (syncMessage
.getVerified().isPresent()) {
1565 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1566 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1568 if (syncMessage
.getConfiguration().isPresent()) {
1575 private File
getContactAvatarFile(String number
) {
1576 return new File(avatarsPath
, "contact-" + number
);
1579 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
{
1580 IOUtils
.createPrivateDirectories(avatarsPath
);
1581 if (attachment
.isPointer()) {
1582 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1583 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1585 SignalServiceAttachmentStream stream
= attachment
.asStream();
1586 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1590 private File
getGroupAvatarFile(byte[] groupId
) {
1591 return new File(avatarsPath
, "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1594 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
{
1595 IOUtils
.createPrivateDirectories(avatarsPath
);
1596 if (attachment
.isPointer()) {
1597 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1598 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1600 SignalServiceAttachmentStream stream
= attachment
.asStream();
1601 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1605 public File
getAttachmentFile(long attachmentId
) {
1606 return new File(attachmentsPath
, attachmentId
+ "");
1609 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
{
1610 IOUtils
.createPrivateDirectories(attachmentsPath
);
1611 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getId()), true);
1614 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
{
1615 if (storePreview
&& pointer
.getPreview().isPresent()) {
1616 File previewFile
= new File(outputFile
+ ".preview");
1617 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1618 byte[] preview
= pointer
.getPreview().get();
1619 output
.write(preview
, 0, preview
.length
);
1620 } catch (FileNotFoundException e
) {
1621 e
.printStackTrace();
1626 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1628 File tmpFile
= IOUtils
.createTempFile();
1629 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
)) {
1630 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1631 byte[] buffer
= new byte[4096];
1634 while ((read
= input
.read(buffer
)) != -1) {
1635 output
.write(buffer
, 0, read
);
1637 } catch (FileNotFoundException e
) {
1638 e
.printStackTrace();
1643 Files
.delete(tmpFile
.toPath());
1644 } catch (IOException e
) {
1645 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1651 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
{
1652 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1653 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
);
1657 public boolean isRemote() {
1661 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1662 File groupsFile
= IOUtils
.createTempFile();
1665 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1666 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1667 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1668 ThreadInfo info
= account
.getThreadStore().getThread(Base64
.encodeBytes(record.groupId
));
1669 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1670 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1671 record.members
.contains(username
), Optional
.fromNullable(info
!= null ? info
.messageExpirationTime
: null),
1672 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1676 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1677 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1678 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1679 .withStream(groupsFileStream
)
1680 .withContentType("application/octet-stream")
1681 .withLength(groupsFile
.length())
1684 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1689 Files
.delete(groupsFile
.toPath());
1690 } catch (IOException e
) {
1691 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1696 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1697 File contactsFile
= IOUtils
.createTempFile();
1700 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1701 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1702 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1703 VerifiedMessage verifiedMessage
= null;
1704 ThreadInfo info
= account
.getThreadStore().getThread(record.number
);
1705 if (getIdentities().containsKey(record.number
)) {
1706 JsonIdentityKeyStore
.Identity currentIdentity
= null;
1707 for (JsonIdentityKeyStore
.Identity id
: getIdentities().get(record.number
)) {
1708 if (currentIdentity
== null || id
.getDateAdded().after(currentIdentity
.getDateAdded())) {
1709 currentIdentity
= id
;
1712 if (currentIdentity
!= null) {
1713 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1717 ProfileKey profileKey
= null;
1719 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1720 } catch (InvalidInputException ignored
) {
1722 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1723 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1724 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1725 Optional
.fromNullable(info
!= null ? info
.messageExpirationTime
: null),
1726 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1729 if (account
.getProfileKey() != null) {
1730 // Send our own profile key as well
1731 out
.write(new DeviceContact(account
.getSelfAddress(),
1732 Optional
.absent(), Optional
.absent(),
1733 Optional
.absent(), Optional
.absent(),
1734 Optional
.of(account
.getProfileKey()),
1735 false, Optional
.absent(), Optional
.absent(), false));
1739 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1740 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1741 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1742 .withStream(contactsFileStream
)
1743 .withContentType("application/octet-stream")
1744 .withLength(contactsFile
.length())
1747 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1752 Files
.delete(contactsFile
.toPath());
1753 } catch (IOException e
) {
1754 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1759 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1760 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1761 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1762 if (record.blocked
) {
1763 addresses
.add(record.getAddress());
1766 List
<byte[]> groupIds
= new ArrayList
<>();
1767 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1768 if (record.blocked
) {
1769 groupIds
.add(record.groupId
);
1772 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1775 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1776 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1777 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1780 public List
<ContactInfo
> getContacts() {
1781 return account
.getContactStore().getContacts();
1784 public ContactInfo
getContact(String number
) {
1785 return account
.getContactStore().getContact(number
);
1788 public GroupInfo
getGroup(byte[] groupId
) {
1789 return account
.getGroupStore().getGroup(groupId
);
1792 public Map
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities() {
1793 return account
.getSignalProtocolStore().getIdentities();
1796 public Pair
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities(String number
) throws InvalidNumberException
{
1797 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, username
);
1798 return new Pair
<>(canonicalizedNumber
, account
.getSignalProtocolStore().getIdentities(canonicalizedNumber
));
1802 * Trust this the identity with this fingerprint
1804 * @param name username of the identity
1805 * @param fingerprint Fingerprint
1807 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) {
1808 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1812 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1813 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1817 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1819 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1820 } catch (IOException
| UntrustedIdentityException e
) {
1821 e
.printStackTrace();
1830 * Trust this the identity with this safety number
1832 * @param name username of the identity
1833 * @param safetyNumber Safety number
1835 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) {
1836 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1840 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1841 if (!safetyNumber
.equals(computeSafetyNumber(name
, id
.getIdentityKey()))) {
1845 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1847 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1848 } catch (IOException
| UntrustedIdentityException e
) {
1849 e
.printStackTrace();
1858 * Trust all keys of this identity without verification
1860 * @param name username of the identity
1862 public boolean trustIdentityAllKeys(String name
) {
1863 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1867 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1868 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1869 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1871 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1872 } catch (IOException
| UntrustedIdentityException e
) {
1873 e
.printStackTrace();
1881 public String
computeSafetyNumber(String theirUsername
, IdentityKey theirIdentityKey
) {
1882 return Utils
.computeSafetyNumber(username
, getIdentity(), theirUsername
, theirIdentityKey
);
1885 public interface ReceiveMessageHandler
{
1887 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);