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
.storage
.threads
.ThreadInfo
;
35 import org
.asamk
.signal
.util
.IOUtils
;
36 import org
.asamk
.signal
.util
.Util
;
37 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
38 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
39 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
40 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
41 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
42 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
43 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
44 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
45 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
46 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
47 import org
.signal
.libsignal
.metadata
.SelfSendException
;
48 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
49 import org
.signal
.zkgroup
.InvalidInputException
;
50 import org
.signal
.zkgroup
.VerificationFailedException
;
51 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
52 import org
.whispersystems
.libsignal
.IdentityKey
;
53 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
54 import org
.whispersystems
.libsignal
.InvalidKeyException
;
55 import org
.whispersystems
.libsignal
.InvalidMessageException
;
56 import org
.whispersystems
.libsignal
.InvalidVersionException
;
57 import org
.whispersystems
.libsignal
.ecc
.Curve
;
58 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
59 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
60 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
61 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
62 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
63 import org
.whispersystems
.libsignal
.util
.Medium
;
64 import org
.whispersystems
.libsignal
.util
.Pair
;
65 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
66 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
67 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
68 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
69 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
70 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
71 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
72 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
73 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
74 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
75 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
79 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
80 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
81 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
82 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
83 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
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
.concurrent
.TimeUnit
;
141 import java
.util
.concurrent
.TimeoutException
;
142 import java
.util
.zip
.ZipEntry
;
143 import java
.util
.zip
.ZipFile
;
145 public class Manager
implements Signal
{
147 private static final SignalServiceProfile
.Capabilities capabilities
= new SignalServiceProfile
.Capabilities(false, false);
149 private final String settingsPath
;
150 private final String dataPath
;
151 private final String attachmentsPath
;
152 private final String avatarsPath
;
153 private final SleepTimer timer
= new UptimeSleepTimer();
155 private SignalAccount account
;
156 private String username
;
157 private SignalServiceAccountManager accountManager
;
158 private SignalServiceMessagePipe messagePipe
= null;
159 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
161 public Manager(String username
, String settingsPath
) {
162 this.username
= username
;
163 this.settingsPath
= settingsPath
;
164 this.dataPath
= this.settingsPath
+ "/data";
165 this.attachmentsPath
= this.settingsPath
+ "/attachments";
166 this.avatarsPath
= this.settingsPath
+ "/avatars";
170 public String
getUsername() {
174 private SignalServiceAddress
getSelfAddress() {
175 return new SignalServiceAddress(null, username
);
178 private SignalServiceAccountManager
getSignalServiceAccountManager() {
179 return new SignalServiceAccountManager(BaseConfig
.serviceConfiguration
, null, account
.getUsername(), account
.getPassword(), account
.getDeviceId(), BaseConfig
.USER_AGENT
, timer
);
182 private IdentityKey
getIdentity() {
183 return account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey();
186 public int getDeviceId() {
187 return account
.getDeviceId();
190 private String
getMessageCachePath() {
191 return this.dataPath
+ "/" + username
+ ".d/msg-cache";
194 private String
getMessageCachePath(String sender
) {
195 return getMessageCachePath() + "/" + sender
.replace("/", "_");
198 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
199 String cachePath
= getMessageCachePath(sender
);
200 IOUtils
.createPrivateDirectories(cachePath
);
201 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
204 public boolean userHasKeys() {
205 return account
!= null && account
.getSignalProtocolStore() != null;
208 public void init() throws IOException
{
209 if (!SignalAccount
.userExists(dataPath
, username
)) {
212 account
= SignalAccount
.load(dataPath
, username
);
214 migrateLegacyConfigs();
216 accountManager
= getSignalServiceAccountManager();
218 if (account
.isRegistered() && accountManager
.getPreKeysCount() < BaseConfig
.PREKEY_MINIMUM_COUNT
) {
222 } catch (AuthorizationFailedException e
) {
223 System
.err
.println("Authorization failed, was the number registered elsewhere?");
228 private void migrateLegacyConfigs() {
229 // Copy group avatars that were previously stored in the attachments folder
230 // to the new avatar folder
231 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
232 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
233 File avatarFile
= getGroupAvatarFile(g
.groupId
);
234 File attachmentFile
= getAttachmentFile(g
.getAvatarId());
235 if (!avatarFile
.exists() && attachmentFile
.exists()) {
237 IOUtils
.createPrivateDirectories(avatarsPath
);
238 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
239 } catch (Exception e
) {
244 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
247 if (account
.getProfileKey() == null) {
248 // Old config file, creating new profile key
249 account
.setProfileKey(KeyUtils
.createProfileKey());
254 private void createNewIdentity() throws IOException
{
255 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
256 int registrationId
= KeyHelper
.generateRegistrationId(false);
257 if (username
== null) {
258 account
= SignalAccount
.createTemporaryAccount(identityKey
, registrationId
);
260 ProfileKey profileKey
= KeyUtils
.createProfileKey();
261 account
= SignalAccount
.create(dataPath
, username
, identityKey
, registrationId
, profileKey
);
266 public boolean isRegistered() {
267 return account
!= null && account
.isRegistered();
270 public void register(boolean voiceVerification
) throws IOException
{
271 if (account
== null) {
274 account
.setPassword(KeyUtils
.createPassword());
275 accountManager
= getSignalServiceAccountManager();
277 if (voiceVerification
) {
278 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
280 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
283 account
.setRegistered(false);
287 public void updateAccountAttributes() throws IOException
{
288 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities
);
291 public void setProfileName(String name
) throws IOException
{
292 accountManager
.setProfileName(account
.getProfileKey(), name
);
295 public void setProfileAvatar(File avatar
) throws IOException
{
296 final StreamDetails streamDetails
= Utils
.createStreamDetailsFromFile(avatar
);
297 accountManager
.setProfileAvatar(account
.getProfileKey(), streamDetails
);
298 streamDetails
.getStream().close();
301 public void removeProfileAvatar() throws IOException
{
302 accountManager
.setProfileAvatar(account
.getProfileKey(), null);
305 public void unregister() throws IOException
{
306 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
307 // If this is the master device, other users can't send messages to this number anymore.
308 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
309 accountManager
.setGcmId(Optional
.absent());
311 account
.setRegistered(false);
315 public String
getDeviceLinkUri() throws TimeoutException
, IOException
{
316 if (account
== null) {
319 account
.setPassword(KeyUtils
.createPassword());
320 accountManager
= getSignalServiceAccountManager();
321 String uuid
= accountManager
.getNewDeviceUuid();
323 return Utils
.createDeviceLinkUri(new Utils
.DeviceLinkInfo(uuid
, getIdentity().getPublicKey()));
326 public void finishDeviceLink(String deviceName
) throws IOException
, InvalidKeyException
, TimeoutException
, UserAlreadyExists
{
327 account
.setSignalingKey(KeyUtils
.createSignalingKey());
328 SignalServiceAccountManager
.NewDeviceRegistrationReturn ret
= accountManager
.finishNewDeviceRegistration(account
.getSignalProtocolStore().getIdentityKeyPair(), account
.getSignalingKey(), false, true, account
.getSignalProtocolStore().getLocalRegistrationId(), deviceName
);
330 username
= ret
.getNumber();
331 // TODO do this check before actually registering
332 if (SignalAccount
.userExists(dataPath
, username
)) {
333 throw new UserAlreadyExists(username
, SignalAccount
.getFileName(dataPath
, username
));
336 // Create new account with the synced identity
337 byte[] profileKeyBytes
= ret
.getProfileKey();
338 ProfileKey profileKey
;
339 if (profileKeyBytes
== null) {
340 profileKey
= KeyUtils
.createProfileKey();
343 profileKey
= new ProfileKey(profileKeyBytes
);
344 } catch (InvalidInputException e
) {
345 throw new IOException("Received invalid profileKey", e
);
348 account
= SignalAccount
.createLinkedAccount(dataPath
, username
, account
.getPassword(), ret
.getDeviceId(), ret
.getIdentity(), account
.getSignalProtocolStore().getLocalRegistrationId(), account
.getSignalingKey(), profileKey
);
353 requestSyncContacts();
354 requestSyncBlocked();
355 requestSyncConfiguration();
360 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
361 List
<DeviceInfo
> devices
= accountManager
.getDevices();
362 account
.setMultiDevice(devices
.size() > 1);
367 public void removeLinkedDevices(int deviceId
) throws IOException
{
368 accountManager
.removeDevice(deviceId
);
369 List
<DeviceInfo
> devices
= accountManager
.getDevices();
370 account
.setMultiDevice(devices
.size() > 1);
374 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
375 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
377 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
380 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
381 IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
382 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
384 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
385 account
.setMultiDevice(true);
389 private List
<PreKeyRecord
> generatePreKeys() {
390 List
<PreKeyRecord
> records
= new ArrayList
<>(BaseConfig
.PREKEY_BATCH_SIZE
);
392 final int offset
= account
.getPreKeyIdOffset();
393 for (int i
= 0; i
< BaseConfig
.PREKEY_BATCH_SIZE
; i
++) {
394 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
395 ECKeyPair keyPair
= Curve
.generateKeyPair();
396 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
401 account
.addPreKeys(records
);
407 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
409 ECKeyPair keyPair
= Curve
.generateKeyPair();
410 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
411 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
413 account
.addSignedPreKey(record);
417 } catch (InvalidKeyException e
) {
418 throw new AssertionError(e
);
422 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
423 verificationCode
= verificationCode
.replace("-", "");
424 account
.setSignalingKey(KeyUtils
.createSignalingKey());
425 // TODO make unrestricted unidentified access configurable
426 accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, capabilities
);
428 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
429 account
.setRegistered(true);
430 account
.setRegistrationLockPin(pin
);
436 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
437 if (pin
.isPresent()) {
438 account
.setRegistrationLockPin(pin
.get());
439 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
441 account
.setRegistrationLockPin(null);
442 accountManager
.removeV1Pin();
447 private void refreshPreKeys() throws IOException
{
448 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
449 final IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
450 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
452 accountManager
.setPreKeys(getIdentity(), signedPreKeyRecord
, oneTimePreKeys
);
455 private SignalServiceMessageReceiver
getMessageReceiver() {
456 return new SignalServiceMessageReceiver(BaseConfig
.serviceConfiguration
, null, username
, account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), BaseConfig
.USER_AGENT
, null, timer
);
459 private SignalServiceMessageSender
getMessageSender() {
460 return new SignalServiceMessageSender(BaseConfig
.serviceConfiguration
, null, username
, account
.getPassword(),
461 account
.getDeviceId(), account
.getSignalProtocolStore(), BaseConfig
.USER_AGENT
, account
.isMultiDevice(), Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent());
464 private SignalServiceProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
465 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
470 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
471 } catch (IOException ignored
) {
475 SignalServiceMessageReceiver receiver
= getMessageReceiver();
477 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
478 } catch (VerificationFailedException e
) {
479 throw new AssertionError(e
);
483 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
484 File file
= getGroupAvatarFile(groupId
);
485 if (!file
.exists()) {
486 return Optional
.absent();
489 return Optional
.of(Utils
.createAttachment(file
));
492 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
493 File file
= getContactAvatarFile(number
);
494 if (!file
.exists()) {
495 return Optional
.absent();
498 return Optional
.of(Utils
.createAttachment(file
));
501 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
502 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
504 throw new GroupNotFoundException(groupId
);
506 for (String member
: g
.members
) {
507 if (member
.equals(this.username
)) {
511 throw new NotAGroupMemberException(groupId
, g
.name
);
514 public List
<GroupInfo
> getGroups() {
515 return account
.getGroupStore().getGroups();
519 public void sendGroupMessage(String messageText
, List
<String
> attachments
,
521 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
522 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
523 if (attachments
!= null) {
524 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
526 if (groupId
!= null) {
527 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
530 messageBuilder
.asGroupMessage(group
);
532 ThreadInfo thread
= account
.getThreadStore().getThread(Base64
.encodeBytes(groupId
));
533 if (thread
!= null) {
534 messageBuilder
.withExpiration(thread
.messageExpirationTime
);
537 final GroupInfo g
= getGroupForSending(groupId
);
539 // Don't send group message to ourself
540 final List
<String
> membersSend
= new ArrayList
<>(g
.members
);
541 membersSend
.remove(this.username
);
542 sendMessageLegacy(messageBuilder
, membersSend
);
545 public void sendGroupMessageReaction(String emoji
, boolean remove
, SignalServiceAddress targetAuthor
,
546 long targetSentTimestamp
, byte[] groupId
)
547 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
{
548 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
);
549 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
550 .withReaction(reaction
)
551 .withProfileKey(account
.getProfileKey().serialize());
552 if (groupId
!= null) {
553 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
556 messageBuilder
.asGroupMessage(group
);
558 final GroupInfo g
= getGroupForSending(groupId
);
559 // Don't send group message to ourself
560 final List
<String
> membersSend
= new ArrayList
<>(g
.members
);
561 membersSend
.remove(this.username
);
562 sendMessageLegacy(messageBuilder
, membersSend
);
565 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
{
566 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
570 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
571 .asGroupMessage(group
);
573 final GroupInfo g
= getGroupForSending(groupId
);
574 g
.members
.remove(this.username
);
575 account
.getGroupStore().updateGroup(g
);
577 sendMessageLegacy(messageBuilder
, g
.members
);
580 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<String
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
582 if (groupId
== null) {
584 g
= new GroupInfo(KeyUtils
.createGroupId());
585 g
.members
.add(username
);
587 g
= getGroupForSending(groupId
);
594 if (members
!= null) {
595 Set
<String
> newMembers
= new HashSet
<>();
596 for (String member
: members
) {
598 member
= Utils
.canonicalizeNumber(member
, username
);
599 } catch (InvalidNumberException e
) {
600 System
.err
.println("Failed to add member \"" + member
+ "\" to group: " + e
.getMessage());
601 System
.err
.println("Aborting…");
604 if (g
.members
.contains(member
)) {
607 newMembers
.add(member
);
608 g
.members
.add(member
);
610 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newMembers
);
611 if (contacts
.size() != newMembers
.size()) {
612 // Some of the new members are not registered on Signal
613 for (ContactTokenDetails contact
: contacts
) {
614 newMembers
.remove(contact
.getNumber());
616 System
.err
.println("Failed to add members " + Util
.join(", ", newMembers
) + " to group: Not registered on Signal");
617 System
.err
.println("Aborting…");
622 if (avatarFile
!= null) {
623 IOUtils
.createPrivateDirectories(avatarsPath
);
624 File aFile
= getGroupAvatarFile(g
.groupId
);
625 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
628 account
.getGroupStore().updateGroup(g
);
630 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
632 // Don't send group message to ourself
633 final List
<String
> membersSend
= new ArrayList
<>(g
.members
);
634 membersSend
.remove(this.username
);
635 sendMessageLegacy(messageBuilder
, membersSend
);
639 private void sendUpdateGroupMessage(byte[] groupId
, String recipient
) throws IOException
, EncapsulatedExceptions
{
640 if (groupId
== null) {
643 GroupInfo g
= getGroupForSending(groupId
);
645 if (!g
.members
.contains(recipient
)) {
649 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
651 // Send group message only to the recipient who requested it
652 final List
<String
> membersSend
= new ArrayList
<>();
653 membersSend
.add(recipient
);
654 sendMessageLegacy(messageBuilder
, membersSend
);
657 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) {
658 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
661 .withMembers(new ArrayList
<>(g
.getMembers()));
663 File aFile
= getGroupAvatarFile(g
.groupId
);
664 if (aFile
.exists()) {
666 group
.withAvatar(Utils
.createAttachment(aFile
));
667 } catch (IOException e
) {
668 throw new AttachmentInvalidException(aFile
.toString(), e
);
672 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
673 .asGroupMessage(group
.build());
675 ThreadInfo thread
= account
.getThreadStore().getThread(Base64
.encodeBytes(g
.groupId
));
676 if (thread
!= null) {
677 messageBuilder
.withExpiration(thread
.messageExpirationTime
);
680 return messageBuilder
;
683 private void sendGroupInfoRequest(byte[] groupId
, String recipient
) throws IOException
, EncapsulatedExceptions
{
684 if (groupId
== null) {
688 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
691 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
692 .asGroupMessage(group
.build());
694 ThreadInfo thread
= account
.getThreadStore().getThread(Base64
.encodeBytes(groupId
));
695 if (thread
!= null) {
696 messageBuilder
.withExpiration(thread
.messageExpirationTime
);
699 // Send group info request message to the recipient who sent us a message with this groupId
700 final List
<String
> membersSend
= new ArrayList
<>();
701 membersSend
.add(recipient
);
702 sendMessageLegacy(messageBuilder
, membersSend
);
706 public void sendMessage(String message
, List
<String
> attachments
, String recipient
)
707 throws EncapsulatedExceptions
, AttachmentInvalidException
, IOException
{
708 List
<String
> recipients
= new ArrayList
<>(1);
709 recipients
.add(recipient
);
710 sendMessage(message
, attachments
, recipients
);
714 public void sendMessage(String messageText
, List
<String
> attachments
,
715 List
<String
> recipients
)
716 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
{
717 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
718 if (attachments
!= null) {
719 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
721 // Upload attachments here, so we only upload once even for multiple recipients
722 SignalServiceMessageSender messageSender
= getMessageSender();
723 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
724 for (SignalServiceAttachment attachment
: attachmentStreams
) {
725 if (attachment
.isStream()) {
726 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
727 } else if (attachment
.isPointer()) {
728 attachmentPointers
.add(attachment
.asPointer());
732 messageBuilder
.withAttachments(attachmentPointers
);
734 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
735 sendMessageLegacy(messageBuilder
, recipients
);
738 public void sendMessageReaction(String emoji
, boolean remove
, SignalServiceAddress targetAuthor
,
739 long targetSentTimestamp
, List
<String
> recipients
)
740 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
{
741 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
);
742 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
743 .withReaction(reaction
)
744 .withProfileKey(account
.getProfileKey().serialize());
745 sendMessageLegacy(messageBuilder
, recipients
);
749 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
{
750 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
751 .asEndSessionMessage();
753 sendMessageLegacy(messageBuilder
, recipients
);
757 public String
getContactName(String number
) throws InvalidNumberException
{
758 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, username
);
759 ContactInfo contact
= account
.getContactStore().getContact(canonicalizedNumber
);
760 if (contact
== null) {
768 public void setContactName(String number
, String name
) throws InvalidNumberException
{
769 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, username
);
770 ContactInfo contact
= account
.getContactStore().getContact(canonicalizedNumber
);
771 if (contact
== null) {
772 contact
= new ContactInfo();
773 contact
.number
= canonicalizedNumber
;
774 System
.err
.println("Add contact " + canonicalizedNumber
+ " named " + name
);
776 System
.err
.println("Updating contact " + canonicalizedNumber
+ " name " + contact
.name
+ " -> " + name
);
779 account
.getContactStore().updateContact(contact
);
784 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
785 number
= Utils
.canonicalizeNumber(number
, username
);
786 ContactInfo contact
= account
.getContactStore().getContact(number
);
787 if (contact
== null) {
788 contact
= new ContactInfo();
789 contact
.number
= number
;
790 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + number
);
792 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + number
);
794 contact
.blocked
= blocked
;
795 account
.getContactStore().updateContact(contact
);
800 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
801 GroupInfo group
= getGroup(groupId
);
803 throw new GroupNotFoundException(groupId
);
805 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
806 group
.blocked
= blocked
;
807 account
.getGroupStore().updateGroup(group
);
813 public List
<byte[]> getGroupIds() {
814 List
<GroupInfo
> groups
= getGroups();
815 List
<byte[]> ids
= new ArrayList
<>(groups
.size());
816 for (GroupInfo group
: groups
) {
817 ids
.add(group
.groupId
);
823 public String
getGroupName(byte[] groupId
) {
824 GroupInfo group
= getGroup(groupId
);
833 public List
<String
> getGroupMembers(byte[] groupId
) {
834 GroupInfo group
= getGroup(groupId
);
836 return new ArrayList
<>();
838 return new ArrayList
<>(group
.members
);
843 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
844 if (groupId
.length
== 0) {
847 if (name
.isEmpty()) {
850 if (members
.size() == 0) {
853 if (avatar
.isEmpty()) {
856 return sendUpdateGroupMessage(groupId
, name
, members
, avatar
);
860 * Change the expiration timer for a thread (number of groupId)
862 * @param numberOrGroupId
863 * @param messageExpirationTimer
865 public void setExpirationTimer(String numberOrGroupId
, int messageExpirationTimer
) {
866 ThreadInfo thread
= account
.getThreadStore().getThread(numberOrGroupId
);
867 thread
.messageExpirationTime
= messageExpirationTimer
;
868 account
.getThreadStore().updateThread(thread
);
872 * Upload the sticker pack from path.
874 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
875 * @return if successful, returns the URL to install the sticker pack in the signal app
877 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
878 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
880 SignalServiceMessageSender messageSender
= getMessageSender();
882 byte[] packKey
= KeyUtils
.createStickerUploadKey();
883 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
886 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
888 } catch (URISyntaxException e
) {
889 throw new AssertionError(e
);
893 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
895 String rootPath
= null;
897 final File file
= new File(path
);
898 if (file
.getName().endsWith(".zip")) {
899 zip
= new ZipFile(file
);
900 } else if (file
.getName().equals("manifest.json")) {
901 rootPath
= file
.getParent();
903 throw new StickerPackInvalidException("Could not find manifest.json");
906 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
908 if (pack
.stickers
== null) {
909 throw new StickerPackInvalidException("Must set a 'stickers' field.");
912 if (pack
.stickers
.isEmpty()) {
913 throw new StickerPackInvalidException("Must include stickers.");
916 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
917 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
918 if (sticker
.file
== null) {
919 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
922 Pair
<InputStream
, Long
> data
;
924 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
925 } catch (IOException ignored
) {
926 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
929 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
930 stickers
.add(stickerInfo
);
933 StickerInfo cover
= null;
934 if (pack
.cover
!= null) {
935 if (pack
.cover
.file
== null) {
936 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
939 Pair
<InputStream
, Long
> data
;
941 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
942 } catch (IOException ignored
) {
943 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
946 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
949 return new SignalServiceStickerManifestUpload(
956 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
, StickerPackInvalidException
{
957 InputStream inputStream
;
959 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
961 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
963 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
966 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
968 final ZipEntry entry
= zip
.getEntry(subfile
);
969 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
971 final File file
= new File(rootPath
, subfile
);
972 return new Pair
<>(new FileInputStream(file
), file
.length());
976 private void requestSyncGroups() throws IOException
{
977 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
978 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
980 sendSyncMessage(message
);
981 } catch (UntrustedIdentityException e
) {
986 private void requestSyncContacts() throws IOException
{
987 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
988 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
990 sendSyncMessage(message
);
991 } catch (UntrustedIdentityException e
) {
996 private void requestSyncBlocked() throws IOException
{
997 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
998 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1000 sendSyncMessage(message
);
1001 } catch (UntrustedIdentityException e
) {
1002 e
.printStackTrace();
1006 private void requestSyncConfiguration() throws IOException
{
1007 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
1008 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1010 sendSyncMessage(message
);
1011 } catch (UntrustedIdentityException e
) {
1012 e
.printStackTrace();
1016 private byte[] getSenderCertificate() throws IOException
{
1017 byte[] certificate
= accountManager
.getSenderCertificate();
1018 // TODO cache for a day
1022 private byte[] getSelfUnidentifiedAccessKey() {
1023 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
1026 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
1027 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
1029 return new SignalProfile(
1030 encryptedProfile
.getIdentityKey(),
1031 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
1032 encryptedProfile
.getAvatar(),
1033 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
1034 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
1036 } catch (InvalidCiphertextException e
) {
1041 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) throws IOException
{
1042 ContactInfo contact
= account
.getContactStore().getContact(recipient
.getNumber().get());
1043 if (contact
== null || contact
.profileKey
== null) {
1046 ProfileKey theirProfileKey
;
1048 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
1049 } catch (InvalidInputException e
) {
1050 throw new AssertionError(e
);
1052 SignalProfile targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
1054 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1058 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1059 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1062 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1065 private Optional
<UnidentifiedAccessPair
> getAccessForSync() throws IOException
{
1066 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1067 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1069 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1070 return Optional
.absent();
1074 return Optional
.of(new UnidentifiedAccessPair(
1075 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1076 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1078 } catch (InvalidCertificateException e
) {
1079 return Optional
.absent();
1083 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) throws IOException
{
1084 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1085 for (SignalServiceAddress recipient
: recipients
) {
1086 result
.add(getAccessFor(recipient
));
1091 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) throws IOException
{
1092 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1093 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1094 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1096 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1097 return Optional
.absent();
1101 return Optional
.of(new UnidentifiedAccessPair(
1102 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1103 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1105 } catch (InvalidCertificateException e
) {
1106 return Optional
.absent();
1110 private void sendSyncMessage(SignalServiceSyncMessage message
)
1111 throws IOException
, UntrustedIdentityException
{
1112 SignalServiceMessageSender messageSender
= getMessageSender();
1114 messageSender
.sendMessage(message
, getAccessForSync());
1115 } catch (UntrustedIdentityException e
) {
1116 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1122 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1124 private void sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<String
> recipients
)
1125 throws EncapsulatedExceptions
, IOException
{
1126 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1128 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1129 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1130 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1132 for (SendMessageResult result
: results
) {
1133 if (result
.isUnregisteredFailure()) {
1134 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getNumber().get(), null));
1135 } else if (result
.isNetworkFailure()) {
1136 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getNumber().get(), null));
1137 } else if (result
.getIdentityFailure() != null) {
1138 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getNumber().get(), result
.getIdentityFailure().getIdentityKey()));
1141 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1142 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1146 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<String
> recipients
)
1147 throws IOException
{
1148 Set
<SignalServiceAddress
> recipientsTS
= Utils
.getSignalServiceAddresses(recipients
, username
);
1149 if (recipientsTS
== null) {
1151 return Collections
.emptyList();
1154 if (messagePipe
== null) {
1155 messagePipe
= getMessageReceiver().createMessagePipe();
1157 if (unidentifiedMessagePipe
== null) {
1158 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1160 SignalServiceDataMessage message
= null;
1162 SignalServiceMessageSender messageSender
= getMessageSender();
1164 message
= messageBuilder
.build();
1165 if (message
.getGroupInfo().isPresent()) {
1167 final boolean isRecipientUpdate
= false;
1168 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipientsTS
), getAccessFor(recipientsTS
), isRecipientUpdate
, message
);
1169 for (SendMessageResult r
: result
) {
1170 if (r
.getIdentityFailure() != null) {
1171 account
.getSignalProtocolStore().saveIdentity(r
.getAddress().getNumber().get(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1175 } catch (UntrustedIdentityException e
) {
1176 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1177 return Collections
.emptyList();
1179 } else if (recipientsTS
.size() == 1 && recipientsTS
.contains(getSelfAddress())) {
1180 SignalServiceAddress recipient
= getSelfAddress();
1181 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1182 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1183 message
.getTimestamp(),
1185 message
.getExpiresInSeconds(),
1186 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1188 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1190 List
<SendMessageResult
> results
= new ArrayList
<>(recipientsTS
.size());
1192 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1193 } catch (UntrustedIdentityException e
) {
1194 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1195 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1199 // Send to all individually, so sync messages are sent correctly
1200 List
<SendMessageResult
> results
= new ArrayList
<>(recipientsTS
.size());
1201 for (SignalServiceAddress address
: recipientsTS
) {
1202 ThreadInfo thread
= account
.getThreadStore().getThread(address
.getNumber().get());
1203 if (thread
!= null) {
1204 messageBuilder
.withExpiration(thread
.messageExpirationTime
);
1206 messageBuilder
.withExpiration(0);
1208 message
= messageBuilder
.build();
1210 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1211 results
.add(result
);
1212 } catch (UntrustedIdentityException e
) {
1213 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1214 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1220 if (message
!= null && message
.isEndSession()) {
1221 for (SignalServiceAddress recipient
: recipientsTS
) {
1222 handleEndSession(recipient
.getNumber().get());
1229 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, ProtocolUntrustedIdentityException
, SelfSendException
, UnsupportedDataMessageException
{
1230 SignalServiceCipher cipher
= new SignalServiceCipher(getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1232 return cipher
.decrypt(envelope
);
1233 } catch (ProtocolUntrustedIdentityException e
) {
1234 // TODO We don't get the new untrusted identity from ProtocolUntrustedIdentityException anymore ... we need to get it from somewhere else
1235 // account.getSignalProtocolStore().saveIdentity(e.getSender(), e.getUntrustedIdentity(), TrustLevel.UNTRUSTED);
1240 private void handleEndSession(String source
) {
1241 account
.getSignalProtocolStore().deleteAllSessions(source
);
1244 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, String source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1246 if (message
.getGroupInfo().isPresent()) {
1247 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1248 threadId
= Base64
.encodeBytes(groupInfo
.getGroupId());
1249 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1250 switch (groupInfo
.getType()) {
1252 if (group
== null) {
1253 group
= new GroupInfo(groupInfo
.getGroupId());
1256 if (groupInfo
.getAvatar().isPresent()) {
1257 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1258 if (avatar
.isPointer()) {
1260 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1261 } catch (IOException
| InvalidMessageException e
) {
1262 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getId() + "): " + e
.getMessage());
1267 if (groupInfo
.getName().isPresent()) {
1268 group
.name
= groupInfo
.getName().get();
1271 if (groupInfo
.getMembers().isPresent()) {
1272 group
.addMembers(groupInfo
.getMembers().get());
1275 account
.getGroupStore().updateGroup(group
);
1278 if (group
== null) {
1280 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1281 } catch (IOException
| EncapsulatedExceptions e
) {
1282 e
.printStackTrace();
1287 if (group
== null) {
1289 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1290 } catch (IOException
| EncapsulatedExceptions e
) {
1291 e
.printStackTrace();
1294 group
.members
.remove(source
);
1295 account
.getGroupStore().updateGroup(group
);
1299 if (group
!= null) {
1301 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1302 } catch (IOException
| EncapsulatedExceptions e
) {
1303 e
.printStackTrace();
1304 } catch (NotAGroupMemberException e
) {
1305 // We have left this group, so don't send a group update message
1312 threadId
= destination
.getNumber().get();
1317 if (message
.isEndSession()) {
1318 handleEndSession(isSync ? destination
.getNumber().get() : source
);
1320 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1321 ThreadInfo thread
= account
.getThreadStore().getThread(threadId
);
1322 if (thread
== null) {
1323 thread
= new ThreadInfo();
1324 thread
.id
= threadId
;
1326 if (thread
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1327 thread
.messageExpirationTime
= message
.getExpiresInSeconds();
1328 account
.getThreadStore().updateThread(thread
);
1331 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1332 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1333 if (attachment
.isPointer()) {
1335 retrieveAttachment(attachment
.asPointer());
1336 } catch (IOException
| InvalidMessageException e
) {
1337 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getId() + "): " + e
.getMessage());
1342 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1343 if (source
.equals(username
)) {
1345 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1346 } catch (InvalidInputException ignored
) {
1349 ContactInfo contact
= account
.getContactStore().getContact(source
);
1350 if (contact
== null) {
1351 contact
= new ContactInfo();
1352 contact
.number
= source
;
1354 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1355 account
.getContactStore().updateContact(contact
);
1357 if (message
.getPreviews().isPresent()) {
1358 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1359 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1360 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1361 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1363 retrieveAttachment(attachment
);
1364 } catch (IOException
| InvalidMessageException e
) {
1365 System
.err
.println("Failed to retrieve attachment (" + attachment
.getId() + "): " + e
.getMessage());
1372 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1373 final File cachePath
= new File(getMessageCachePath());
1374 if (!cachePath
.exists()) {
1377 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1378 if (!dir
.isDirectory()) {
1382 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1383 if (!fileEntry
.isFile()) {
1386 SignalServiceEnvelope envelope
;
1388 envelope
= Utils
.loadEnvelope(fileEntry
);
1389 if (envelope
== null) {
1392 } catch (IOException e
) {
1393 e
.printStackTrace();
1396 SignalServiceContent content
= null;
1397 if (!envelope
.isReceipt()) {
1399 content
= decryptMessage(envelope
);
1400 } catch (Exception e
) {
1403 handleMessage(envelope
, content
, ignoreAttachments
);
1406 handler
.handleMessage(envelope
, content
, null);
1408 Files
.delete(fileEntry
.toPath());
1409 } catch (IOException e
) {
1410 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1413 // Try to delete directory if empty
1418 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1419 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1420 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1423 if (messagePipe
== null) {
1424 messagePipe
= messageReceiver
.createMessagePipe();
1428 SignalServiceEnvelope envelope
;
1429 SignalServiceContent content
= null;
1430 Exception exception
= null;
1431 final long now
= new Date().getTime();
1433 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1434 // store message on disk, before acknowledging receipt to the server
1436 File cacheFile
= getMessageCacheFile(envelope1
.getSourceE164().get(), now
, envelope1
.getTimestamp());
1437 Utils
.storeEnvelope(envelope1
, cacheFile
);
1438 } catch (IOException e
) {
1439 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1442 } catch (TimeoutException e
) {
1443 if (returnOnTimeout
)
1446 } catch (InvalidVersionException e
) {
1447 System
.err
.println("Ignoring error: " + e
.getMessage());
1450 if (!envelope
.isReceipt()) {
1452 content
= decryptMessage(envelope
);
1453 } catch (Exception e
) {
1456 handleMessage(envelope
, content
, ignoreAttachments
);
1459 if (!isMessageBlocked(envelope
, content
)) {
1460 handler
.handleMessage(envelope
, content
, exception
);
1462 if (!(exception
instanceof ProtocolUntrustedIdentityException
)) {
1463 File cacheFile
= null;
1465 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1466 Files
.delete(cacheFile
.toPath());
1467 // Try to delete directory if empty
1468 new File(getMessageCachePath()).delete();
1469 } catch (IOException e
) {
1470 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1475 if (messagePipe
!= null) {
1476 messagePipe
.shutdown();
1482 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1483 SignalServiceAddress source
;
1484 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1485 source
= envelope
.getSourceAddress();
1486 } else if (content
!= null) {
1487 source
= content
.getSender();
1491 ContactInfo sourceContact
= getContact(source
.getNumber().get());
1492 if (sourceContact
!= null && sourceContact
.blocked
) {
1496 if (content
!= null && content
.getDataMessage().isPresent()) {
1497 SignalServiceDataMessage message
= content
.getDataMessage().get();
1498 if (message
.getGroupInfo().isPresent()) {
1499 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1500 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1501 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1509 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1510 if (content
!= null) {
1511 SignalServiceAddress sender
;
1512 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1513 sender
= envelope
.getSourceAddress();
1515 sender
= content
.getSender();
1517 if (content
.getDataMessage().isPresent()) {
1518 SignalServiceDataMessage message
= content
.getDataMessage().get();
1519 handleSignalServiceDataMessage(message
, false, sender
.getNumber().get(), getSelfAddress(), ignoreAttachments
);
1521 if (content
.getSyncMessage().isPresent()) {
1522 account
.setMultiDevice(true);
1523 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1524 if (syncMessage
.getSent().isPresent()) {
1525 SentTranscriptMessage message
= syncMessage
.getSent().get();
1526 handleSignalServiceDataMessage(message
.getMessage(), true, sender
.getNumber().get(), message
.getDestination().orNull(), ignoreAttachments
);
1528 if (syncMessage
.getRequest().isPresent()) {
1529 RequestMessage rm
= syncMessage
.getRequest().get();
1530 if (rm
.isContactsRequest()) {
1533 } catch (UntrustedIdentityException
| IOException e
) {
1534 e
.printStackTrace();
1537 if (rm
.isGroupsRequest()) {
1540 } catch (UntrustedIdentityException
| IOException e
) {
1541 e
.printStackTrace();
1544 if (rm
.isBlockedListRequest()) {
1547 } catch (UntrustedIdentityException
| IOException e
) {
1548 e
.printStackTrace();
1551 // TODO Handle rm.isConfigurationRequest();
1553 if (syncMessage
.getGroups().isPresent()) {
1554 File tmpFile
= null;
1556 tmpFile
= IOUtils
.createTempFile();
1557 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1558 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1560 while ((g
= s
.read()) != null) {
1561 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1562 if (syncGroup
== null) {
1563 syncGroup
= new GroupInfo(g
.getId());
1565 if (g
.getName().isPresent()) {
1566 syncGroup
.name
= g
.getName().get();
1568 syncGroup
.addMembers(g
.getMembers());
1569 if (!g
.isActive()) {
1570 syncGroup
.members
.remove(username
);
1572 syncGroup
.blocked
= g
.isBlocked();
1573 if (g
.getColor().isPresent()) {
1574 syncGroup
.color
= g
.getColor().get();
1577 if (g
.getAvatar().isPresent()) {
1578 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1580 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1581 syncGroup
.archived
= g
.isArchived();
1582 account
.getGroupStore().updateGroup(syncGroup
);
1585 } catch (Exception e
) {
1586 e
.printStackTrace();
1588 if (tmpFile
!= null) {
1590 Files
.delete(tmpFile
.toPath());
1591 } catch (IOException e
) {
1592 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1597 if (syncMessage
.getBlockedList().isPresent()) {
1598 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1599 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1600 if (address
.getNumber().isPresent()) {
1602 setContactBlocked(address
.getNumber().get(), true);
1603 } catch (InvalidNumberException e
) {
1604 e
.printStackTrace();
1608 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1610 setGroupBlocked(groupId
, true);
1611 } catch (GroupNotFoundException e
) {
1612 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1616 if (syncMessage
.getContacts().isPresent()) {
1617 File tmpFile
= null;
1619 tmpFile
= IOUtils
.createTempFile();
1620 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1621 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1622 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1623 if (contactsMessage
.isComplete()) {
1624 account
.getContactStore().clear();
1627 while ((c
= s
.read()) != null) {
1628 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1629 account
.setProfileKey(c
.getProfileKey().get());
1631 ContactInfo contact
= account
.getContactStore().getContact(c
.getAddress().getNumber().get());
1632 if (contact
== null) {
1633 contact
= new ContactInfo();
1634 contact
.number
= c
.getAddress().getNumber().get();
1636 if (c
.getName().isPresent()) {
1637 contact
.name
= c
.getName().get();
1639 if (c
.getColor().isPresent()) {
1640 contact
.color
= c
.getColor().get();
1642 if (c
.getProfileKey().isPresent()) {
1643 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1645 if (c
.getVerified().isPresent()) {
1646 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1647 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1649 if (c
.getExpirationTimer().isPresent()) {
1650 ThreadInfo thread
= account
.getThreadStore().getThread(c
.getAddress().getNumber().get());
1651 if (thread
== null) {
1652 thread
= new ThreadInfo();
1653 thread
.id
= c
.getAddress().getNumber().get();
1655 thread
.messageExpirationTime
= c
.getExpirationTimer().get();
1656 account
.getThreadStore().updateThread(thread
);
1658 contact
.blocked
= c
.isBlocked();
1659 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1660 contact
.archived
= c
.isArchived();
1661 account
.getContactStore().updateContact(contact
);
1663 if (c
.getAvatar().isPresent()) {
1664 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1668 } catch (Exception e
) {
1669 e
.printStackTrace();
1671 if (tmpFile
!= null) {
1673 Files
.delete(tmpFile
.toPath());
1674 } catch (IOException e
) {
1675 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1680 if (syncMessage
.getVerified().isPresent()) {
1681 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1682 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1684 if (syncMessage
.getConfiguration().isPresent()) {
1691 private File
getContactAvatarFile(String number
) {
1692 return new File(avatarsPath
, "contact-" + number
);
1695 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
{
1696 IOUtils
.createPrivateDirectories(avatarsPath
);
1697 if (attachment
.isPointer()) {
1698 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1699 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1701 SignalServiceAttachmentStream stream
= attachment
.asStream();
1702 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1706 private File
getGroupAvatarFile(byte[] groupId
) {
1707 return new File(avatarsPath
, "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1710 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
{
1711 IOUtils
.createPrivateDirectories(avatarsPath
);
1712 if (attachment
.isPointer()) {
1713 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1714 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1716 SignalServiceAttachmentStream stream
= attachment
.asStream();
1717 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1721 public File
getAttachmentFile(long attachmentId
) {
1722 return new File(attachmentsPath
, attachmentId
+ "");
1725 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
{
1726 IOUtils
.createPrivateDirectories(attachmentsPath
);
1727 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getId()), true);
1730 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
{
1731 if (storePreview
&& pointer
.getPreview().isPresent()) {
1732 File previewFile
= new File(outputFile
+ ".preview");
1733 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1734 byte[] preview
= pointer
.getPreview().get();
1735 output
.write(preview
, 0, preview
.length
);
1736 } catch (FileNotFoundException e
) {
1737 e
.printStackTrace();
1742 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1744 File tmpFile
= IOUtils
.createTempFile();
1745 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
)) {
1746 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1747 byte[] buffer
= new byte[4096];
1750 while ((read
= input
.read(buffer
)) != -1) {
1751 output
.write(buffer
, 0, read
);
1753 } catch (FileNotFoundException e
) {
1754 e
.printStackTrace();
1759 Files
.delete(tmpFile
.toPath());
1760 } catch (IOException e
) {
1761 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1767 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
{
1768 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1769 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
);
1773 public boolean isRemote() {
1777 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1778 File groupsFile
= IOUtils
.createTempFile();
1781 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1782 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1783 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1784 ThreadInfo info
= account
.getThreadStore().getThread(Base64
.encodeBytes(record.groupId
));
1785 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1786 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1787 record.members
.contains(username
), Optional
.fromNullable(info
!= null ? info
.messageExpirationTime
: null),
1788 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1792 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1793 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1794 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1795 .withStream(groupsFileStream
)
1796 .withContentType("application/octet-stream")
1797 .withLength(groupsFile
.length())
1800 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1805 Files
.delete(groupsFile
.toPath());
1806 } catch (IOException e
) {
1807 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1812 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1813 File contactsFile
= IOUtils
.createTempFile();
1816 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1817 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1818 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1819 VerifiedMessage verifiedMessage
= null;
1820 ThreadInfo info
= account
.getThreadStore().getThread(record.number
);
1821 if (getIdentities().containsKey(record.number
)) {
1822 JsonIdentityKeyStore
.Identity currentIdentity
= null;
1823 for (JsonIdentityKeyStore
.Identity id
: getIdentities().get(record.number
)) {
1824 if (currentIdentity
== null || id
.getDateAdded().after(currentIdentity
.getDateAdded())) {
1825 currentIdentity
= id
;
1828 if (currentIdentity
!= null) {
1829 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1833 ProfileKey profileKey
= null;
1835 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1836 } catch (InvalidInputException ignored
) {
1838 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1839 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1840 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1841 Optional
.fromNullable(info
!= null ? info
.messageExpirationTime
: null),
1842 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1845 if (account
.getProfileKey() != null) {
1846 // Send our own profile key as well
1847 out
.write(new DeviceContact(account
.getSelfAddress(),
1848 Optional
.absent(), Optional
.absent(),
1849 Optional
.absent(), Optional
.absent(),
1850 Optional
.of(account
.getProfileKey()),
1851 false, Optional
.absent(), Optional
.absent(), false));
1855 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1856 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1857 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1858 .withStream(contactsFileStream
)
1859 .withContentType("application/octet-stream")
1860 .withLength(contactsFile
.length())
1863 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1868 Files
.delete(contactsFile
.toPath());
1869 } catch (IOException e
) {
1870 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1875 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1876 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1877 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1878 if (record.blocked
) {
1879 addresses
.add(record.getAddress());
1882 List
<byte[]> groupIds
= new ArrayList
<>();
1883 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1884 if (record.blocked
) {
1885 groupIds
.add(record.groupId
);
1888 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1891 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1892 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1893 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1896 public List
<ContactInfo
> getContacts() {
1897 return account
.getContactStore().getContacts();
1900 public ContactInfo
getContact(String number
) {
1901 return account
.getContactStore().getContact(number
);
1904 public GroupInfo
getGroup(byte[] groupId
) {
1905 return account
.getGroupStore().getGroup(groupId
);
1908 public Map
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities() {
1909 return account
.getSignalProtocolStore().getIdentities();
1912 public Pair
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities(String number
) throws InvalidNumberException
{
1913 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, username
);
1914 return new Pair
<>(canonicalizedNumber
, account
.getSignalProtocolStore().getIdentities(canonicalizedNumber
));
1918 * Trust this the identity with this fingerprint
1920 * @param name username of the identity
1921 * @param fingerprint Fingerprint
1923 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) {
1924 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1928 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1929 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1933 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1935 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1936 } catch (IOException
| UntrustedIdentityException e
) {
1937 e
.printStackTrace();
1946 * Trust this the identity with this safety number
1948 * @param name username of the identity
1949 * @param safetyNumber Safety number
1951 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) {
1952 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1956 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1957 if (!safetyNumber
.equals(computeSafetyNumber(name
, id
.getIdentityKey()))) {
1961 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1963 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1964 } catch (IOException
| UntrustedIdentityException e
) {
1965 e
.printStackTrace();
1974 * Trust all keys of this identity without verification
1976 * @param name username of the identity
1978 public boolean trustIdentityAllKeys(String name
) {
1979 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1983 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1984 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1985 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1987 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1988 } catch (IOException
| UntrustedIdentityException e
) {
1989 e
.printStackTrace();
1997 public String
computeSafetyNumber(String theirUsername
, IdentityKey theirIdentityKey
) {
1998 return Utils
.computeSafetyNumber(username
, getIdentity(), theirUsername
, theirIdentityKey
);
2001 public interface ReceiveMessageHandler
{
2003 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);