2 Copyright (C) 2015-2021 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
.manager
.groups
.GroupId
;
22 import org
.asamk
.signal
.manager
.groups
.GroupIdV1
;
23 import org
.asamk
.signal
.manager
.groups
.GroupIdV2
;
24 import org
.asamk
.signal
.manager
.groups
.GroupInviteLinkUrl
;
25 import org
.asamk
.signal
.manager
.groups
.GroupNotFoundException
;
26 import org
.asamk
.signal
.manager
.groups
.GroupUtils
;
27 import org
.asamk
.signal
.manager
.groups
.NotAGroupMemberException
;
28 import org
.asamk
.signal
.manager
.helper
.GroupHelper
;
29 import org
.asamk
.signal
.manager
.helper
.PinHelper
;
30 import org
.asamk
.signal
.manager
.helper
.ProfileHelper
;
31 import org
.asamk
.signal
.manager
.helper
.UnidentifiedAccessHelper
;
32 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
33 import org
.asamk
.signal
.manager
.storage
.contacts
.ContactInfo
;
34 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfo
;
35 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfoV1
;
36 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfoV2
;
37 import org
.asamk
.signal
.manager
.storage
.messageCache
.CachedMessage
;
38 import org
.asamk
.signal
.manager
.storage
.profiles
.SignalProfile
;
39 import org
.asamk
.signal
.manager
.storage
.profiles
.SignalProfileEntry
;
40 import org
.asamk
.signal
.manager
.storage
.protocol
.IdentityInfo
;
41 import org
.asamk
.signal
.manager
.storage
.stickers
.Sticker
;
42 import org
.asamk
.signal
.manager
.util
.AttachmentUtils
;
43 import org
.asamk
.signal
.manager
.util
.IOUtils
;
44 import org
.asamk
.signal
.manager
.util
.KeyUtils
;
45 import org
.asamk
.signal
.manager
.util
.Utils
;
46 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
47 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
48 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
49 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
50 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
51 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
52 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
53 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
54 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
55 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
56 import org
.signal
.libsignal
.metadata
.SelfSendException
;
57 import org
.signal
.libsignal
.metadata
.certificate
.CertificateValidator
;
58 import org
.signal
.storageservice
.protos
.groups
.GroupChange
;
59 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroup
;
60 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroupJoinInfo
;
61 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedMember
;
62 import org
.signal
.zkgroup
.InvalidInputException
;
63 import org
.signal
.zkgroup
.VerificationFailedException
;
64 import org
.signal
.zkgroup
.auth
.AuthCredentialResponse
;
65 import org
.signal
.zkgroup
.groups
.GroupMasterKey
;
66 import org
.signal
.zkgroup
.groups
.GroupSecretParams
;
67 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
68 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
69 import org
.signal
.zkgroup
.profiles
.ProfileKeyCredential
;
70 import org
.slf4j
.Logger
;
71 import org
.slf4j
.LoggerFactory
;
72 import org
.whispersystems
.libsignal
.IdentityKey
;
73 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
74 import org
.whispersystems
.libsignal
.InvalidKeyException
;
75 import org
.whispersystems
.libsignal
.InvalidMessageException
;
76 import org
.whispersystems
.libsignal
.InvalidVersionException
;
77 import org
.whispersystems
.libsignal
.ecc
.Curve
;
78 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
79 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
80 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
81 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
82 import org
.whispersystems
.libsignal
.util
.Medium
;
83 import org
.whispersystems
.libsignal
.util
.Pair
;
84 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
85 import org
.whispersystems
.signalservice
.api
.KeyBackupService
;
86 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
87 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
88 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
89 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
90 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
91 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
92 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
93 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
94 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
95 import org
.whispersystems
.signalservice
.api
.groupsv2
.ClientZkOperations
;
96 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
97 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Api
;
98 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2AuthorizationString
;
99 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
100 import org
.whispersystems
.signalservice
.api
.kbs
.MasterKey
;
101 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
102 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
103 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
104 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
105 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
106 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
107 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
108 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
109 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
110 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroupV2
;
111 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
112 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
113 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
114 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
115 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
116 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
117 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
118 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
119 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
120 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
121 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
122 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
123 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
124 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
125 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
126 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.StickerPackOperationMessage
;
127 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
128 import org
.whispersystems
.signalservice
.api
.profiles
.ProfileAndCredential
;
129 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
130 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
131 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
132 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
133 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
134 import org
.whispersystems
.signalservice
.api
.util
.PhoneNumberFormatter
;
135 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
136 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
137 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
138 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
139 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
140 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.Quote
;
141 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedQuoteException
;
142 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedResponseException
;
143 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
144 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
145 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
146 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
147 import org
.whispersystems
.util
.Base64
;
149 import java
.io
.Closeable
;
151 import java
.io
.FileInputStream
;
152 import java
.io
.FileNotFoundException
;
153 import java
.io
.FileOutputStream
;
154 import java
.io
.IOException
;
155 import java
.io
.InputStream
;
156 import java
.io
.OutputStream
;
158 import java
.net
.URISyntaxException
;
159 import java
.net
.URLEncoder
;
160 import java
.nio
.charset
.StandardCharsets
;
161 import java
.nio
.file
.Files
;
162 import java
.nio
.file
.Paths
;
163 import java
.nio
.file
.StandardCopyOption
;
164 import java
.security
.SignatureException
;
165 import java
.util
.ArrayList
;
166 import java
.util
.Arrays
;
167 import java
.util
.Collection
;
168 import java
.util
.Date
;
169 import java
.util
.HashMap
;
170 import java
.util
.HashSet
;
171 import java
.util
.List
;
172 import java
.util
.Map
;
173 import java
.util
.Set
;
174 import java
.util
.UUID
;
175 import java
.util
.concurrent
.ExecutorService
;
176 import java
.util
.concurrent
.TimeUnit
;
177 import java
.util
.concurrent
.TimeoutException
;
178 import java
.util
.stream
.Collectors
;
179 import java
.util
.zip
.ZipEntry
;
180 import java
.util
.zip
.ZipFile
;
182 import static org
.asamk
.signal
.manager
.ServiceConfig
.CDS_MRENCLAVE
;
183 import static org
.asamk
.signal
.manager
.ServiceConfig
.capabilities
;
184 import static org
.asamk
.signal
.manager
.ServiceConfig
.getIasKeyStore
;
186 public class Manager
implements Closeable
{
188 final static Logger logger
= LoggerFactory
.getLogger(Manager
.class);
190 private final CertificateValidator certificateValidator
= new CertificateValidator(ServiceConfig
.getUnidentifiedSenderTrustRoot());
192 private final SignalServiceConfiguration serviceConfiguration
;
193 private final String userAgent
;
195 private SignalAccount account
;
196 private final PathConfig pathConfig
;
197 private final SignalServiceAccountManager accountManager
;
198 private final GroupsV2Api groupsV2Api
;
199 private final GroupsV2Operations groupsV2Operations
;
200 private final SignalServiceMessageReceiver messageReceiver
;
201 private final ClientZkProfileOperations clientZkProfileOperations
;
203 private SignalServiceMessagePipe messagePipe
= null;
204 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
206 private final UnidentifiedAccessHelper unidentifiedAccessHelper
;
207 private final ProfileHelper profileHelper
;
208 private final GroupHelper groupHelper
;
209 private final PinHelper pinHelper
;
212 SignalAccount account
,
213 PathConfig pathConfig
,
214 SignalServiceConfiguration serviceConfiguration
,
217 this.account
= account
;
218 this.pathConfig
= pathConfig
;
219 this.serviceConfiguration
= serviceConfiguration
;
220 this.userAgent
= userAgent
;
221 this.groupsV2Operations
= capabilities
.isGv2() ?
new GroupsV2Operations(ClientZkOperations
.create(
222 serviceConfiguration
)) : null;
223 final SleepTimer timer
= new UptimeSleepTimer();
224 this.accountManager
= new SignalServiceAccountManager(serviceConfiguration
,
225 new DynamicCredentialsProvider(account
.getUuid(),
226 account
.getUsername(),
227 account
.getPassword(),
228 account
.getSignalingKey(),
229 account
.getDeviceId()),
233 this.groupsV2Api
= accountManager
.getGroupsV2Api();
234 final KeyBackupService keyBackupService
= ServiceConfig
.createKeyBackupService(accountManager
);
235 this.pinHelper
= new PinHelper(keyBackupService
);
236 this.clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(serviceConfiguration
)
237 .getProfileOperations() : null;
238 this.messageReceiver
= new SignalServiceMessageReceiver(serviceConfiguration
,
240 account
.getUsername(),
241 account
.getPassword(),
242 account
.getDeviceId(),
243 account
.getSignalingKey(),
247 clientZkProfileOperations
);
249 this.account
.setResolver(this::resolveSignalServiceAddress
);
251 this.unidentifiedAccessHelper
= new UnidentifiedAccessHelper(account
::getProfileKey
,
252 account
.getProfileStore()::getProfileKey
,
253 this::getRecipientProfile
,
254 this::getSenderCertificate
);
255 this.profileHelper
= new ProfileHelper(account
.getProfileStore()::getProfileKey
,
256 unidentifiedAccessHelper
::getAccessFor
,
257 unidentified
-> unidentified ?
getOrCreateUnidentifiedMessagePipe() : getOrCreateMessagePipe(),
258 () -> messageReceiver
);
259 this.groupHelper
= new GroupHelper(this::getRecipientProfileKeyCredential
,
260 this::getRecipientProfile
,
261 account
::getSelfAddress
,
264 this::getGroupAuthForToday
);
267 public String
getUsername() {
268 return account
.getUsername();
271 public SignalServiceAddress
getSelfAddress() {
272 return account
.getSelfAddress();
275 private IdentityKeyPair
getIdentityKeyPair() {
276 return account
.getSignalProtocolStore().getIdentityKeyPair();
279 public int getDeviceId() {
280 return account
.getDeviceId();
283 public static Manager
init(
284 String username
, File settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
285 ) throws IOException
, NotRegisteredException
{
286 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
288 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
289 throw new NotRegisteredException();
292 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
294 if (!account
.isRegistered()) {
295 throw new NotRegisteredException();
298 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
301 public void checkAccountState() throws IOException
{
302 if (account
.isRegistered()) {
303 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
307 if (account
.getUuid() == null) {
308 account
.setUuid(accountManager
.getOwnUuid());
311 updateAccountAttributes();
315 public boolean isRegistered() {
316 return account
.isRegistered();
320 * This is used for checking a set of phone numbers for registration on Signal
322 * @param numbers The set of phone number in question
323 * @return A map of numbers to booleans. True if registered, false otherwise. Should never be null
324 * @throws IOException if its unable to check if the users are registered
326 public Map
<String
, Boolean
> areUsersRegistered(Set
<String
> numbers
) throws IOException
{
327 // Note "contactDetails" has no optionals. It only gives us info on users who are registered
328 List
<ContactTokenDetails
> contactDetails
= this.accountManager
.getContacts(numbers
);
330 Set
<String
> registeredUsers
= contactDetails
.stream()
331 .map(ContactTokenDetails
::getNumber
)
332 .collect(Collectors
.toSet());
334 return numbers
.stream().collect(Collectors
.toMap(x
-> x
, registeredUsers
::contains
));
337 public void updateAccountAttributes() throws IOException
{
338 accountManager
.setAccountAttributes(account
.getSignalingKey(),
339 account
.getSignalProtocolStore().getLocalRegistrationId(),
341 // set legacy pin only if no KBS master key is set
342 account
.getPinMasterKey() == null ? account
.getRegistrationLockPin() : null,
343 account
.getPinMasterKey() == null ?
null : account
.getPinMasterKey().deriveRegistrationLock(),
344 account
.getSelfUnidentifiedAccessKey(),
345 account
.isUnrestrictedUnidentifiedAccess(),
347 account
.isDiscoverableByPhoneNumber());
350 public void setProfile(String name
, File avatar
) throws IOException
{
351 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
352 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
356 public void unregister() throws IOException
{
357 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
358 // If this is the master device, other users can't send messages to this number anymore.
359 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
360 accountManager
.setGcmId(Optional
.absent());
362 account
.setRegistered(false);
366 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
367 List
<DeviceInfo
> devices
= accountManager
.getDevices();
368 account
.setMultiDevice(devices
.size() > 1);
373 public void removeLinkedDevices(int deviceId
) throws IOException
{
374 accountManager
.removeDevice(deviceId
);
375 List
<DeviceInfo
> devices
= accountManager
.getDevices();
376 account
.setMultiDevice(devices
.size() > 1);
380 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
381 DeviceLinkInfo info
= DeviceLinkInfo
.parseDeviceLinkUri(linkUri
);
383 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
386 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
387 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
388 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
390 accountManager
.addDevice(deviceIdentifier
,
393 Optional
.of(account
.getProfileKey().serialize()),
395 account
.setMultiDevice(true);
399 private List
<PreKeyRecord
> generatePreKeys() {
400 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
402 final int offset
= account
.getPreKeyIdOffset();
403 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
404 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
405 ECKeyPair keyPair
= Curve
.generateKeyPair();
406 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
411 account
.addPreKeys(records
);
417 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
419 ECKeyPair keyPair
= Curve
.generateKeyPair();
420 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(),
421 keyPair
.getPublicKey().serialize());
422 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(),
423 System
.currentTimeMillis(),
427 account
.addSignedPreKey(record);
431 } catch (InvalidKeyException e
) {
432 throw new AssertionError(e
);
436 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
, UnauthenticatedResponseException
{
437 if (pin
.isPresent()) {
438 final MasterKey masterKey
= account
.getPinMasterKey() != null
439 ? account
.getPinMasterKey()
440 : KeyUtils
.createMasterKey();
442 pinHelper
.setRegistrationLockPin(pin
.get(), masterKey
);
444 account
.setRegistrationLockPin(pin
.get());
445 account
.setPinMasterKey(masterKey
);
447 // Remove legacy registration lock
448 accountManager
.removeRegistrationLockV1();
451 pinHelper
.removeRegistrationLockPin();
453 account
.setRegistrationLockPin(null);
454 account
.setPinMasterKey(null);
459 void refreshPreKeys() throws IOException
{
460 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
461 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
462 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
464 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
467 private SignalServiceMessagePipe
getOrCreateMessagePipe() {
468 if (messagePipe
== null) {
469 messagePipe
= messageReceiver
.createMessagePipe();
474 private SignalServiceMessagePipe
getOrCreateUnidentifiedMessagePipe() {
475 if (unidentifiedMessagePipe
== null) {
476 unidentifiedMessagePipe
= messageReceiver
.createUnidentifiedMessagePipe();
478 return unidentifiedMessagePipe
;
481 private SignalServiceMessageSender
createMessageSender() {
482 final ExecutorService executor
= null;
483 return new SignalServiceMessageSender(serviceConfiguration
,
485 account
.getUsername(),
486 account
.getPassword(),
487 account
.getDeviceId(),
488 account
.getSignalProtocolStore(),
490 account
.isMultiDevice(),
491 Optional
.fromNullable(messagePipe
),
492 Optional
.fromNullable(unidentifiedMessagePipe
),
494 clientZkProfileOperations
,
496 ServiceConfig
.MAX_ENVELOPE_SIZE
);
499 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
) throws IOException
{
500 return profileHelper
.retrieveProfileSync(address
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
503 private SignalProfile
getRecipientProfile(
504 SignalServiceAddress address
506 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
507 if (profileEntry
== null) {
510 long now
= new Date().getTime();
511 // Profiles are cache for 24h before retrieving them again
512 if (!profileEntry
.isRequestPending() && (
513 profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000
515 ProfileKey profileKey
= profileEntry
.getProfileKey();
516 profileEntry
.setRequestPending(true);
517 SignalProfile profile
;
519 profile
= retrieveRecipientProfile(address
, profileKey
);
520 } catch (IOException e
) {
521 logger
.warn("Failed to retrieve profile, ignoring: {}", e
.getMessage());
522 profileEntry
.setRequestPending(false);
525 profileEntry
.setRequestPending(false);
526 account
.getProfileStore()
527 .updateProfile(address
, profileKey
, now
, profile
, profileEntry
.getProfileKeyCredential());
530 return profileEntry
.getProfile();
533 private ProfileKeyCredential
getRecipientProfileKeyCredential(SignalServiceAddress address
) {
534 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
535 if (profileEntry
== null) {
538 if (profileEntry
.getProfileKeyCredential() == null) {
539 ProfileAndCredential profileAndCredential
;
541 profileAndCredential
= profileHelper
.retrieveProfileSync(address
,
542 SignalServiceProfile
.RequestType
.PROFILE_AND_CREDENTIAL
);
543 } catch (IOException e
) {
544 logger
.warn("Failed to retrieve profile key credential, ignoring: {}", e
.getMessage());
548 long now
= new Date().getTime();
549 final ProfileKeyCredential profileKeyCredential
= profileAndCredential
.getProfileKeyCredential().orNull();
550 final SignalProfile profile
= decryptProfile(address
,
551 profileEntry
.getProfileKey(),
552 profileAndCredential
.getProfile());
553 account
.getProfileStore()
554 .updateProfile(address
, profileEntry
.getProfileKey(), now
, profile
, profileKeyCredential
);
555 return profileKeyCredential
;
557 return profileEntry
.getProfileKeyCredential();
560 private SignalProfile
retrieveRecipientProfile(
561 SignalServiceAddress address
, ProfileKey profileKey
562 ) throws IOException
{
563 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
);
565 return decryptProfile(address
, profileKey
, encryptedProfile
);
568 private SignalProfile
decryptProfile(
569 final SignalServiceAddress address
, final ProfileKey profileKey
, final SignalServiceProfile encryptedProfile
571 File avatarFile
= null;
573 avatarFile
= encryptedProfile
.getAvatar() == null
575 : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
576 } catch (Throwable e
) {
577 logger
.warn("Failed to retrieve profile avatar, ignoring: {}", e
.getMessage());
580 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
584 name
= encryptedProfile
.getName() == null
586 : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName())));
587 } catch (IOException e
) {
590 String unidentifiedAccess
;
592 unidentifiedAccess
= encryptedProfile
.getUnidentifiedAccess() == null
593 || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess()))
595 : encryptedProfile
.getUnidentifiedAccess();
596 } catch (IOException e
) {
597 unidentifiedAccess
= null;
599 return new SignalProfile(encryptedProfile
.getIdentityKey(),
603 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
604 encryptedProfile
.getCapabilities());
605 } catch (InvalidCiphertextException e
) {
610 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(GroupId groupId
) throws IOException
{
611 File file
= getGroupAvatarFile(groupId
);
612 if (!file
.exists()) {
613 return Optional
.absent();
616 return Optional
.of(AttachmentUtils
.createAttachment(file
));
619 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
620 File file
= getContactAvatarFile(number
);
621 if (!file
.exists()) {
622 return Optional
.absent();
625 return Optional
.of(AttachmentUtils
.createAttachment(file
));
628 private GroupInfo
getGroupForSending(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
629 GroupInfo g
= getGroup(groupId
);
631 throw new GroupNotFoundException(groupId
);
633 if (!g
.isMember(account
.getSelfAddress())) {
634 throw new NotAGroupMemberException(groupId
, g
.getTitle());
639 private GroupInfo
getGroupForUpdating(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
640 GroupInfo g
= getGroup(groupId
);
642 throw new GroupNotFoundException(groupId
);
644 if (!g
.isMember(account
.getSelfAddress()) && !g
.isPendingMember(account
.getSelfAddress())) {
645 throw new NotAGroupMemberException(groupId
, g
.getTitle());
650 public List
<GroupInfo
> getGroups() {
651 return account
.getGroupStore().getGroups();
654 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
655 SignalServiceDataMessage
.Builder messageBuilder
, GroupId groupId
656 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
{
657 final GroupInfo g
= getGroupForSending(groupId
);
659 GroupUtils
.setGroupContext(messageBuilder
, g
);
660 messageBuilder
.withExpiration(g
.getMessageExpirationTime());
662 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
665 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
666 String messageText
, List
<String
> attachments
, GroupId groupId
667 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
668 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
669 .withBody(messageText
);
670 if (attachments
!= null) {
671 messageBuilder
.withAttachments(AttachmentUtils
.getSignalServiceAttachments(attachments
));
674 return sendGroupMessage(messageBuilder
, groupId
);
677 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessageReaction(
678 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, GroupId groupId
679 ) throws IOException
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
680 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
682 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
683 targetSentTimestamp
);
684 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
685 .withReaction(reaction
);
687 return sendGroupMessage(messageBuilder
, groupId
);
690 public Pair
<Long
, List
<SendMessageResult
>> sendQuitGroupMessage(GroupId groupId
) throws GroupNotFoundException
, IOException
, NotAGroupMemberException
{
692 SignalServiceDataMessage
.Builder messageBuilder
;
694 final GroupInfo g
= getGroupForUpdating(groupId
);
695 if (g
instanceof GroupInfoV1
) {
696 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
697 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
698 .withId(groupId
.serialize())
700 messageBuilder
= SignalServiceDataMessage
.newBuilder().asGroupMessage(group
);
701 groupInfoV1
.removeMember(account
.getSelfAddress());
702 account
.getGroupStore().updateGroup(groupInfoV1
);
704 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) g
;
705 final Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.leaveGroup(groupInfoV2
);
706 groupInfoV2
.setGroup(groupGroupChangePair
.first());
707 messageBuilder
= getGroupUpdateMessageBuilder(groupInfoV2
, groupGroupChangePair
.second().toByteArray());
708 account
.getGroupStore().updateGroup(groupInfoV2
);
711 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
714 private Pair
<GroupId
, List
<SendMessageResult
>> sendUpdateGroupMessage(
715 GroupId groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
716 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
718 SignalServiceDataMessage
.Builder messageBuilder
;
719 if (groupId
== null) {
721 GroupInfoV2 gv2
= groupHelper
.createGroupV2(name
, members
, avatarFile
);
723 GroupInfoV1 gv1
= new GroupInfoV1(GroupIdV1
.createRandom());
724 gv1
.addMembers(List
.of(account
.getSelfAddress()));
725 updateGroupV1(gv1
, name
, members
, avatarFile
);
726 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
729 messageBuilder
= getGroupUpdateMessageBuilder(gv2
, null);
733 GroupInfo group
= getGroupForUpdating(groupId
);
734 if (group
instanceof GroupInfoV2
) {
735 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) group
;
737 Pair
<Long
, List
<SendMessageResult
>> result
= null;
738 if (groupInfoV2
.isPendingMember(getSelfAddress())) {
739 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.acceptInvite(groupInfoV2
);
740 result
= sendUpdateGroupMessage(groupInfoV2
,
741 groupGroupChangePair
.first(),
742 groupGroupChangePair
.second());
745 if (members
!= null) {
746 final Set
<SignalServiceAddress
> newMembers
= new HashSet
<>(members
);
747 newMembers
.removeAll(group
.getMembers()
749 .map(this::resolveSignalServiceAddress
)
750 .collect(Collectors
.toSet()));
751 if (newMembers
.size() > 0) {
752 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
754 result
= sendUpdateGroupMessage(groupInfoV2
,
755 groupGroupChangePair
.first(),
756 groupGroupChangePair
.second());
759 if (result
== null || name
!= null || avatarFile
!= null) {
760 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
763 result
= sendUpdateGroupMessage(groupInfoV2
,
764 groupGroupChangePair
.first(),
765 groupGroupChangePair
.second());
768 return new Pair
<>(group
.getGroupId(), result
.second());
770 GroupInfoV1 gv1
= (GroupInfoV1
) group
;
771 updateGroupV1(gv1
, name
, members
, avatarFile
);
772 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
777 account
.getGroupStore().updateGroup(g
);
779 final Pair
<Long
, List
<SendMessageResult
>> result
= sendMessage(messageBuilder
,
780 g
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
781 return new Pair
<>(g
.getGroupId(), result
.second());
784 public Pair
<GroupId
, List
<SendMessageResult
>> joinGroup(
785 GroupInviteLinkUrl inviteLinkUrl
786 ) throws IOException
, GroupLinkNotActiveException
{
787 return sendJoinGroupMessage(inviteLinkUrl
);
790 private Pair
<GroupId
, List
<SendMessageResult
>> sendJoinGroupMessage(
791 GroupInviteLinkUrl inviteLinkUrl
792 ) throws IOException
, GroupLinkNotActiveException
{
793 final DecryptedGroupJoinInfo groupJoinInfo
= groupHelper
.getDecryptedGroupJoinInfo(inviteLinkUrl
.getGroupMasterKey(),
794 inviteLinkUrl
.getPassword());
795 final GroupChange groupChange
= groupHelper
.joinGroup(inviteLinkUrl
.getGroupMasterKey(),
796 inviteLinkUrl
.getPassword(),
798 final GroupInfoV2 group
= getOrMigrateGroup(inviteLinkUrl
.getGroupMasterKey(),
799 groupJoinInfo
.getRevision() + 1,
800 groupChange
.toByteArray());
802 if (group
.getGroup() == null) {
803 // Only requested member, can't send update to group members
804 return new Pair
<>(group
.getGroupId(), List
.of());
807 final Pair
<Long
, List
<SendMessageResult
>> result
= sendUpdateGroupMessage(group
, group
.getGroup(), groupChange
);
809 return new Pair
<>(group
.getGroupId(), result
.second());
812 private Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
813 GroupInfoV2 group
, DecryptedGroup newDecryptedGroup
, GroupChange groupChange
814 ) throws IOException
{
815 group
.setGroup(newDecryptedGroup
);
816 final SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(group
,
817 groupChange
.toByteArray());
818 account
.getGroupStore().updateGroup(group
);
819 return sendMessage(messageBuilder
, group
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
822 private void updateGroupV1(
825 final Collection
<SignalServiceAddress
> members
,
826 final String avatarFile
827 ) throws IOException
{
832 if (members
!= null) {
833 final Set
<String
> newE164Members
= new HashSet
<>();
834 for (SignalServiceAddress member
: members
) {
835 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
838 newE164Members
.add(member
.getNumber().get());
841 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
842 if (contacts
.size() != newE164Members
.size()) {
843 // Some of the new members are not registered on Signal
844 for (ContactTokenDetails contact
: contacts
) {
845 newE164Members
.remove(contact
.getNumber());
847 throw new IOException("Failed to add members "
848 + String
.join(", ", newE164Members
)
849 + " to group: Not registered on Signal");
852 g
.addMembers(members
);
855 if (avatarFile
!= null) {
856 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
857 File aFile
= getGroupAvatarFile(g
.getGroupId());
858 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
862 Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
863 GroupIdV1 groupId
, SignalServiceAddress recipient
864 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
866 GroupInfo group
= getGroupForSending(groupId
);
867 if (!(group
instanceof GroupInfoV1
)) {
868 throw new RuntimeException("Received an invalid group request for a v2 group!");
870 g
= (GroupInfoV1
) group
;
872 if (!g
.isMember(recipient
)) {
873 throw new NotAGroupMemberException(groupId
, g
.name
);
876 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
878 // Send group message only to the recipient who requested it
879 return sendMessage(messageBuilder
, List
.of(recipient
));
882 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV1 g
) throws AttachmentInvalidException
{
883 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
884 .withId(g
.getGroupId().serialize())
886 .withMembers(new ArrayList
<>(g
.getMembers()));
888 File aFile
= getGroupAvatarFile(g
.getGroupId());
889 if (aFile
.exists()) {
891 group
.withAvatar(AttachmentUtils
.createAttachment(aFile
));
892 } catch (IOException e
) {
893 throw new AttachmentInvalidException(aFile
.toString(), e
);
897 return SignalServiceDataMessage
.newBuilder()
898 .asGroupMessage(group
.build())
899 .withExpiration(g
.getMessageExpirationTime());
902 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV2 g
, byte[] signedGroupChange
) {
903 SignalServiceGroupV2
.Builder group
= SignalServiceGroupV2
.newBuilder(g
.getMasterKey())
904 .withRevision(g
.getGroup().getRevision())
905 .withSignedGroupChange(signedGroupChange
);
906 return SignalServiceDataMessage
.newBuilder()
907 .asGroupMessage(group
.build())
908 .withExpiration(g
.getMessageExpirationTime());
911 Pair
<Long
, List
<SendMessageResult
>> sendGroupInfoRequest(
912 GroupIdV1 groupId
, SignalServiceAddress recipient
913 ) throws IOException
{
914 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
915 .withId(groupId
.serialize());
917 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
918 .asGroupMessage(group
.build());
920 // Send group info request message to the recipient who sent us a message with this groupId
921 return sendMessage(messageBuilder
, List
.of(recipient
));
925 SignalServiceAddress remoteAddress
, long messageId
926 ) throws IOException
, UntrustedIdentityException
{
927 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
929 System
.currentTimeMillis());
931 createMessageSender().sendReceipt(remoteAddress
,
932 unidentifiedAccessHelper
.getAccessFor(remoteAddress
),
936 public Pair
<Long
, List
<SendMessageResult
>> sendMessage(
937 String messageText
, List
<String
> attachments
, List
<String
> recipients
938 ) throws IOException
, AttachmentInvalidException
, InvalidNumberException
{
939 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
940 .withBody(messageText
);
941 if (attachments
!= null) {
942 List
<SignalServiceAttachment
> attachmentStreams
= AttachmentUtils
.getSignalServiceAttachments(attachments
);
944 // Upload attachments here, so we only upload once even for multiple recipients
945 SignalServiceMessageSender messageSender
= createMessageSender();
946 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
947 for (SignalServiceAttachment attachment
: attachmentStreams
) {
948 if (attachment
.isStream()) {
949 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
950 } else if (attachment
.isPointer()) {
951 attachmentPointers
.add(attachment
.asPointer());
955 messageBuilder
.withAttachments(attachmentPointers
);
957 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
960 public Pair
<Long
, List
<SendMessageResult
>> sendMessageReaction(
961 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, List
<String
> recipients
962 ) throws IOException
, InvalidNumberException
{
963 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
965 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
966 targetSentTimestamp
);
967 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
968 .withReaction(reaction
);
969 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
972 public Pair
<Long
, List
<SendMessageResult
>> sendEndSessionMessage(List
<String
> recipients
) throws IOException
, InvalidNumberException
{
973 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().asEndSessionMessage();
975 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
977 return sendMessage(messageBuilder
, signalServiceAddresses
);
978 } catch (Exception e
) {
979 for (SignalServiceAddress address
: signalServiceAddresses
) {
980 handleEndSession(address
);
987 public String
getContactName(String number
) throws InvalidNumberException
{
988 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
989 if (contact
== null) {
996 public void setContactName(String number
, String name
) throws InvalidNumberException
{
997 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
998 ContactInfo contact
= account
.getContactStore().getContact(address
);
999 if (contact
== null) {
1000 contact
= new ContactInfo(address
);
1002 contact
.name
= name
;
1003 account
.getContactStore().updateContact(contact
);
1007 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
1008 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
1011 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
1012 ContactInfo contact
= account
.getContactStore().getContact(address
);
1013 if (contact
== null) {
1014 contact
= new ContactInfo(address
);
1016 contact
.blocked
= blocked
;
1017 account
.getContactStore().updateContact(contact
);
1021 public void setGroupBlocked(final GroupId groupId
, final boolean blocked
) throws GroupNotFoundException
{
1022 GroupInfo group
= getGroup(groupId
);
1023 if (group
== null) {
1024 throw new GroupNotFoundException(groupId
);
1027 group
.setBlocked(blocked
);
1028 account
.getGroupStore().updateGroup(group
);
1032 public Pair
<GroupId
, List
<SendMessageResult
>> updateGroup(
1033 GroupId groupId
, String name
, List
<String
> members
, String avatar
1034 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
1035 return sendUpdateGroupMessage(groupId
,
1037 members
== null ?
null : getSignalServiceAddresses(members
),
1042 * Change the expiration timer for a contact
1044 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
1045 ContactInfo contact
= account
.getContactStore().getContact(address
);
1046 contact
.messageExpirationTime
= messageExpirationTimer
;
1047 account
.getContactStore().updateContact(contact
);
1048 sendExpirationTimerUpdate(address
);
1052 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
1053 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1054 .asExpirationUpdate();
1055 sendMessage(messageBuilder
, List
.of(address
));
1059 * Change the expiration timer for a contact
1061 public void setExpirationTimer(
1062 String number
, int messageExpirationTimer
1063 ) throws IOException
, InvalidNumberException
{
1064 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1065 setExpirationTimer(address
, messageExpirationTimer
);
1069 * Change the expiration timer for a group
1071 public void setExpirationTimer(GroupId groupId
, int messageExpirationTimer
) {
1072 GroupInfo g
= getGroup(groupId
);
1073 if (g
instanceof GroupInfoV1
) {
1074 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
1075 groupInfoV1
.messageExpirationTime
= messageExpirationTimer
;
1076 account
.getGroupStore().updateGroup(groupInfoV1
);
1078 throw new RuntimeException("TODO Not implemented!");
1083 * Upload the sticker pack from path.
1085 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
1086 * @return if successful, returns the URL to install the sticker pack in the signal app
1088 public String
uploadStickerPack(File path
) throws IOException
, StickerPackInvalidException
{
1089 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
1091 SignalServiceMessageSender messageSender
= createMessageSender();
1093 byte[] packKey
= KeyUtils
.createStickerUploadKey();
1094 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
1096 Sticker sticker
= new Sticker(Hex
.fromStringCondensed(packId
), packKey
);
1097 account
.getStickerStore().updateSticker(sticker
);
1101 return new URI("https",
1104 "pack_id=" + URLEncoder
.encode(packId
, StandardCharsets
.UTF_8
) + "&pack_key=" + URLEncoder
.encode(
1105 Hex
.toStringCondensed(packKey
),
1106 StandardCharsets
.UTF_8
)).toString();
1107 } catch (URISyntaxException e
) {
1108 throw new AssertionError(e
);
1112 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(
1114 ) throws IOException
, StickerPackInvalidException
{
1116 String rootPath
= null;
1118 if (file
.getName().endsWith(".zip")) {
1119 zip
= new ZipFile(file
);
1120 } else if (file
.getName().equals("manifest.json")) {
1121 rootPath
= file
.getParent();
1123 throw new StickerPackInvalidException("Could not find manifest.json");
1126 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
1128 if (pack
.stickers
== null) {
1129 throw new StickerPackInvalidException("Must set a 'stickers' field.");
1132 if (pack
.stickers
.isEmpty()) {
1133 throw new StickerPackInvalidException("Must include stickers.");
1136 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
1137 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
1138 if (sticker
.file
== null) {
1139 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
1142 Pair
<InputStream
, Long
> data
;
1144 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
1145 } catch (IOException ignored
) {
1146 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
1149 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
1150 StickerInfo stickerInfo
= new StickerInfo(data
.first(),
1152 Optional
.fromNullable(sticker
.emoji
).or(""),
1154 stickers
.add(stickerInfo
);
1157 StickerInfo cover
= null;
1158 if (pack
.cover
!= null) {
1159 if (pack
.cover
.file
== null) {
1160 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
1163 Pair
<InputStream
, Long
> data
;
1165 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
1166 } catch (IOException ignored
) {
1167 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
1170 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
1171 cover
= new StickerInfo(data
.first(),
1173 Optional
.fromNullable(pack
.cover
.emoji
).or(""),
1177 return new SignalServiceStickerManifestUpload(pack
.title
, pack
.author
, cover
, stickers
);
1180 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
1181 InputStream inputStream
;
1183 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
1185 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
1187 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
1190 private static Pair
<InputStream
, Long
> getInputStreamAndLength(
1191 final String rootPath
, final ZipFile zip
, final String subfile
1192 ) throws IOException
{
1194 final ZipEntry entry
= zip
.getEntry(subfile
);
1195 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
1197 final File file
= new File(rootPath
, subfile
);
1198 return new Pair
<>(new FileInputStream(file
), file
.length());
1202 void requestSyncGroups() throws IOException
{
1203 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1204 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
)
1206 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1208 sendSyncMessage(message
);
1209 } catch (UntrustedIdentityException e
) {
1210 e
.printStackTrace();
1214 void requestSyncContacts() throws IOException
{
1215 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1216 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
)
1218 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1220 sendSyncMessage(message
);
1221 } catch (UntrustedIdentityException e
) {
1222 e
.printStackTrace();
1226 void requestSyncBlocked() throws IOException
{
1227 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1228 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
)
1230 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1232 sendSyncMessage(message
);
1233 } catch (UntrustedIdentityException e
) {
1234 e
.printStackTrace();
1238 void requestSyncConfiguration() throws IOException
{
1239 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1240 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
)
1242 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1244 sendSyncMessage(message
);
1245 } catch (UntrustedIdentityException e
) {
1246 e
.printStackTrace();
1250 private byte[] getSenderCertificate() {
1251 // TODO support UUID capable sender certificates
1252 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
1255 certificate
= accountManager
.getSenderCertificate();
1256 } catch (IOException e
) {
1257 logger
.warn("Failed to get sender certificate, ignoring: {}", e
.getMessage());
1260 // TODO cache for a day
1264 private void sendSyncMessage(SignalServiceSyncMessage message
) throws IOException
, UntrustedIdentityException
{
1265 SignalServiceMessageSender messageSender
= createMessageSender();
1267 messageSender
.sendMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
1268 } catch (UntrustedIdentityException e
) {
1269 account
.getSignalProtocolStore()
1270 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1272 TrustLevel
.UNTRUSTED
);
1277 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1278 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1279 final Set
<SignalServiceAddress
> missingUuids
= new HashSet
<>();
1281 for (String number
: numbers
) {
1282 final SignalServiceAddress resolvedAddress
= canonicalizeAndResolveSignalServiceAddress(number
);
1283 if (resolvedAddress
.getUuid().isPresent()) {
1284 signalServiceAddresses
.add(resolvedAddress
);
1286 missingUuids
.add(resolvedAddress
);
1290 Map
<String
, UUID
> registeredUsers
;
1292 registeredUsers
= accountManager
.getRegisteredUsers(getIasKeyStore(),
1293 missingUuids
.stream().map(a
-> a
.getNumber().get()).collect(Collectors
.toSet()),
1295 } catch (IOException
| Quote
.InvalidQuoteFormatException
| UnauthenticatedQuoteException
| SignatureException
| UnauthenticatedResponseException e
) {
1296 logger
.warn("Failed to resolve uuids from server, ignoring: {}", e
.getMessage());
1297 registeredUsers
= new HashMap
<>();
1300 for (SignalServiceAddress address
: missingUuids
) {
1301 final String number
= address
.getNumber().get();
1302 if (registeredUsers
.containsKey(number
)) {
1303 final SignalServiceAddress newAddress
= resolveSignalServiceAddress(new SignalServiceAddress(
1304 registeredUsers
.get(number
),
1306 signalServiceAddresses
.add(newAddress
);
1308 signalServiceAddresses
.add(address
);
1312 return signalServiceAddresses
;
1315 private Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1316 SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
1317 ) throws IOException
{
1318 recipients
= recipients
.stream().map(this::resolveSignalServiceAddress
).collect(Collectors
.toSet());
1319 final long timestamp
= System
.currentTimeMillis();
1320 messageBuilder
.withTimestamp(timestamp
);
1321 getOrCreateMessagePipe();
1322 getOrCreateUnidentifiedMessagePipe();
1323 SignalServiceDataMessage message
= null;
1325 message
= messageBuilder
.build();
1326 if (message
.getGroupContext().isPresent()) {
1328 SignalServiceMessageSender messageSender
= createMessageSender();
1329 final boolean isRecipientUpdate
= false;
1330 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
),
1331 unidentifiedAccessHelper
.getAccessFor(recipients
),
1334 for (SendMessageResult r
: result
) {
1335 if (r
.getIdentityFailure() != null) {
1336 account
.getSignalProtocolStore()
1337 .saveIdentity(r
.getAddress(),
1338 r
.getIdentityFailure().getIdentityKey(),
1339 TrustLevel
.UNTRUSTED
);
1342 return new Pair
<>(timestamp
, result
);
1343 } catch (UntrustedIdentityException e
) {
1344 account
.getSignalProtocolStore()
1345 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1347 TrustLevel
.UNTRUSTED
);
1348 return new Pair
<>(timestamp
, List
.of());
1351 // Send to all individually, so sync messages are sent correctly
1352 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1353 for (SignalServiceAddress address
: recipients
) {
1354 ContactInfo contact
= account
.getContactStore().getContact(address
);
1355 if (contact
!= null) {
1356 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1357 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1359 messageBuilder
.withExpiration(0);
1360 messageBuilder
.withProfileKey(null);
1362 message
= messageBuilder
.build();
1363 if (address
.matches(account
.getSelfAddress())) {
1364 results
.add(sendSelfMessage(message
));
1366 results
.add(sendMessage(address
, message
));
1369 return new Pair
<>(timestamp
, results
);
1372 if (message
!= null && message
.isEndSession()) {
1373 for (SignalServiceAddress recipient
: recipients
) {
1374 handleEndSession(recipient
);
1381 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1382 SignalServiceMessageSender messageSender
= createMessageSender();
1384 SignalServiceAddress recipient
= account
.getSelfAddress();
1386 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= unidentifiedAccessHelper
.getAccessFor(recipient
);
1387 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1388 message
.getTimestamp(),
1390 message
.getExpiresInSeconds(),
1391 Map
.of(recipient
, unidentifiedAccess
.isPresent()),
1393 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1396 long startTime
= System
.currentTimeMillis();
1397 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1398 return SendMessageResult
.success(recipient
,
1399 unidentifiedAccess
.isPresent(),
1401 System
.currentTimeMillis() - startTime
);
1402 } catch (UntrustedIdentityException e
) {
1403 account
.getSignalProtocolStore()
1404 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1406 TrustLevel
.UNTRUSTED
);
1407 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1411 private SendMessageResult
sendMessage(
1412 SignalServiceAddress address
, SignalServiceDataMessage message
1413 ) throws IOException
{
1414 SignalServiceMessageSender messageSender
= createMessageSender();
1417 return messageSender
.sendMessage(address
, unidentifiedAccessHelper
.getAccessFor(address
), message
);
1418 } catch (UntrustedIdentityException e
) {
1419 account
.getSignalProtocolStore()
1420 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1422 TrustLevel
.UNTRUSTED
);
1423 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1427 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1428 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(),
1429 account
.getSignalProtocolStore(),
1430 certificateValidator
);
1432 return cipher
.decrypt(envelope
);
1433 } catch (ProtocolUntrustedIdentityException e
) {
1434 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1435 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
1437 account
.getSignalProtocolStore()
1438 .saveIdentity(resolveSignalServiceAddress(identityException
.getName()),
1439 identityException
.getUntrustedIdentity(),
1440 TrustLevel
.UNTRUSTED
);
1441 throw identityException
;
1443 throw new AssertionError(e
);
1447 private void handleEndSession(SignalServiceAddress source
) {
1448 account
.getSignalProtocolStore().deleteAllSessions(source
);
1451 private static int currentTimeDays() {
1452 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
1455 private GroupsV2AuthorizationString
getGroupAuthForToday(
1456 final GroupSecretParams groupSecretParams
1457 ) throws IOException
{
1458 final int today
= currentTimeDays();
1459 // Returns credentials for the next 7 days
1460 final HashMap
<Integer
, AuthCredentialResponse
> credentials
= groupsV2Api
.getCredentials(today
);
1461 // TODO cache credentials until they expire
1462 AuthCredentialResponse authCredentialResponse
= credentials
.get(today
);
1464 return groupsV2Api
.getGroupsV2AuthorizationString(account
.getUuid(),
1467 authCredentialResponse
);
1468 } catch (VerificationFailedException e
) {
1469 throw new IOException(e
);
1473 private List
<HandleAction
> handleSignalServiceDataMessage(
1474 SignalServiceDataMessage message
,
1476 SignalServiceAddress source
,
1477 SignalServiceAddress destination
,
1478 boolean ignoreAttachments
1480 List
<HandleAction
> actions
= new ArrayList
<>();
1481 if (message
.getGroupContext().isPresent()) {
1482 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1483 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1484 GroupIdV1 groupId
= GroupId
.v1(groupInfo
.getGroupId());
1485 GroupInfo group
= getGroup(groupId
);
1486 if (group
== null || group
instanceof GroupInfoV1
) {
1487 GroupInfoV1 groupV1
= (GroupInfoV1
) group
;
1488 switch (groupInfo
.getType()) {
1490 if (groupV1
== null) {
1491 groupV1
= new GroupInfoV1(groupId
);
1494 if (groupInfo
.getAvatar().isPresent()) {
1495 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1496 if (avatar
.isPointer()) {
1498 retrieveGroupAvatarAttachment(avatar
.asPointer(), groupV1
.getGroupId());
1499 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1500 logger
.warn("Failed to retrieve avatar for group {}, ignoring: {}",
1507 if (groupInfo
.getName().isPresent()) {
1508 groupV1
.name
= groupInfo
.getName().get();
1511 if (groupInfo
.getMembers().isPresent()) {
1512 groupV1
.addMembers(groupInfo
.getMembers()
1515 .map(this::resolveSignalServiceAddress
)
1516 .collect(Collectors
.toSet()));
1519 account
.getGroupStore().updateGroup(groupV1
);
1523 if (groupV1
== null && !isSync
) {
1524 actions
.add(new SendGroupInfoRequestAction(source
, groupId
));
1528 if (groupV1
!= null) {
1529 groupV1
.removeMember(source
);
1530 account
.getGroupStore().updateGroup(groupV1
);
1535 if (groupV1
!= null && !isSync
) {
1536 actions
.add(new SendGroupUpdateAction(source
, groupV1
.getGroupId()));
1541 // Received a group v1 message for a v2 group
1544 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1545 final SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1546 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1548 getOrMigrateGroup(groupMasterKey
,
1549 groupContext
.getRevision(),
1550 groupContext
.hasSignedGroupChange() ? groupContext
.getSignedGroupChange() : null);
1554 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1555 if (conversationPartnerAddress
!= null && message
.isEndSession()) {
1556 handleEndSession(conversationPartnerAddress
);
1558 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1559 if (message
.getGroupContext().isPresent()) {
1560 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1561 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1562 GroupInfoV1 group
= account
.getGroupStore().getOrCreateGroupV1(GroupId
.v1(groupInfo
.getGroupId()));
1563 if (group
!= null) {
1564 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1565 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1566 account
.getGroupStore().updateGroup(group
);
1569 } else if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1570 // disappearing message timer already stored in the DecryptedGroup
1572 } else if (conversationPartnerAddress
!= null) {
1573 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1574 if (contact
== null) {
1575 contact
= new ContactInfo(conversationPartnerAddress
);
1577 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1578 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1579 account
.getContactStore().updateContact(contact
);
1583 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1584 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1585 if (attachment
.isPointer()) {
1587 retrieveAttachment(attachment
.asPointer());
1588 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1589 logger
.warn("Failed to retrieve attachment ({}), ignoring: {}",
1590 attachment
.asPointer().getRemoteId(),
1596 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1597 final ProfileKey profileKey
;
1599 profileKey
= new ProfileKey(message
.getProfileKey().get());
1600 } catch (InvalidInputException e
) {
1601 throw new AssertionError(e
);
1603 if (source
.matches(account
.getSelfAddress())) {
1604 this.account
.setProfileKey(profileKey
);
1606 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1608 if (message
.getPreviews().isPresent()) {
1609 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1610 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1611 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1612 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1614 retrieveAttachment(attachment
);
1615 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1616 logger
.warn("Failed to retrieve preview image ({}), ignoring: {}",
1617 attachment
.getRemoteId(),
1623 if (message
.getQuote().isPresent()) {
1624 final SignalServiceDataMessage
.Quote quote
= message
.getQuote().get();
1626 for (SignalServiceDataMessage
.Quote
.QuotedAttachment quotedAttachment
: quote
.getAttachments()) {
1627 final SignalServiceAttachment attachment
= quotedAttachment
.getThumbnail();
1628 if (attachment
!= null && attachment
.isPointer()) {
1630 retrieveAttachment(attachment
.asPointer());
1631 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1632 logger
.warn("Failed to retrieve quote attachment thumbnail ({}), ignoring: {}",
1633 attachment
.asPointer().getRemoteId(),
1639 if (message
.getSticker().isPresent()) {
1640 final SignalServiceDataMessage
.Sticker messageSticker
= message
.getSticker().get();
1641 Sticker sticker
= account
.getStickerStore().getSticker(messageSticker
.getPackId());
1642 if (sticker
== null) {
1643 sticker
= new Sticker(messageSticker
.getPackId(), messageSticker
.getPackKey());
1644 account
.getStickerStore().updateSticker(sticker
);
1650 private GroupInfoV2
getOrMigrateGroup(
1651 final GroupMasterKey groupMasterKey
, final int revision
, final byte[] signedGroupChange
1653 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
1655 GroupIdV2 groupId
= GroupUtils
.getGroupIdV2(groupSecretParams
);
1656 GroupInfo groupInfo
= getGroup(groupId
);
1657 final GroupInfoV2 groupInfoV2
;
1658 if (groupInfo
instanceof GroupInfoV1
) {
1659 // Received a v2 group message for a v1 group, we need to locally migrate the group
1660 account
.getGroupStore().deleteGroup(groupInfo
.getGroupId());
1661 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1662 logger
.info("Locally migrated group {} to group v2, id: {}",
1663 groupInfo
.getGroupId().toBase64(),
1664 groupInfoV2
.getGroupId().toBase64());
1665 } else if (groupInfo
instanceof GroupInfoV2
) {
1666 groupInfoV2
= (GroupInfoV2
) groupInfo
;
1668 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1671 if (groupInfoV2
.getGroup() == null || groupInfoV2
.getGroup().getRevision() < revision
) {
1672 DecryptedGroup group
= null;
1673 if (signedGroupChange
!= null
1674 && groupInfoV2
.getGroup() != null
1675 && groupInfoV2
.getGroup().getRevision() + 1 == revision
) {
1676 group
= groupHelper
.getUpdatedDecryptedGroup(groupInfoV2
.getGroup(), signedGroupChange
, groupMasterKey
);
1678 if (group
== null) {
1679 group
= groupHelper
.getDecryptedGroup(groupSecretParams
);
1681 if (group
!= null) {
1682 storeProfileKeysFromMembers(group
);
1683 final String avatar
= group
.getAvatar();
1684 if (avatar
!= null && !avatar
.isEmpty()) {
1686 retrieveGroupAvatar(groupId
, groupSecretParams
, avatar
);
1687 } catch (IOException e
) {
1688 logger
.warn("Failed to download group avatar, ignoring: {}", e
.getMessage());
1692 groupInfoV2
.setGroup(group
);
1693 account
.getGroupStore().updateGroup(groupInfoV2
);
1699 private void storeProfileKeysFromMembers(final DecryptedGroup group
) {
1700 for (DecryptedMember member
: group
.getMembersList()) {
1701 final SignalServiceAddress address
= resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil
.parseOrThrow(
1702 member
.getUuid().toByteArray()), null));
1704 account
.getProfileStore()
1705 .storeProfileKey(address
, new ProfileKey(member
.getProfileKey().toByteArray()));
1706 } catch (InvalidInputException ignored
) {
1711 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1712 for (CachedMessage cachedMessage
: account
.getMessageCache().getCachedMessages()) {
1713 retryFailedReceivedMessage(handler
, ignoreAttachments
, cachedMessage
);
1717 private void retryFailedReceivedMessage(
1718 final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final CachedMessage cachedMessage
1720 SignalServiceEnvelope envelope
= cachedMessage
.loadEnvelope();
1721 if (envelope
== null) {
1724 SignalServiceContent content
= null;
1725 if (!envelope
.isReceipt()) {
1727 content
= decryptMessage(envelope
);
1728 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1730 } catch (Exception er
) {
1731 // All other errors are not recoverable, so delete the cached message
1732 cachedMessage
.delete();
1735 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1736 for (HandleAction action
: actions
) {
1738 action
.execute(this);
1739 } catch (Throwable e
) {
1740 e
.printStackTrace();
1745 handler
.handleMessage(envelope
, content
, null);
1746 cachedMessage
.delete();
1749 public void receiveMessages(
1752 boolean returnOnTimeout
,
1753 boolean ignoreAttachments
,
1754 ReceiveMessageHandler handler
1755 ) throws IOException
{
1756 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1758 Set
<HandleAction
> queuedActions
= null;
1760 final SignalServiceMessagePipe messagePipe
= getOrCreateMessagePipe();
1762 boolean hasCaughtUpWithOldMessages
= false;
1765 SignalServiceEnvelope envelope
;
1766 SignalServiceContent content
= null;
1767 Exception exception
= null;
1768 final CachedMessage
[] cachedMessage
= {null};
1770 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1771 // store message on disk, before acknowledging receipt to the server
1772 cachedMessage
[0] = account
.getMessageCache().cacheMessage(envelope1
);
1774 if (result
.isPresent()) {
1775 envelope
= result
.get();
1777 // Received indicator that server queue is empty
1778 hasCaughtUpWithOldMessages
= true;
1780 if (queuedActions
!= null) {
1781 for (HandleAction action
: queuedActions
) {
1783 action
.execute(this);
1784 } catch (Throwable e
) {
1785 e
.printStackTrace();
1789 queuedActions
.clear();
1790 queuedActions
= null;
1793 // Continue to wait another timeout for new messages
1796 } catch (TimeoutException e
) {
1797 if (returnOnTimeout
) return;
1799 } catch (InvalidVersionException e
) {
1800 logger
.warn("Error while receiving messages, ignoring: {}", e
.getMessage());
1804 if (envelope
.hasSource()) {
1805 // Store uuid if we don't have it already
1806 SignalServiceAddress source
= envelope
.getSourceAddress();
1807 resolveSignalServiceAddress(source
);
1809 if (!envelope
.isReceipt()) {
1811 content
= decryptMessage(envelope
);
1812 } catch (Exception e
) {
1815 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1816 if (hasCaughtUpWithOldMessages
) {
1817 for (HandleAction action
: actions
) {
1819 action
.execute(this);
1820 } catch (Throwable e
) {
1821 e
.printStackTrace();
1825 if (queuedActions
== null) {
1826 queuedActions
= new HashSet
<>();
1828 queuedActions
.addAll(actions
);
1832 if (!isMessageBlocked(envelope
, content
)) {
1833 handler
.handleMessage(envelope
, content
, exception
);
1835 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1836 if (cachedMessage
[0] != null) {
1837 cachedMessage
[0].delete();
1843 private boolean isMessageBlocked(
1844 SignalServiceEnvelope envelope
, SignalServiceContent content
1846 SignalServiceAddress source
;
1847 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1848 source
= envelope
.getSourceAddress();
1849 } else if (content
!= null) {
1850 source
= content
.getSender();
1854 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1855 if (sourceContact
!= null && sourceContact
.blocked
) {
1859 if (content
!= null && content
.getDataMessage().isPresent()) {
1860 SignalServiceDataMessage message
= content
.getDataMessage().get();
1861 if (message
.getGroupContext().isPresent()) {
1862 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1863 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1864 if (groupInfo
.getType() != SignalServiceGroup
.Type
.DELIVER
) {
1868 GroupId groupId
= GroupUtils
.getGroupId(message
.getGroupContext().get());
1869 GroupInfo group
= getGroup(groupId
);
1870 if (group
!= null && group
.isBlocked()) {
1878 private List
<HandleAction
> handleMessage(
1879 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
1881 List
<HandleAction
> actions
= new ArrayList
<>();
1882 if (content
!= null) {
1883 final SignalServiceAddress sender
;
1884 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1885 sender
= envelope
.getSourceAddress();
1887 sender
= content
.getSender();
1889 // Store uuid if we don't have it already
1890 resolveSignalServiceAddress(sender
);
1892 if (content
.getDataMessage().isPresent()) {
1893 SignalServiceDataMessage message
= content
.getDataMessage().get();
1895 if (content
.isNeedsReceipt()) {
1896 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1899 actions
.addAll(handleSignalServiceDataMessage(message
,
1902 account
.getSelfAddress(),
1903 ignoreAttachments
));
1905 if (content
.getSyncMessage().isPresent()) {
1906 account
.setMultiDevice(true);
1907 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1908 if (syncMessage
.getSent().isPresent()) {
1909 SentTranscriptMessage message
= syncMessage
.getSent().get();
1910 final SignalServiceAddress destination
= message
.getDestination().orNull();
1911 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
1915 ignoreAttachments
));
1917 if (syncMessage
.getRequest().isPresent()) {
1918 RequestMessage rm
= syncMessage
.getRequest().get();
1919 if (rm
.isContactsRequest()) {
1920 actions
.add(SendSyncContactsAction
.create());
1922 if (rm
.isGroupsRequest()) {
1923 actions
.add(SendSyncGroupsAction
.create());
1925 if (rm
.isBlockedListRequest()) {
1926 actions
.add(SendSyncBlockedListAction
.create());
1928 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
1930 if (syncMessage
.getGroups().isPresent()) {
1931 File tmpFile
= null;
1933 tmpFile
= IOUtils
.createTempFile();
1934 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups()
1936 .asPointer(), tmpFile
)) {
1937 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1939 while ((g
= s
.read()) != null) {
1940 GroupInfoV1 syncGroup
= account
.getGroupStore()
1941 .getOrCreateGroupV1(GroupId
.v1(g
.getId()));
1942 if (syncGroup
!= null) {
1943 if (g
.getName().isPresent()) {
1944 syncGroup
.name
= g
.getName().get();
1946 syncGroup
.addMembers(g
.getMembers()
1948 .map(this::resolveSignalServiceAddress
)
1949 .collect(Collectors
.toSet()));
1950 if (!g
.isActive()) {
1951 syncGroup
.removeMember(account
.getSelfAddress());
1953 // Add ourself to the member set as it's marked as active
1954 syncGroup
.addMembers(List
.of(account
.getSelfAddress()));
1956 syncGroup
.blocked
= g
.isBlocked();
1957 if (g
.getColor().isPresent()) {
1958 syncGroup
.color
= g
.getColor().get();
1961 if (g
.getAvatar().isPresent()) {
1962 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.getGroupId());
1964 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1965 syncGroup
.archived
= g
.isArchived();
1966 account
.getGroupStore().updateGroup(syncGroup
);
1970 } catch (Exception e
) {
1971 logger
.warn("Failed to handle received sync groups “{}”, ignoring: {}",
1974 e
.printStackTrace();
1976 if (tmpFile
!= null) {
1978 Files
.delete(tmpFile
.toPath());
1979 } catch (IOException e
) {
1980 logger
.warn("Failed to delete received groups temp file “{}”, ignoring: {}",
1987 if (syncMessage
.getBlockedList().isPresent()) {
1988 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1989 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1990 setContactBlocked(resolveSignalServiceAddress(address
), true);
1992 for (GroupId groupId
: blockedListMessage
.getGroupIds()
1994 .map(GroupId
::unknownVersion
)
1995 .collect(Collectors
.toSet())) {
1997 setGroupBlocked(groupId
, true);
1998 } catch (GroupNotFoundException e
) {
1999 logger
.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
2000 groupId
.toBase64());
2004 if (syncMessage
.getContacts().isPresent()) {
2005 File tmpFile
= null;
2007 tmpFile
= IOUtils
.createTempFile();
2008 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
2009 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream()
2010 .asPointer(), tmpFile
)) {
2011 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
2012 if (contactsMessage
.isComplete()) {
2013 account
.getContactStore().clear();
2016 while ((c
= s
.read()) != null) {
2017 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
2018 account
.setProfileKey(c
.getProfileKey().get());
2020 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
2021 ContactInfo contact
= account
.getContactStore().getContact(address
);
2022 if (contact
== null) {
2023 contact
= new ContactInfo(address
);
2025 if (c
.getName().isPresent()) {
2026 contact
.name
= c
.getName().get();
2028 if (c
.getColor().isPresent()) {
2029 contact
.color
= c
.getColor().get();
2031 if (c
.getProfileKey().isPresent()) {
2032 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
2034 if (c
.getVerified().isPresent()) {
2035 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
2036 account
.getSignalProtocolStore()
2037 .setIdentityTrustLevel(verifiedMessage
.getDestination(),
2038 verifiedMessage
.getIdentityKey(),
2039 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2041 if (c
.getExpirationTimer().isPresent()) {
2042 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
2044 contact
.blocked
= c
.isBlocked();
2045 contact
.inboxPosition
= c
.getInboxPosition().orNull();
2046 contact
.archived
= c
.isArchived();
2047 account
.getContactStore().updateContact(contact
);
2049 if (c
.getAvatar().isPresent()) {
2050 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
2054 } catch (Exception e
) {
2055 e
.printStackTrace();
2057 if (tmpFile
!= null) {
2059 Files
.delete(tmpFile
.toPath());
2060 } catch (IOException e
) {
2061 logger
.warn("Failed to delete received contacts temp file “{}”, ignoring: {}",
2068 if (syncMessage
.getVerified().isPresent()) {
2069 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
2070 account
.getSignalProtocolStore()
2071 .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()),
2072 verifiedMessage
.getIdentityKey(),
2073 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2075 if (syncMessage
.getStickerPackOperations().isPresent()) {
2076 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations()
2078 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
2079 if (!m
.getPackId().isPresent()) {
2082 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
2083 if (sticker
== null) {
2084 if (!m
.getPackKey().isPresent()) {
2087 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
2089 sticker
.setInstalled(!m
.getType().isPresent()
2090 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
2091 account
.getStickerStore().updateSticker(sticker
);
2094 if (syncMessage
.getConfiguration().isPresent()) {
2102 private File
getContactAvatarFile(String number
) {
2103 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
2106 private File
retrieveContactAvatarAttachment(
2107 SignalServiceAttachment attachment
, String number
2108 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2109 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2110 if (attachment
.isPointer()) {
2111 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2112 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
2114 SignalServiceAttachmentStream stream
= attachment
.asStream();
2115 return AttachmentUtils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
2119 private File
getGroupAvatarFile(GroupId groupId
) {
2120 return new File(pathConfig
.getAvatarsPath(), "group-" + groupId
.toBase64().replace("/", "_"));
2123 private File
retrieveGroupAvatarAttachment(
2124 SignalServiceAttachment attachment
, GroupId groupId
2125 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2126 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2127 if (attachment
.isPointer()) {
2128 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2129 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
2131 SignalServiceAttachmentStream stream
= attachment
.asStream();
2132 return AttachmentUtils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
2136 private File
retrieveGroupAvatar(
2137 GroupId groupId
, GroupSecretParams groupSecretParams
, String cdnKey
2138 ) throws IOException
{
2139 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2140 File outputFile
= getGroupAvatarFile(groupId
);
2141 GroupsV2Operations
.GroupOperations groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
2143 File tmpFile
= IOUtils
.createTempFile();
2144 tmpFile
.deleteOnExit();
2145 try (InputStream input
= messageReceiver
.retrieveGroupsV2ProfileAvatar(cdnKey
,
2147 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2148 byte[] encryptedData
= IOUtils
.readFully(input
);
2150 byte[] decryptedData
= groupOperations
.decryptAvatar(encryptedData
);
2151 try (OutputStream output
= new FileOutputStream(outputFile
)) {
2152 output
.write(decryptedData
);
2156 Files
.delete(tmpFile
.toPath());
2157 } catch (IOException e
) {
2158 logger
.warn("Failed to delete received group avatar temp file “{}”, ignoring: {}",
2166 private File
getProfileAvatarFile(SignalServiceAddress address
) {
2167 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
2170 private File
retrieveProfileAvatar(
2171 SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
2172 ) throws IOException
{
2173 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2174 File outputFile
= getProfileAvatarFile(address
);
2176 File tmpFile
= IOUtils
.createTempFile();
2177 try (InputStream input
= messageReceiver
.retrieveProfileAvatar(avatarPath
,
2180 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2181 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2182 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
2185 Files
.delete(tmpFile
.toPath());
2186 } catch (IOException e
) {
2187 logger
.warn("Failed to delete received profile avatar temp file “{}”, ignoring: {}",
2195 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
2196 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
2199 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2200 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
2201 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
2204 private File
retrieveAttachment(
2205 SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
2206 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2207 if (storePreview
&& pointer
.getPreview().isPresent()) {
2208 File previewFile
= new File(outputFile
+ ".preview");
2209 try (OutputStream output
= new FileOutputStream(previewFile
)) {
2210 byte[] preview
= pointer
.getPreview().get();
2211 output
.write(preview
, 0, preview
.length
);
2212 } catch (FileNotFoundException e
) {
2213 e
.printStackTrace();
2218 File tmpFile
= IOUtils
.createTempFile();
2219 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
,
2221 ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
2222 IOUtils
.copyStreamToFile(input
, outputFile
);
2225 Files
.delete(tmpFile
.toPath());
2226 } catch (IOException e
) {
2227 logger
.warn("Failed to delete received attachment temp file “{}”, ignoring: {}",
2235 private InputStream
retrieveAttachmentAsStream(
2236 SignalServiceAttachmentPointer pointer
, File tmpFile
2237 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2238 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
2241 void sendGroups() throws IOException
, UntrustedIdentityException
{
2242 File groupsFile
= IOUtils
.createTempFile();
2245 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
2246 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
2247 for (GroupInfo
record : getGroups()) {
2248 if (record instanceof GroupInfoV1
) {
2249 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
2250 out
.write(new DeviceGroup(groupInfo
.getGroupId().serialize(),
2251 Optional
.fromNullable(groupInfo
.name
),
2252 new ArrayList
<>(groupInfo
.getMembers()),
2253 createGroupAvatarAttachment(groupInfo
.getGroupId()),
2254 groupInfo
.isMember(account
.getSelfAddress()),
2255 Optional
.of(groupInfo
.messageExpirationTime
),
2256 Optional
.fromNullable(groupInfo
.color
),
2258 Optional
.fromNullable(groupInfo
.inboxPosition
),
2259 groupInfo
.archived
));
2264 if (groupsFile
.exists() && groupsFile
.length() > 0) {
2265 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
2266 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2267 .withStream(groupsFileStream
)
2268 .withContentType("application/octet-stream")
2269 .withLength(groupsFile
.length())
2272 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
2277 Files
.delete(groupsFile
.toPath());
2278 } catch (IOException e
) {
2279 logger
.warn("Failed to delete groups temp file “{}”, ignoring: {}", groupsFile
, e
.getMessage());
2284 public void sendContacts() throws IOException
, UntrustedIdentityException
{
2285 File contactsFile
= IOUtils
.createTempFile();
2288 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
2289 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
2290 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2291 VerifiedMessage verifiedMessage
= null;
2292 IdentityInfo currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
2293 if (currentIdentity
!= null) {
2294 verifiedMessage
= new VerifiedMessage(record.getAddress(),
2295 currentIdentity
.getIdentityKey(),
2296 currentIdentity
.getTrustLevel().toVerifiedState(),
2297 currentIdentity
.getDateAdded().getTime());
2300 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
2301 out
.write(new DeviceContact(record.getAddress(),
2302 Optional
.fromNullable(record.name
),
2303 createContactAvatarAttachment(record.number
),
2304 Optional
.fromNullable(record.color
),
2305 Optional
.fromNullable(verifiedMessage
),
2306 Optional
.fromNullable(profileKey
),
2308 Optional
.of(record.messageExpirationTime
),
2309 Optional
.fromNullable(record.inboxPosition
),
2313 if (account
.getProfileKey() != null) {
2314 // Send our own profile key as well
2315 out
.write(new DeviceContact(account
.getSelfAddress(),
2320 Optional
.of(account
.getProfileKey()),
2328 if (contactsFile
.exists() && contactsFile
.length() > 0) {
2329 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
2330 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2331 .withStream(contactsFileStream
)
2332 .withContentType("application/octet-stream")
2333 .withLength(contactsFile
.length())
2336 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
2341 Files
.delete(contactsFile
.toPath());
2342 } catch (IOException e
) {
2343 logger
.warn("Failed to delete contacts temp file “{}”, ignoring: {}", contactsFile
, e
.getMessage());
2348 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2349 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2350 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2351 if (record.blocked
) {
2352 addresses
.add(record.getAddress());
2355 List
<byte[]> groupIds
= new ArrayList
<>();
2356 for (GroupInfo
record : getGroups()) {
2357 if (record.isBlocked()) {
2358 groupIds
.add(record.getGroupId().serialize());
2361 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2364 private void sendVerifiedMessage(
2365 SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
2366 ) throws IOException
, UntrustedIdentityException
{
2367 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
,
2369 trustLevel
.toVerifiedState(),
2370 System
.currentTimeMillis());
2371 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2374 public List
<ContactInfo
> getContacts() {
2375 return account
.getContactStore().getContacts();
2378 public ContactInfo
getContact(String number
) {
2379 return account
.getContactStore().getContact(Utils
.getSignalServiceAddressFromIdentifier(number
));
2382 public GroupInfo
getGroup(GroupId groupId
) {
2383 final GroupInfo group
= account
.getGroupStore().getGroup(groupId
);
2384 if (group
instanceof GroupInfoV2
&& ((GroupInfoV2
) group
).getGroup() == null) {
2385 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(((GroupInfoV2
) group
).getMasterKey());
2386 ((GroupInfoV2
) group
).setGroup(groupHelper
.getDecryptedGroup(groupSecretParams
));
2387 account
.getGroupStore().updateGroup(group
);
2392 public List
<IdentityInfo
> getIdentities() {
2393 return account
.getSignalProtocolStore().getIdentities();
2396 public List
<IdentityInfo
> getIdentities(String number
) throws InvalidNumberException
{
2397 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2401 * Trust this the identity with this fingerprint
2403 * @param name username of the identity
2404 * @param fingerprint Fingerprint
2406 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2407 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2408 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2412 for (IdentityInfo id
: ids
) {
2413 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2417 account
.getSignalProtocolStore()
2418 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2420 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2421 } catch (IOException
| UntrustedIdentityException e
) {
2422 e
.printStackTrace();
2431 * Trust this the identity with this safety number
2433 * @param name username of the identity
2434 * @param safetyNumber Safety number
2436 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2437 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2438 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2442 for (IdentityInfo id
: ids
) {
2443 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2447 account
.getSignalProtocolStore()
2448 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2450 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2451 } catch (IOException
| UntrustedIdentityException e
) {
2452 e
.printStackTrace();
2461 * Trust all keys of this identity without verification
2463 * @param name username of the identity
2465 public boolean trustIdentityAllKeys(String name
) {
2466 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2467 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2471 for (IdentityInfo id
: ids
) {
2472 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2473 account
.getSignalProtocolStore()
2474 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2476 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2477 } catch (IOException
| UntrustedIdentityException e
) {
2478 e
.printStackTrace();
2486 public String
computeSafetyNumber(
2487 SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
2489 return Utils
.computeSafetyNumber(ServiceConfig
.capabilities
.isUuid(),
2490 account
.getSelfAddress(),
2491 getIdentityKeyPair().getPublicKey(),
2496 void saveAccount() {
2500 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2501 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
)
2503 : PhoneNumberFormatter
.formatNumber(identifier
, account
.getUsername());
2504 return resolveSignalServiceAddress(canonicalizedNumber
);
2507 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2508 SignalServiceAddress address
= Utils
.getSignalServiceAddressFromIdentifier(identifier
);
2510 return resolveSignalServiceAddress(address
);
2513 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2514 if (address
.matches(account
.getSelfAddress())) {
2515 return account
.getSelfAddress();
2518 return account
.getRecipientStore().resolveServiceAddress(address
);
2522 public void close() throws IOException
{
2526 void close(boolean closeAccount
) throws IOException
{
2527 if (messagePipe
!= null) {
2528 messagePipe
.shutdown();
2532 if (unidentifiedMessagePipe
!= null) {
2533 unidentifiedMessagePipe
.shutdown();
2534 unidentifiedMessagePipe
= null;
2537 if (closeAccount
&& account
!= null) {
2543 public interface ReceiveMessageHandler
{
2545 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);