1 package org
.asamk
.signal
.manager
.helper
;
3 import org
.asamk
.signal
.manager
.SignalDependencies
;
4 import org
.asamk
.signal
.manager
.api
.PhoneNumberSharingMode
;
5 import org
.asamk
.signal
.manager
.api
.TrustLevel
;
6 import org
.asamk
.signal
.manager
.groups
.GroupId
;
7 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
8 import org
.asamk
.signal
.manager
.storage
.recipients
.Contact
;
9 import org
.asamk
.signal
.manager
.storage
.recipients
.Profile
;
10 import org
.signal
.libsignal
.protocol
.IdentityKey
;
11 import org
.signal
.libsignal
.protocol
.InvalidKeyException
;
12 import org
.signal
.libsignal
.zkgroup
.InvalidInputException
;
13 import org
.signal
.libsignal
.zkgroup
.groups
.GroupMasterKey
;
14 import org
.signal
.libsignal
.zkgroup
.profiles
.ProfileKey
;
15 import org
.slf4j
.Logger
;
16 import org
.slf4j
.LoggerFactory
;
17 import org
.whispersystems
.signalservice
.api
.storage
.SignalAccountRecord
;
18 import org
.whispersystems
.signalservice
.api
.storage
.SignalStorageManifest
;
19 import org
.whispersystems
.signalservice
.api
.storage
.SignalStorageRecord
;
20 import org
.whispersystems
.signalservice
.api
.storage
.StorageId
;
21 import org
.whispersystems
.signalservice
.internal
.storage
.protos
.AccountRecord
;
22 import org
.whispersystems
.signalservice
.internal
.storage
.protos
.ManifestRecord
;
24 import java
.io
.IOException
;
25 import java
.util
.ArrayList
;
26 import java
.util
.Collection
;
27 import java
.util
.Collections
;
28 import java
.util
.List
;
29 import java
.util
.Optional
;
30 import java
.util
.stream
.Collectors
;
32 public class StorageHelper
{
34 private final static Logger logger
= LoggerFactory
.getLogger(StorageHelper
.class);
36 private final SignalAccount account
;
37 private final SignalDependencies dependencies
;
38 private final Context context
;
40 public StorageHelper(final Context context
) {
41 this.account
= context
.getAccount();
42 this.dependencies
= context
.getDependencies();
43 this.context
= context
;
46 public void readDataFromStorage() throws IOException
{
47 final var storageKey
= account
.getOrCreateStorageKey();
48 if (storageKey
== null) {
49 logger
.debug("Storage key unknown, requesting from primary device.");
50 context
.getSyncHelper().requestSyncKeys();
54 logger
.debug("Reading data from remote storage");
55 Optional
<SignalStorageManifest
> manifest
;
57 manifest
= dependencies
.getAccountManager()
58 .getStorageManifestIfDifferentVersion(storageKey
, account
.getStorageManifestVersion());
59 } catch (InvalidKeyException e
) {
60 logger
.warn("Manifest couldn't be decrypted, ignoring.");
64 if (manifest
.isEmpty()) {
65 logger
.debug("Manifest is up to date, does not exist or couldn't be decrypted, ignoring.");
69 logger
.trace("Remote storage manifest has {} records", manifest
.get().getStorageIds().size());
70 final var storageIds
= manifest
.get()
73 .filter(id
-> !id
.isUnknown())
74 .collect(Collectors
.toSet());
76 Optional
<SignalStorageManifest
> localManifest
= account
.getStorageManifest();
77 localManifest
.ifPresent(m
-> m
.getStorageIds().forEach(storageIds
::remove
));
79 logger
.trace("Reading {} new records", manifest
.get().getStorageIds().size());
80 for (final var record : getSignalStorageRecords(storageIds
)) {
81 logger
.debug("Reading record of type {}", record.getType());
82 if (record.getType() == ManifestRecord
.Identifier
.Type
.ACCOUNT_VALUE
) {
83 readAccountRecord(record);
84 } else if (record.getType() == ManifestRecord
.Identifier
.Type
.GROUPV2_VALUE
) {
85 readGroupV2Record(record);
86 } else if (record.getType() == ManifestRecord
.Identifier
.Type
.GROUPV1_VALUE
) {
87 readGroupV1Record(record);
88 } else if (record.getType() == ManifestRecord
.Identifier
.Type
.CONTACT_VALUE
) {
89 readContactRecord(record);
92 account
.setStorageManifestVersion(manifest
.get().getVersion());
93 account
.setStorageManifest(manifest
.get());
94 logger
.debug("Done reading data from remote storage");
97 private void readContactRecord(final SignalStorageRecord
record) {
98 if (record == null || record.getContact().isEmpty()) {
102 final var contactRecord
= record.getContact().get();
103 final var address
= contactRecord
.getAddress();
104 final var recipientId
= account
.getRecipientResolver().resolveRecipient(address
);
106 final var contact
= account
.getContactStore().getContact(recipientId
);
107 final var blocked
= contact
!= null && contact
.isBlocked();
108 final var profileShared
= contact
!= null && contact
.isProfileSharingEnabled();
109 final var archived
= contact
!= null && contact
.isArchived();
110 if (blocked
!= contactRecord
.isBlocked()
111 || profileShared
!= contactRecord
.isProfileSharingEnabled()
112 || archived
!= contactRecord
.isArchived()) {
113 logger
.debug("Storing new or updated contact {}", recipientId
);
114 final var contactBuilder
= contact
== null ? Contact
.newBuilder() : Contact
.newBuilder(contact
);
115 final var newContact
= contactBuilder
.withBlocked(contactRecord
.isBlocked())
116 .withProfileSharingEnabled(contactRecord
.isProfileSharingEnabled())
117 .withArchived(contactRecord
.isArchived())
119 account
.getContactStore().storeContact(recipientId
, newContact
);
122 final var profile
= account
.getProfileStore().getProfile(recipientId
);
123 final var givenName
= profile
== null ?
null : profile
.getGivenName();
124 final var familyName
= profile
== null ?
null : profile
.getFamilyName();
125 if ((contactRecord
.getGivenName().isPresent() && !contactRecord
.getGivenName().get().equals(givenName
)) || (
126 contactRecord
.getFamilyName().isPresent() && !contactRecord
.getFamilyName().get().equals(familyName
)
128 final var profileBuilder
= profile
== null ? Profile
.newBuilder() : Profile
.newBuilder(profile
);
129 final var newProfile
= profileBuilder
.withGivenName(contactRecord
.getGivenName().orElse(null))
130 .withFamilyName(contactRecord
.getFamilyName().orElse(null))
132 account
.getProfileStore().storeProfile(recipientId
, newProfile
);
134 if (contactRecord
.getProfileKey().isPresent()) {
136 logger
.trace("Storing profile key {}", recipientId
);
137 final var profileKey
= new ProfileKey(contactRecord
.getProfileKey().get());
138 account
.getProfileStore().storeProfileKey(recipientId
, profileKey
);
139 } catch (InvalidInputException e
) {
140 logger
.warn("Received invalid contact profile key from storage");
143 if (contactRecord
.getIdentityKey().isPresent()) {
145 logger
.trace("Storing identity key {}", recipientId
);
146 final var identityKey
= new IdentityKey(contactRecord
.getIdentityKey().get());
147 account
.getIdentityKeyStore().saveIdentity(address
.getServiceId(), identityKey
);
149 final var trustLevel
= TrustLevel
.fromIdentityState(contactRecord
.getIdentityState());
150 if (trustLevel
!= null) {
151 account
.getIdentityKeyStore()
152 .setIdentityTrustLevel(address
.getServiceId(), identityKey
, trustLevel
);
154 } catch (InvalidKeyException e
) {
155 logger
.warn("Received invalid contact identity key from storage");
160 private void readGroupV1Record(final SignalStorageRecord
record) {
161 if (record == null || record.getGroupV1().isEmpty()) {
165 final var groupV1Record
= record.getGroupV1().get();
166 final var groupIdV1
= GroupId
.v1(groupV1Record
.getGroupId());
168 var group
= account
.getGroupStore().getGroup(groupIdV1
);
171 context
.getGroupHelper().sendGroupInfoRequest(groupIdV1
, account
.getSelfRecipientId());
172 } catch (Throwable e
) {
173 logger
.warn("Failed to send group request", e
);
175 group
= account
.getGroupStore().getOrCreateGroupV1(groupIdV1
);
177 if (group
!= null && group
.isBlocked() != groupV1Record
.isBlocked()) {
178 group
.setBlocked(groupV1Record
.isBlocked());
179 account
.getGroupStore().updateGroup(group
);
183 private void readGroupV2Record(final SignalStorageRecord
record) {
184 if (record == null || record.getGroupV2().isEmpty()) {
188 final var groupV2Record
= record.getGroupV2().get();
189 if (groupV2Record
.isArchived()) {
193 final GroupMasterKey groupMasterKey
;
195 groupMasterKey
= new GroupMasterKey(groupV2Record
.getMasterKeyBytes());
196 } catch (InvalidInputException e
) {
197 logger
.warn("Received invalid group master key from storage");
201 final var group
= context
.getGroupHelper().getOrMigrateGroup(groupMasterKey
, 0, null);
202 if (group
.isBlocked() != groupV2Record
.isBlocked()) {
203 group
.setBlocked(groupV2Record
.isBlocked());
204 account
.getGroupStore().updateGroup(group
);
208 private void readAccountRecord(final SignalStorageRecord
record) throws IOException
{
209 if (record == null) {
210 logger
.warn("Could not find account record, even though we had an ID, ignoring.");
214 SignalAccountRecord accountRecord
= record.getAccount().orElse(null);
215 if (accountRecord
== null) {
216 logger
.warn("The storage record didn't actually have an account, ignoring.");
220 if (!accountRecord
.getE164().equals(account
.getNumber())) {
221 context
.getAccountHelper().checkWhoAmiI();
224 account
.getConfigurationStore().setReadReceipts(accountRecord
.isReadReceiptsEnabled());
225 account
.getConfigurationStore().setTypingIndicators(accountRecord
.isTypingIndicatorsEnabled());
226 account
.getConfigurationStore()
227 .setUnidentifiedDeliveryIndicators(accountRecord
.isSealedSenderIndicatorsEnabled());
228 account
.getConfigurationStore().setLinkPreviews(accountRecord
.isLinkPreviewsEnabled());
229 if (accountRecord
.getPhoneNumberSharingMode() != AccountRecord
.PhoneNumberSharingMode
.UNRECOGNIZED
) {
230 account
.getConfigurationStore()
231 .setPhoneNumberSharingMode(switch (accountRecord
.getPhoneNumberSharingMode()) {
232 case EVERYBODY
-> PhoneNumberSharingMode
.EVERYBODY
;
233 case NOBODY
-> PhoneNumberSharingMode
.NOBODY
;
234 default -> PhoneNumberSharingMode
.CONTACTS
;
237 account
.getConfigurationStore().setPhoneNumberUnlisted(accountRecord
.isPhoneNumberUnlisted());
239 if (accountRecord
.getProfileKey().isPresent()) {
240 ProfileKey profileKey
;
242 profileKey
= new ProfileKey(accountRecord
.getProfileKey().get());
243 } catch (InvalidInputException e
) {
244 logger
.warn("Received invalid profile key from storage");
247 if (profileKey
!= null) {
248 account
.setProfileKey(profileKey
);
249 final var avatarPath
= accountRecord
.getAvatarUrlPath().orElse(null);
250 context
.getProfileHelper().downloadProfileAvatar(account
.getSelfRecipientId(), avatarPath
, profileKey
);
254 context
.getProfileHelper()
257 accountRecord
.getGivenName().orElse(null),
258 accountRecord
.getFamilyName().orElse(null),
265 private SignalStorageRecord
getSignalStorageRecord(final StorageId accountId
) throws IOException
{
266 List
<SignalStorageRecord
> records
;
268 records
= dependencies
.getAccountManager()
269 .readStorageRecords(account
.getStorageKey(), Collections
.singletonList(accountId
));
270 } catch (InvalidKeyException e
) {
271 logger
.warn("Failed to read storage records, ignoring.");
274 return records
.size() > 0 ? records
.get(0) : null;
277 private List
<SignalStorageRecord
> getSignalStorageRecords(final Collection
<StorageId
> storageIds
) throws IOException
{
278 List
<SignalStorageRecord
> records
;
280 records
= dependencies
.getAccountManager()
281 .readStorageRecords(account
.getStorageKey(), new ArrayList
<>(storageIds
));
282 } catch (InvalidKeyException e
) {
283 logger
.warn("Failed to read storage records, ignoring.");