2 Copyright (C) 2015-2020 AsamK and contributors
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 package org
.asamk
.signal
.manager
;
19 import com
.fasterxml
.jackson
.databind
.ObjectMapper
;
21 import org
.asamk
.Signal
;
22 import org
.asamk
.signal
.AttachmentInvalidException
;
23 import org
.asamk
.signal
.GroupNotFoundException
;
24 import org
.asamk
.signal
.JsonStickerPack
;
25 import org
.asamk
.signal
.NotAGroupMemberException
;
26 import org
.asamk
.signal
.StickerPackInvalidException
;
27 import org
.asamk
.signal
.TrustLevel
;
28 import org
.asamk
.signal
.UserAlreadyExists
;
29 import org
.asamk
.signal
.storage
.SignalAccount
;
30 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
31 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
32 import org
.asamk
.signal
.storage
.groups
.JsonGroupStore
;
33 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
34 import org
.asamk
.signal
.util
.IOUtils
;
35 import org
.asamk
.signal
.util
.Util
;
36 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
37 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
38 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
39 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
40 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
41 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
42 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
43 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
44 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
45 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
46 import org
.signal
.libsignal
.metadata
.SelfSendException
;
47 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
48 import org
.signal
.zkgroup
.InvalidInputException
;
49 import org
.signal
.zkgroup
.VerificationFailedException
;
50 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
51 import org
.whispersystems
.libsignal
.IdentityKey
;
52 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
53 import org
.whispersystems
.libsignal
.InvalidKeyException
;
54 import org
.whispersystems
.libsignal
.InvalidMessageException
;
55 import org
.whispersystems
.libsignal
.InvalidVersionException
;
56 import org
.whispersystems
.libsignal
.ecc
.Curve
;
57 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
58 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
59 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
60 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
61 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
62 import org
.whispersystems
.libsignal
.util
.Medium
;
63 import org
.whispersystems
.libsignal
.util
.Pair
;
64 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
65 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
66 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
67 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
68 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
69 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
70 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
71 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
72 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
73 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
74 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
79 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
80 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
81 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
82 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
83 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
84 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
85 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
92 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
93 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
94 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
95 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
96 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
97 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
98 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
99 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
100 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
101 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
102 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.AuthorizationFailedException
;
103 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
104 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
105 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
106 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
107 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
108 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
109 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
110 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
111 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
112 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
113 import org
.whispersystems
.util
.Base64
;
116 import java
.io
.FileInputStream
;
117 import java
.io
.FileNotFoundException
;
118 import java
.io
.FileOutputStream
;
119 import java
.io
.IOException
;
120 import java
.io
.InputStream
;
121 import java
.io
.OutputStream
;
123 import java
.net
.URISyntaxException
;
124 import java
.net
.URLEncoder
;
125 import java
.nio
.file
.Files
;
126 import java
.nio
.file
.Paths
;
127 import java
.nio
.file
.StandardCopyOption
;
128 import java
.util
.ArrayList
;
129 import java
.util
.Arrays
;
130 import java
.util
.Collection
;
131 import java
.util
.Collections
;
132 import java
.util
.Date
;
133 import java
.util
.HashSet
;
134 import java
.util
.LinkedList
;
135 import java
.util
.List
;
136 import java
.util
.Locale
;
137 import java
.util
.Map
;
138 import java
.util
.Objects
;
139 import java
.util
.Set
;
140 import java
.util
.UUID
;
141 import java
.util
.concurrent
.TimeUnit
;
142 import java
.util
.concurrent
.TimeoutException
;
143 import java
.util
.zip
.ZipEntry
;
144 import java
.util
.zip
.ZipFile
;
146 public class Manager
implements Signal
{
148 private static final SignalServiceProfile
.Capabilities capabilities
= new SignalServiceProfile
.Capabilities(false, false);
150 private final String settingsPath
;
151 private final String dataPath
;
152 private final String attachmentsPath
;
153 private final String avatarsPath
;
154 private final SleepTimer timer
= new UptimeSleepTimer();
156 private SignalAccount account
;
157 private String username
;
158 private SignalServiceAccountManager accountManager
;
159 private SignalServiceMessagePipe messagePipe
= null;
160 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
162 public Manager(String username
, String settingsPath
) {
163 this.username
= username
;
164 this.settingsPath
= settingsPath
;
165 this.dataPath
= this.settingsPath
+ "/data";
166 this.attachmentsPath
= this.settingsPath
+ "/attachments";
167 this.avatarsPath
= this.settingsPath
+ "/avatars";
171 public String
getUsername() {
175 public SignalServiceAddress
getSelfAddress() {
176 return account
.getSelfAddress();
179 private SignalServiceAccountManager
getSignalServiceAccountManager() {
180 return new SignalServiceAccountManager(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), BaseConfig
.USER_AGENT
, timer
);
183 private IdentityKey
getIdentity() {
184 return account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey();
187 public int getDeviceId() {
188 return account
.getDeviceId();
191 private String
getMessageCachePath() {
192 return this.dataPath
+ "/" + username
+ ".d/msg-cache";
195 private String
getMessageCachePath(String sender
) {
196 return getMessageCachePath() + "/" + sender
.replace("/", "_");
199 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
200 String cachePath
= getMessageCachePath(sender
);
201 IOUtils
.createPrivateDirectories(cachePath
);
202 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
205 public boolean userHasKeys() {
206 return account
!= null && account
.getSignalProtocolStore() != null;
209 public void init() throws IOException
{
210 if (!SignalAccount
.userExists(dataPath
, username
)) {
213 account
= SignalAccount
.load(dataPath
, username
);
215 migrateLegacyConfigs();
217 accountManager
= getSignalServiceAccountManager();
219 if (account
.isRegistered()) {
220 if (accountManager
.getPreKeysCount() < BaseConfig
.PREKEY_MINIMUM_COUNT
) {
224 if (account
.getUuid() == null) {
225 account
.setUuid(accountManager
.getOwnUuid());
229 } catch (AuthorizationFailedException e
) {
230 System
.err
.println("Authorization failed, was the number registered elsewhere?");
235 private void migrateLegacyConfigs() {
236 // Copy group avatars that were previously stored in the attachments folder
237 // to the new avatar folder
238 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
239 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
240 File avatarFile
= getGroupAvatarFile(g
.groupId
);
241 File attachmentFile
= getAttachmentFile(g
.getAvatarId());
242 if (!avatarFile
.exists() && attachmentFile
.exists()) {
244 IOUtils
.createPrivateDirectories(avatarsPath
);
245 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
246 } catch (Exception e
) {
251 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
254 if (account
.getProfileKey() == null) {
255 // Old config file, creating new profile key
256 account
.setProfileKey(KeyUtils
.createProfileKey());
261 private void createNewIdentity() throws IOException
{
262 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
263 int registrationId
= KeyHelper
.generateRegistrationId(false);
264 if (username
== null) {
265 account
= SignalAccount
.createTemporaryAccount(identityKey
, registrationId
);
267 ProfileKey profileKey
= KeyUtils
.createProfileKey();
268 account
= SignalAccount
.create(dataPath
, username
, identityKey
, registrationId
, profileKey
);
273 public boolean isRegistered() {
274 return account
!= null && account
.isRegistered();
277 public void register(boolean voiceVerification
) throws IOException
{
278 if (account
== null) {
281 account
.setPassword(KeyUtils
.createPassword());
282 accountManager
= getSignalServiceAccountManager();
284 if (voiceVerification
) {
285 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
287 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
290 account
.setRegistered(false);
294 public void updateAccountAttributes() throws IOException
{
295 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities
);
298 public void setProfileName(String name
) throws IOException
{
299 accountManager
.setProfileName(account
.getProfileKey(), name
);
302 public void setProfileAvatar(File avatar
) throws IOException
{
303 final StreamDetails streamDetails
= Utils
.createStreamDetailsFromFile(avatar
);
304 accountManager
.setProfileAvatar(account
.getProfileKey(), streamDetails
);
305 streamDetails
.getStream().close();
308 public void removeProfileAvatar() throws IOException
{
309 accountManager
.setProfileAvatar(account
.getProfileKey(), null);
312 public void unregister() throws IOException
{
313 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
314 // If this is the master device, other users can't send messages to this number anymore.
315 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
316 accountManager
.setGcmId(Optional
.absent());
318 account
.setRegistered(false);
322 public String
getDeviceLinkUri() throws TimeoutException
, IOException
{
323 if (account
== null) {
326 account
.setPassword(KeyUtils
.createPassword());
327 accountManager
= getSignalServiceAccountManager();
328 String uuid
= accountManager
.getNewDeviceUuid();
330 return Utils
.createDeviceLinkUri(new Utils
.DeviceLinkInfo(uuid
, getIdentity().getPublicKey()));
333 public void finishDeviceLink(String deviceName
) throws IOException
, InvalidKeyException
, TimeoutException
, UserAlreadyExists
{
334 account
.setSignalingKey(KeyUtils
.createSignalingKey());
335 SignalServiceAccountManager
.NewDeviceRegistrationReturn ret
= accountManager
.finishNewDeviceRegistration(account
.getSignalProtocolStore().getIdentityKeyPair(), account
.getSignalingKey(), false, true, account
.getSignalProtocolStore().getLocalRegistrationId(), deviceName
);
337 username
= ret
.getNumber();
338 // TODO do this check before actually registering
339 if (SignalAccount
.userExists(dataPath
, username
)) {
340 throw new UserAlreadyExists(username
, SignalAccount
.getFileName(dataPath
, username
));
343 // Create new account with the synced identity
344 byte[] profileKeyBytes
= ret
.getProfileKey();
345 ProfileKey profileKey
;
346 if (profileKeyBytes
== null) {
347 profileKey
= KeyUtils
.createProfileKey();
350 profileKey
= new ProfileKey(profileKeyBytes
);
351 } catch (InvalidInputException e
) {
352 throw new IOException("Received invalid profileKey", e
);
355 account
= SignalAccount
.createLinkedAccount(dataPath
, username
, ret
.getUuid(), account
.getPassword(), ret
.getDeviceId(), ret
.getIdentity(), account
.getSignalProtocolStore().getLocalRegistrationId(), account
.getSignalingKey(), profileKey
);
360 requestSyncContacts();
361 requestSyncBlocked();
362 requestSyncConfiguration();
367 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
368 List
<DeviceInfo
> devices
= accountManager
.getDevices();
369 account
.setMultiDevice(devices
.size() > 1);
374 public void removeLinkedDevices(int deviceId
) throws IOException
{
375 accountManager
.removeDevice(deviceId
);
376 List
<DeviceInfo
> devices
= accountManager
.getDevices();
377 account
.setMultiDevice(devices
.size() > 1);
381 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
382 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
384 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
387 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
388 IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
389 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
391 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
392 account
.setMultiDevice(true);
396 private List
<PreKeyRecord
> generatePreKeys() {
397 List
<PreKeyRecord
> records
= new ArrayList
<>(BaseConfig
.PREKEY_BATCH_SIZE
);
399 final int offset
= account
.getPreKeyIdOffset();
400 for (int i
= 0; i
< BaseConfig
.PREKEY_BATCH_SIZE
; i
++) {
401 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
402 ECKeyPair keyPair
= Curve
.generateKeyPair();
403 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
408 account
.addPreKeys(records
);
414 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
416 ECKeyPair keyPair
= Curve
.generateKeyPair();
417 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
418 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
420 account
.addSignedPreKey(record);
424 } catch (InvalidKeyException e
) {
425 throw new AssertionError(e
);
429 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
430 verificationCode
= verificationCode
.replace("-", "");
431 account
.setSignalingKey(KeyUtils
.createSignalingKey());
432 // TODO make unrestricted unidentified access configurable
433 UUID uuid
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, capabilities
);
435 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
436 account
.setRegistered(true);
437 account
.setUuid(uuid
);
438 account
.setRegistrationLockPin(pin
);
444 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
445 if (pin
.isPresent()) {
446 account
.setRegistrationLockPin(pin
.get());
447 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
449 account
.setRegistrationLockPin(null);
450 accountManager
.removeV1Pin();
455 private void refreshPreKeys() throws IOException
{
456 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
457 final IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
458 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
460 accountManager
.setPreKeys(getIdentity(), signedPreKeyRecord
, oneTimePreKeys
);
463 private SignalServiceMessageReceiver
getMessageReceiver() {
464 return new SignalServiceMessageReceiver(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), BaseConfig
.USER_AGENT
, null, timer
);
467 private SignalServiceMessageSender
getMessageSender() {
468 return new SignalServiceMessageSender(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
469 account
.getDeviceId(), account
.getSignalProtocolStore(), BaseConfig
.USER_AGENT
, account
.isMultiDevice(), Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent());
472 private SignalServiceProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
473 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
478 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
479 } catch (IOException ignored
) {
483 SignalServiceMessageReceiver receiver
= getMessageReceiver();
485 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
486 } catch (VerificationFailedException e
) {
487 throw new AssertionError(e
);
491 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
492 File file
= getGroupAvatarFile(groupId
);
493 if (!file
.exists()) {
494 return Optional
.absent();
497 return Optional
.of(Utils
.createAttachment(file
));
500 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
501 File file
= getContactAvatarFile(number
);
502 if (!file
.exists()) {
503 return Optional
.absent();
506 return Optional
.of(Utils
.createAttachment(file
));
509 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
510 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
512 throw new GroupNotFoundException(groupId
);
514 if (!g
.isMember(account
.getSelfAddress())) {
515 throw new NotAGroupMemberException(groupId
, g
.name
);
520 public List
<GroupInfo
> getGroups() {
521 return account
.getGroupStore().getGroups();
525 public void sendGroupMessage(String messageText
, List
<String
> attachments
,
527 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
528 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
529 if (attachments
!= null) {
530 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
532 if (groupId
!= null) {
533 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
536 messageBuilder
.asGroupMessage(group
);
539 final GroupInfo g
= getGroupForSending(groupId
);
541 messageBuilder
.withExpiration(g
.messageExpirationTime
);
543 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
546 public void sendGroupMessageReaction(String emoji
, boolean remove
, SignalServiceAddress targetAuthor
,
547 long targetSentTimestamp
, byte[] groupId
)
548 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
{
549 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
);
550 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
551 .withReaction(reaction
)
552 .withProfileKey(account
.getProfileKey().serialize());
553 if (groupId
!= null) {
554 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
557 messageBuilder
.asGroupMessage(group
);
559 final GroupInfo g
= getGroupForSending(groupId
);
560 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
563 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
{
564 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
568 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
569 .asGroupMessage(group
);
571 final GroupInfo g
= getGroupForSending(groupId
);
572 g
.removeMember(account
.getSelfAddress());
573 account
.getGroupStore().updateGroup(g
);
575 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
578 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
580 if (groupId
== null) {
582 g
= new GroupInfo(KeyUtils
.createGroupId());
583 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
585 g
= getGroupForSending(groupId
);
592 if (members
!= null) {
593 final Set
<String
> newE164Members
= new HashSet
<>();
594 for (SignalServiceAddress member
: members
) {
595 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
598 newE164Members
.add(member
.getNumber().get());
601 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
602 if (contacts
.size() != newE164Members
.size()) {
603 // Some of the new members are not registered on Signal
604 for (ContactTokenDetails contact
: contacts
) {
605 newE164Members
.remove(contact
.getNumber());
607 System
.err
.println("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
608 System
.err
.println("Aborting…");
612 g
.addMembers(members
);
615 if (avatarFile
!= null) {
616 IOUtils
.createPrivateDirectories(avatarsPath
);
617 File aFile
= getGroupAvatarFile(g
.groupId
);
618 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
621 account
.getGroupStore().updateGroup(g
);
623 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
625 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
629 private void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
630 if (groupId
== null) {
633 GroupInfo g
= getGroupForSending(groupId
);
635 if (!g
.isMember(recipient
)) {
639 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
641 // Send group message only to the recipient who requested it
642 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
645 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) {
646 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
649 .withMembers(new ArrayList
<>(g
.getMembers()));
651 File aFile
= getGroupAvatarFile(g
.groupId
);
652 if (aFile
.exists()) {
654 group
.withAvatar(Utils
.createAttachment(aFile
));
655 } catch (IOException e
) {
656 throw new AttachmentInvalidException(aFile
.toString(), e
);
660 return SignalServiceDataMessage
.newBuilder()
661 .asGroupMessage(group
.build())
662 .withExpiration(g
.messageExpirationTime
);
665 private void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
666 if (groupId
== null) {
670 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
673 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
674 .asGroupMessage(group
.build());
676 // Send group info request message to the recipient who sent us a message with this groupId
677 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
680 private void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
681 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
682 Collections
.singletonList(messageId
),
683 System
.currentTimeMillis());
685 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
689 public void sendMessage(String message
, List
<String
> attachments
, String recipient
)
690 throws EncapsulatedExceptions
, AttachmentInvalidException
, IOException
, InvalidNumberException
{
691 List
<String
> recipients
= new ArrayList
<>(1);
692 recipients
.add(recipient
);
693 sendMessage(message
, attachments
, recipients
);
697 public void sendMessage(String messageText
, List
<String
> attachments
,
698 List
<String
> recipients
)
699 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
700 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
701 if (attachments
!= null) {
702 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
704 // Upload attachments here, so we only upload once even for multiple recipients
705 SignalServiceMessageSender messageSender
= getMessageSender();
706 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
707 for (SignalServiceAttachment attachment
: attachmentStreams
) {
708 if (attachment
.isStream()) {
709 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
710 } else if (attachment
.isPointer()) {
711 attachmentPointers
.add(attachment
.asPointer());
715 messageBuilder
.withAttachments(attachmentPointers
);
717 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
718 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
721 public void sendMessageReaction(String emoji
, boolean remove
, SignalServiceAddress targetAuthor
,
722 long targetSentTimestamp
, List
<String
> recipients
)
723 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
724 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
);
725 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
726 .withReaction(reaction
)
727 .withProfileKey(account
.getProfileKey().serialize());
728 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
732 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
733 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
734 .asEndSessionMessage();
736 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
740 public String
getContactName(String number
) throws InvalidNumberException
{
741 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, account
.getUsername());
742 ContactInfo contact
= account
.getContactStore().getContact(new SignalServiceAddress(null, canonicalizedNumber
));
743 if (contact
== null) {
751 public void setContactName(String number
, String name
) throws InvalidNumberException
{
752 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, account
.getUsername());
753 final SignalServiceAddress address
= new SignalServiceAddress(null, canonicalizedNumber
);
754 ContactInfo contact
= account
.getContactStore().getContact(address
);
755 if (contact
== null) {
756 contact
= new ContactInfo(address
);
757 System
.err
.println("Add contact " + canonicalizedNumber
+ " named " + name
);
759 System
.err
.println("Updating contact " + canonicalizedNumber
+ " name " + contact
.name
+ " -> " + name
);
762 account
.getContactStore().updateContact(contact
);
767 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
768 number
= Utils
.canonicalizeNumber(number
, account
.getUsername());
769 final SignalServiceAddress address
= new SignalServiceAddress(null, number
);
770 ContactInfo contact
= account
.getContactStore().getContact(address
);
771 if (contact
== null) {
772 contact
= new ContactInfo(address
);
773 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + number
);
775 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + number
);
777 contact
.blocked
= blocked
;
778 account
.getContactStore().updateContact(contact
);
783 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
784 GroupInfo group
= getGroup(groupId
);
786 throw new GroupNotFoundException(groupId
);
788 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
789 group
.blocked
= blocked
;
790 account
.getGroupStore().updateGroup(group
);
796 public List
<byte[]> getGroupIds() {
797 List
<GroupInfo
> groups
= getGroups();
798 List
<byte[]> ids
= new ArrayList
<>(groups
.size());
799 for (GroupInfo group
: groups
) {
800 ids
.add(group
.groupId
);
806 public String
getGroupName(byte[] groupId
) {
807 GroupInfo group
= getGroup(groupId
);
816 public List
<String
> getGroupMembers(byte[] groupId
) {
817 GroupInfo group
= getGroup(groupId
);
819 return Collections
.emptyList();
821 return new ArrayList
<>(group
.getMembersE164());
826 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
{
827 if (groupId
.length
== 0) {
830 if (name
.isEmpty()) {
833 if (members
.size() == 0) {
836 if (avatar
.isEmpty()) {
839 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
843 * Change the expiration timer for a contact
845 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) {
846 ContactInfo c
= account
.getContactStore().getContact(address
);
847 c
.messageExpirationTime
= messageExpirationTimer
;
848 account
.getContactStore().updateContact(c
);
852 * Change the expiration timer for a group
854 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
855 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
856 g
.messageExpirationTime
= messageExpirationTimer
;
857 account
.getGroupStore().updateGroup(g
);
861 * Upload the sticker pack from path.
863 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
864 * @return if successful, returns the URL to install the sticker pack in the signal app
866 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
867 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
869 SignalServiceMessageSender messageSender
= getMessageSender();
871 byte[] packKey
= KeyUtils
.createStickerUploadKey();
872 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
875 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
877 } catch (URISyntaxException e
) {
878 throw new AssertionError(e
);
882 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
884 String rootPath
= null;
886 final File file
= new File(path
);
887 if (file
.getName().endsWith(".zip")) {
888 zip
= new ZipFile(file
);
889 } else if (file
.getName().equals("manifest.json")) {
890 rootPath
= file
.getParent();
892 throw new StickerPackInvalidException("Could not find manifest.json");
895 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
897 if (pack
.stickers
== null) {
898 throw new StickerPackInvalidException("Must set a 'stickers' field.");
901 if (pack
.stickers
.isEmpty()) {
902 throw new StickerPackInvalidException("Must include stickers.");
905 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
906 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
907 if (sticker
.file
== null) {
908 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
911 Pair
<InputStream
, Long
> data
;
913 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
914 } catch (IOException ignored
) {
915 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
918 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
919 stickers
.add(stickerInfo
);
922 StickerInfo cover
= null;
923 if (pack
.cover
!= null) {
924 if (pack
.cover
.file
== null) {
925 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
928 Pair
<InputStream
, Long
> data
;
930 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
931 } catch (IOException ignored
) {
932 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
935 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
938 return new SignalServiceStickerManifestUpload(
945 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
946 InputStream inputStream
;
948 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
950 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
952 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
955 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
957 final ZipEntry entry
= zip
.getEntry(subfile
);
958 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
960 final File file
= new File(rootPath
, subfile
);
961 return new Pair
<>(new FileInputStream(file
), file
.length());
965 private void requestSyncGroups() throws IOException
{
966 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
967 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
969 sendSyncMessage(message
);
970 } catch (UntrustedIdentityException e
) {
975 private void requestSyncContacts() throws IOException
{
976 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
977 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
979 sendSyncMessage(message
);
980 } catch (UntrustedIdentityException e
) {
985 private void requestSyncBlocked() throws IOException
{
986 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
987 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
989 sendSyncMessage(message
);
990 } catch (UntrustedIdentityException e
) {
995 private void requestSyncConfiguration() throws IOException
{
996 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
997 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
999 sendSyncMessage(message
);
1000 } catch (UntrustedIdentityException e
) {
1001 e
.printStackTrace();
1005 private byte[] getSenderCertificate() throws IOException
{
1006 byte[] certificate
= accountManager
.getSenderCertificate();
1007 // TODO cache for a day
1011 private byte[] getSelfUnidentifiedAccessKey() {
1012 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
1015 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
1016 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
1018 return new SignalProfile(
1019 encryptedProfile
.getIdentityKey(),
1020 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
1021 encryptedProfile
.getAvatar(),
1022 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
1023 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
1025 } catch (InvalidCiphertextException e
) {
1030 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) throws IOException
{
1031 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
1032 if (contact
== null || contact
.profileKey
== null) {
1035 ProfileKey theirProfileKey
;
1037 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
1038 } catch (InvalidInputException e
) {
1039 throw new AssertionError(e
);
1041 SignalProfile targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
1043 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1047 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1048 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1051 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1054 private Optional
<UnidentifiedAccessPair
> getAccessForSync() throws IOException
{
1055 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1056 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1058 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1059 return Optional
.absent();
1063 return Optional
.of(new UnidentifiedAccessPair(
1064 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1065 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1067 } catch (InvalidCertificateException e
) {
1068 return Optional
.absent();
1072 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) throws IOException
{
1073 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1074 for (SignalServiceAddress recipient
: recipients
) {
1075 result
.add(getAccessFor(recipient
));
1080 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) throws IOException
{
1081 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1082 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1083 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1085 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1086 return Optional
.absent();
1090 return Optional
.of(new UnidentifiedAccessPair(
1091 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1092 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1094 } catch (InvalidCertificateException e
) {
1095 return Optional
.absent();
1099 private void sendSyncMessage(SignalServiceSyncMessage message
)
1100 throws IOException
, UntrustedIdentityException
{
1101 SignalServiceMessageSender messageSender
= getMessageSender();
1103 messageSender
.sendMessage(message
, getAccessForSync());
1104 } catch (UntrustedIdentityException e
) {
1105 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1111 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1113 private void sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1114 throws EncapsulatedExceptions
, IOException
{
1115 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1117 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1118 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1119 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1121 for (SendMessageResult result
: results
) {
1122 if (result
.isUnregisteredFailure()) {
1123 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getNumber().get(), null));
1124 } else if (result
.isNetworkFailure()) {
1125 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getNumber().get(), null));
1126 } else if (result
.getIdentityFailure() != null) {
1127 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getNumber().get(), result
.getIdentityFailure().getIdentityKey()));
1130 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1131 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1135 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1136 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1137 final String username
= account
.getUsername();
1139 for (String number
: numbers
) {
1140 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, username
);
1141 if (canonicalizedNumber
.equals(username
)) {
1142 signalServiceAddresses
.add(account
.getSelfAddress());
1144 // TODO get corresponding uuid
1145 signalServiceAddresses
.add(new SignalServiceAddress(null, canonicalizedNumber
));
1148 return signalServiceAddresses
;
1151 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1152 throws IOException
{
1153 if (messagePipe
== null) {
1154 messagePipe
= getMessageReceiver().createMessagePipe();
1156 if (unidentifiedMessagePipe
== null) {
1157 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1159 SignalServiceDataMessage message
= null;
1161 SignalServiceMessageSender messageSender
= getMessageSender();
1163 message
= messageBuilder
.build();
1164 if (message
.getGroupInfo().isPresent()) {
1166 final boolean isRecipientUpdate
= false;
1167 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1168 for (SendMessageResult r
: result
) {
1169 if (r
.getIdentityFailure() != null) {
1170 account
.getSignalProtocolStore().saveIdentity(r
.getAddress().getNumber().get(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1174 } catch (UntrustedIdentityException e
) {
1175 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1176 return Collections
.emptyList();
1178 } else if (recipients
.size() == 1 && recipients
.contains(account
.getSelfAddress())) {
1179 SignalServiceAddress recipient
= account
.getSelfAddress();
1180 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1181 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1182 message
.getTimestamp(),
1184 message
.getExpiresInSeconds(),
1185 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1187 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1189 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1191 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1192 } catch (UntrustedIdentityException e
) {
1193 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1194 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1198 // Send to all individually, so sync messages are sent correctly
1199 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1200 for (SignalServiceAddress address
: recipients
) {
1201 ContactInfo contact
= account
.getContactStore().getContact(address
);
1202 if (contact
!= null) {
1203 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1205 messageBuilder
.withExpiration(0);
1207 message
= messageBuilder
.build();
1209 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1210 results
.add(result
);
1211 } catch (UntrustedIdentityException e
) {
1212 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1213 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1219 if (message
!= null && message
.isEndSession()) {
1220 for (SignalServiceAddress recipient
: recipients
) {
1221 handleEndSession(recipient
.getNumber().get());
1228 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, ProtocolUntrustedIdentityException
, SelfSendException
, UnsupportedDataMessageException
{
1229 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1231 return cipher
.decrypt(envelope
);
1232 } catch (ProtocolUntrustedIdentityException e
) {
1233 // TODO We don't get the new untrusted identity from ProtocolUntrustedIdentityException anymore ... we need to get it from somewhere else
1234 // account.getSignalProtocolStore().saveIdentity(e.getSender(), e.getUntrustedIdentity(), TrustLevel.UNTRUSTED);
1239 private void handleEndSession(String source
) {
1240 account
.getSignalProtocolStore().deleteAllSessions(source
);
1243 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1244 if (message
.getGroupInfo().isPresent()) {
1245 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1246 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1247 switch (groupInfo
.getType()) {
1249 if (group
== null) {
1250 group
= new GroupInfo(groupInfo
.getGroupId());
1253 if (groupInfo
.getAvatar().isPresent()) {
1254 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1255 if (avatar
.isPointer()) {
1257 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1258 } catch (IOException
| InvalidMessageException e
) {
1259 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getId() + "): " + e
.getMessage());
1264 if (groupInfo
.getName().isPresent()) {
1265 group
.name
= groupInfo
.getName().get();
1268 if (groupInfo
.getMembers().isPresent()) {
1269 group
.addMembers(groupInfo
.getMembers().get());
1272 account
.getGroupStore().updateGroup(group
);
1275 if (group
== null) {
1277 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1278 } catch (IOException
| EncapsulatedExceptions e
) {
1279 e
.printStackTrace();
1284 if (group
== null) {
1286 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1287 } catch (IOException
| EncapsulatedExceptions e
) {
1288 e
.printStackTrace();
1291 group
.removeMember(source
);
1292 account
.getGroupStore().updateGroup(group
);
1296 if (group
!= null) {
1298 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1299 } catch (IOException
| EncapsulatedExceptions e
) {
1300 e
.printStackTrace();
1301 } catch (NotAGroupMemberException e
) {
1302 // We have left this group, so don't send a group update message
1308 if (message
.isEndSession()) {
1309 handleEndSession(isSync ? destination
.getNumber().get() : source
.getNumber().get());
1311 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1312 if (message
.getGroupInfo().isPresent()) {
1313 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1314 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1315 if (group
== null) {
1316 group
= new GroupInfo(groupInfo
.getGroupId());
1318 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1319 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1320 account
.getGroupStore().updateGroup(group
);
1323 ContactInfo contact
= account
.getContactStore().getContact(isSync ? destination
: source
);
1324 if (contact
== null) {
1325 contact
= new ContactInfo(isSync ? destination
: source
);
1327 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1328 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1329 account
.getContactStore().updateContact(contact
);
1333 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1334 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1335 if (attachment
.isPointer()) {
1337 retrieveAttachment(attachment
.asPointer());
1338 } catch (IOException
| InvalidMessageException e
) {
1339 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getId() + "): " + e
.getMessage());
1344 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1345 if (source
.equals(account
.getSelfAddress())) {
1347 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1348 } catch (InvalidInputException ignored
) {
1351 ContactInfo contact
= account
.getContactStore().getContact(source
);
1352 if (contact
== null) {
1353 contact
= new ContactInfo(source
);
1355 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1356 account
.getContactStore().updateContact(contact
);
1358 if (message
.getPreviews().isPresent()) {
1359 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1360 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1361 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1362 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1364 retrieveAttachment(attachment
);
1365 } catch (IOException
| InvalidMessageException e
) {
1366 System
.err
.println("Failed to retrieve attachment (" + attachment
.getId() + "): " + e
.getMessage());
1373 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1374 final File cachePath
= new File(getMessageCachePath());
1375 if (!cachePath
.exists()) {
1378 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1379 if (!dir
.isDirectory()) {
1383 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1384 if (!fileEntry
.isFile()) {
1387 SignalServiceEnvelope envelope
;
1389 envelope
= Utils
.loadEnvelope(fileEntry
);
1390 if (envelope
== null) {
1393 } catch (IOException e
) {
1394 e
.printStackTrace();
1397 SignalServiceContent content
= null;
1398 if (!envelope
.isReceipt()) {
1400 content
= decryptMessage(envelope
);
1401 } catch (Exception e
) {
1404 handleMessage(envelope
, content
, ignoreAttachments
);
1407 handler
.handleMessage(envelope
, content
, null);
1409 Files
.delete(fileEntry
.toPath());
1410 } catch (IOException e
) {
1411 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1414 // Try to delete directory if empty
1419 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1420 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1421 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1424 if (messagePipe
== null) {
1425 messagePipe
= messageReceiver
.createMessagePipe();
1429 SignalServiceEnvelope envelope
;
1430 SignalServiceContent content
= null;
1431 Exception exception
= null;
1432 final long now
= new Date().getTime();
1434 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1435 // store message on disk, before acknowledging receipt to the server
1437 File cacheFile
= getMessageCacheFile(envelope1
.getSourceE164().get(), now
, envelope1
.getTimestamp());
1438 Utils
.storeEnvelope(envelope1
, cacheFile
);
1439 } catch (IOException e
) {
1440 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1443 } catch (TimeoutException e
) {
1444 if (returnOnTimeout
)
1447 } catch (InvalidVersionException e
) {
1448 System
.err
.println("Ignoring error: " + e
.getMessage());
1451 if (!envelope
.isReceipt()) {
1453 content
= decryptMessage(envelope
);
1454 } catch (Exception e
) {
1457 handleMessage(envelope
, content
, ignoreAttachments
);
1460 if (!isMessageBlocked(envelope
, content
)) {
1461 handler
.handleMessage(envelope
, content
, exception
);
1463 if (!(exception
instanceof ProtocolUntrustedIdentityException
)) {
1464 File cacheFile
= null;
1466 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1467 Files
.delete(cacheFile
.toPath());
1468 // Try to delete directory if empty
1469 new File(getMessageCachePath()).delete();
1470 } catch (IOException e
) {
1471 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1476 if (messagePipe
!= null) {
1477 messagePipe
.shutdown();
1483 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1484 SignalServiceAddress source
;
1485 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1486 source
= envelope
.getSourceAddress();
1487 } else if (content
!= null) {
1488 source
= content
.getSender();
1492 ContactInfo sourceContact
= getContact(source
.getNumber().get());
1493 if (sourceContact
!= null && sourceContact
.blocked
) {
1497 if (content
!= null && content
.getDataMessage().isPresent()) {
1498 SignalServiceDataMessage message
= content
.getDataMessage().get();
1499 if (message
.getGroupInfo().isPresent()) {
1500 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1501 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1502 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1510 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1511 if (content
!= null) {
1512 SignalServiceAddress sender
;
1513 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1514 sender
= envelope
.getSourceAddress();
1516 sender
= content
.getSender();
1518 if (content
.getDataMessage().isPresent()) {
1519 SignalServiceDataMessage message
= content
.getDataMessage().get();
1521 if (content
.isNeedsReceipt()) {
1523 sendReceipt(sender
, message
.getTimestamp());
1524 } catch (IOException
| UntrustedIdentityException e
) {
1525 e
.printStackTrace();
1529 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1531 if (content
.getSyncMessage().isPresent()) {
1532 account
.setMultiDevice(true);
1533 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1534 if (syncMessage
.getSent().isPresent()) {
1535 SentTranscriptMessage message
= syncMessage
.getSent().get();
1536 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1538 if (syncMessage
.getRequest().isPresent()) {
1539 RequestMessage rm
= syncMessage
.getRequest().get();
1540 if (rm
.isContactsRequest()) {
1543 } catch (UntrustedIdentityException
| IOException e
) {
1544 e
.printStackTrace();
1547 if (rm
.isGroupsRequest()) {
1550 } catch (UntrustedIdentityException
| IOException e
) {
1551 e
.printStackTrace();
1554 if (rm
.isBlockedListRequest()) {
1557 } catch (UntrustedIdentityException
| IOException e
) {
1558 e
.printStackTrace();
1561 // TODO Handle rm.isConfigurationRequest();
1563 if (syncMessage
.getGroups().isPresent()) {
1564 File tmpFile
= null;
1566 tmpFile
= IOUtils
.createTempFile();
1567 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1568 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1570 while ((g
= s
.read()) != null) {
1571 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1572 if (syncGroup
== null) {
1573 syncGroup
= new GroupInfo(g
.getId());
1575 if (g
.getName().isPresent()) {
1576 syncGroup
.name
= g
.getName().get();
1578 syncGroup
.addMembers(g
.getMembers());
1579 if (!g
.isActive()) {
1580 syncGroup
.removeMember(account
.getSelfAddress());
1582 // Add ourself to the member set as it's marked as active
1583 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1585 syncGroup
.blocked
= g
.isBlocked();
1586 if (g
.getColor().isPresent()) {
1587 syncGroup
.color
= g
.getColor().get();
1590 if (g
.getAvatar().isPresent()) {
1591 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1593 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1594 syncGroup
.archived
= g
.isArchived();
1595 account
.getGroupStore().updateGroup(syncGroup
);
1598 } catch (Exception e
) {
1599 e
.printStackTrace();
1601 if (tmpFile
!= null) {
1603 Files
.delete(tmpFile
.toPath());
1604 } catch (IOException e
) {
1605 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1610 if (syncMessage
.getBlockedList().isPresent()) {
1611 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1612 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1613 if (address
.getNumber().isPresent()) {
1615 setContactBlocked(address
.getNumber().get(), true);
1616 } catch (InvalidNumberException e
) {
1617 e
.printStackTrace();
1621 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1623 setGroupBlocked(groupId
, true);
1624 } catch (GroupNotFoundException e
) {
1625 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1629 if (syncMessage
.getContacts().isPresent()) {
1630 File tmpFile
= null;
1632 tmpFile
= IOUtils
.createTempFile();
1633 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1634 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1635 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1636 if (contactsMessage
.isComplete()) {
1637 account
.getContactStore().clear();
1640 while ((c
= s
.read()) != null) {
1641 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1642 account
.setProfileKey(c
.getProfileKey().get());
1644 ContactInfo contact
= account
.getContactStore().getContact(c
.getAddress());
1645 if (contact
== null) {
1646 contact
= new ContactInfo(c
.getAddress());
1648 if (c
.getName().isPresent()) {
1649 contact
.name
= c
.getName().get();
1651 if (c
.getColor().isPresent()) {
1652 contact
.color
= c
.getColor().get();
1654 if (c
.getProfileKey().isPresent()) {
1655 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1657 if (c
.getVerified().isPresent()) {
1658 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1659 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1661 if (c
.getExpirationTimer().isPresent()) {
1662 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1664 contact
.blocked
= c
.isBlocked();
1665 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1666 contact
.archived
= c
.isArchived();
1667 account
.getContactStore().updateContact(contact
);
1669 if (c
.getAvatar().isPresent()) {
1670 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1674 } catch (Exception e
) {
1675 e
.printStackTrace();
1677 if (tmpFile
!= null) {
1679 Files
.delete(tmpFile
.toPath());
1680 } catch (IOException e
) {
1681 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1686 if (syncMessage
.getVerified().isPresent()) {
1687 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1688 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1690 if (syncMessage
.getConfiguration().isPresent()) {
1697 private File
getContactAvatarFile(String number
) {
1698 return new File(avatarsPath
, "contact-" + number
);
1701 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
{
1702 IOUtils
.createPrivateDirectories(avatarsPath
);
1703 if (attachment
.isPointer()) {
1704 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1705 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1707 SignalServiceAttachmentStream stream
= attachment
.asStream();
1708 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1712 private File
getGroupAvatarFile(byte[] groupId
) {
1713 return new File(avatarsPath
, "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1716 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
{
1717 IOUtils
.createPrivateDirectories(avatarsPath
);
1718 if (attachment
.isPointer()) {
1719 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1720 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1722 SignalServiceAttachmentStream stream
= attachment
.asStream();
1723 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1727 public File
getAttachmentFile(long attachmentId
) {
1728 return new File(attachmentsPath
, attachmentId
+ "");
1731 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
{
1732 IOUtils
.createPrivateDirectories(attachmentsPath
);
1733 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getId()), true);
1736 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
{
1737 if (storePreview
&& pointer
.getPreview().isPresent()) {
1738 File previewFile
= new File(outputFile
+ ".preview");
1739 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1740 byte[] preview
= pointer
.getPreview().get();
1741 output
.write(preview
, 0, preview
.length
);
1742 } catch (FileNotFoundException e
) {
1743 e
.printStackTrace();
1748 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1750 File tmpFile
= IOUtils
.createTempFile();
1751 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
)) {
1752 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1753 byte[] buffer
= new byte[4096];
1756 while ((read
= input
.read(buffer
)) != -1) {
1757 output
.write(buffer
, 0, read
);
1759 } catch (FileNotFoundException e
) {
1760 e
.printStackTrace();
1765 Files
.delete(tmpFile
.toPath());
1766 } catch (IOException e
) {
1767 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1773 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
{
1774 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1775 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
);
1779 public boolean isRemote() {
1783 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1784 File groupsFile
= IOUtils
.createTempFile();
1787 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1788 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1789 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1790 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1791 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1792 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1793 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1797 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1798 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1799 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1800 .withStream(groupsFileStream
)
1801 .withContentType("application/octet-stream")
1802 .withLength(groupsFile
.length())
1805 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1810 Files
.delete(groupsFile
.toPath());
1811 } catch (IOException e
) {
1812 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1817 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1818 File contactsFile
= IOUtils
.createTempFile();
1821 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1822 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1823 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1824 VerifiedMessage verifiedMessage
= null;
1825 if (getIdentities().containsKey(record.number
)) {
1826 JsonIdentityKeyStore
.Identity currentIdentity
= null;
1827 for (JsonIdentityKeyStore
.Identity id
: getIdentities().get(record.number
)) {
1828 if (currentIdentity
== null || id
.getDateAdded().after(currentIdentity
.getDateAdded())) {
1829 currentIdentity
= id
;
1832 if (currentIdentity
!= null) {
1833 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1837 ProfileKey profileKey
= null;
1839 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1840 } catch (InvalidInputException ignored
) {
1842 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1843 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1844 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1845 Optional
.of(record.messageExpirationTime
),
1846 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1849 if (account
.getProfileKey() != null) {
1850 // Send our own profile key as well
1851 out
.write(new DeviceContact(account
.getSelfAddress(),
1852 Optional
.absent(), Optional
.absent(),
1853 Optional
.absent(), Optional
.absent(),
1854 Optional
.of(account
.getProfileKey()),
1855 false, Optional
.absent(), Optional
.absent(), false));
1859 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1860 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1861 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1862 .withStream(contactsFileStream
)
1863 .withContentType("application/octet-stream")
1864 .withLength(contactsFile
.length())
1867 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1872 Files
.delete(contactsFile
.toPath());
1873 } catch (IOException e
) {
1874 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1879 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1880 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1881 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1882 if (record.blocked
) {
1883 addresses
.add(record.getAddress());
1886 List
<byte[]> groupIds
= new ArrayList
<>();
1887 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1888 if (record.blocked
) {
1889 groupIds
.add(record.groupId
);
1892 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1895 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1896 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1897 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1900 public List
<ContactInfo
> getContacts() {
1901 return account
.getContactStore().getContacts();
1904 public ContactInfo
getContact(String number
) {
1905 return account
.getContactStore().getContact(new SignalServiceAddress(null, number
));
1908 public GroupInfo
getGroup(byte[] groupId
) {
1909 return account
.getGroupStore().getGroup(groupId
);
1912 public Map
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities() {
1913 return account
.getSignalProtocolStore().getIdentities();
1916 public Pair
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities(String number
) throws InvalidNumberException
{
1917 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, account
.getUsername());
1918 return new Pair
<>(canonicalizedNumber
, account
.getSignalProtocolStore().getIdentities(canonicalizedNumber
));
1922 * Trust this the identity with this fingerprint
1924 * @param name username of the identity
1925 * @param fingerprint Fingerprint
1927 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) {
1928 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1932 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1933 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1937 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1939 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1940 } catch (IOException
| UntrustedIdentityException e
) {
1941 e
.printStackTrace();
1950 * Trust this the identity with this safety number
1952 * @param name username of the identity
1953 * @param safetyNumber Safety number
1955 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) {
1956 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1960 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1961 if (!safetyNumber
.equals(computeSafetyNumber(name
, id
.getIdentityKey()))) {
1965 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1967 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1968 } catch (IOException
| UntrustedIdentityException e
) {
1969 e
.printStackTrace();
1978 * Trust all keys of this identity without verification
1980 * @param name username of the identity
1982 public boolean trustIdentityAllKeys(String name
) {
1983 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1987 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1988 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1989 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1991 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1992 } catch (IOException
| UntrustedIdentityException e
) {
1993 e
.printStackTrace();
2001 public String
computeSafetyNumber(String theirUsername
, IdentityKey theirIdentityKey
) {
2002 return Utils
.computeSafetyNumber(account
.getUsername(), getIdentity(), theirUsername
, theirIdentityKey
);
2005 public interface ReceiveMessageHandler
{
2007 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);