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
.signal
.libsignal
.protocol
.IdentityKey
;
10 import org
.signal
.libsignal
.protocol
.InvalidKeyException
;
11 import org
.signal
.libsignal
.zkgroup
.InvalidInputException
;
12 import org
.signal
.libsignal
.zkgroup
.groups
.GroupMasterKey
;
13 import org
.signal
.libsignal
.zkgroup
.profiles
.ProfileKey
;
14 import org
.slf4j
.Logger
;
15 import org
.slf4j
.LoggerFactory
;
16 import org
.whispersystems
.signalservice
.api
.storage
.SignalAccountRecord
;
17 import org
.whispersystems
.signalservice
.api
.storage
.SignalStorageManifest
;
18 import org
.whispersystems
.signalservice
.api
.storage
.SignalStorageRecord
;
19 import org
.whispersystems
.signalservice
.api
.storage
.StorageId
;
20 import org
.whispersystems
.signalservice
.internal
.storage
.protos
.AccountRecord
;
21 import org
.whispersystems
.signalservice
.internal
.storage
.protos
.ManifestRecord
;
23 import java
.io
.IOException
;
24 import java
.util
.Collections
;
25 import java
.util
.Date
;
26 import java
.util
.List
;
27 import java
.util
.Optional
;
29 public class StorageHelper
{
31 private final static Logger logger
= LoggerFactory
.getLogger(StorageHelper
.class);
33 private final SignalAccount account
;
34 private final SignalDependencies dependencies
;
35 private final Context context
;
37 public StorageHelper(final Context context
) {
38 this.account
= context
.getAccount();
39 this.dependencies
= context
.getDependencies();
40 this.context
= context
;
43 public void readDataFromStorage() throws IOException
{
44 logger
.debug("Reading data from remote storage");
45 Optional
<SignalStorageManifest
> manifest
;
47 manifest
= dependencies
.getAccountManager()
48 .getStorageManifestIfDifferentVersion(account
.getStorageKey(), account
.getStorageManifestVersion());
49 } catch (InvalidKeyException e
) {
50 logger
.warn("Manifest couldn't be decrypted, ignoring.");
54 if (manifest
.isEmpty()) {
55 logger
.debug("Manifest is up to date, does not exist or couldn't be decrypted, ignoring.");
59 account
.setStorageManifestVersion(manifest
.get().getVersion());
61 readAccountRecord(manifest
.get());
63 final var storageIds
= manifest
.get()
66 .filter(id
-> !id
.isUnknown() && id
.getType() != ManifestRecord
.Identifier
.Type
.ACCOUNT_VALUE
)
69 for (final var record : getSignalStorageRecords(storageIds
)) {
70 if (record.getType() == ManifestRecord
.Identifier
.Type
.GROUPV2_VALUE
) {
71 readGroupV2Record(record);
72 } else if (record.getType() == ManifestRecord
.Identifier
.Type
.GROUPV1_VALUE
) {
73 readGroupV1Record(record);
74 } else if (record.getType() == ManifestRecord
.Identifier
.Type
.CONTACT_VALUE
) {
75 readContactRecord(record);
78 logger
.debug("Done reading data from remote storage");
81 private void readContactRecord(final SignalStorageRecord
record) {
82 if (record == null || record.getContact().isEmpty()) {
86 final var contactRecord
= record.getContact().get();
87 final var address
= contactRecord
.getAddress();
89 final var recipientId
= account
.getRecipientResolver().resolveRecipient(address
);
90 final var contact
= account
.getContactStore().getContact(recipientId
);
91 final var blocked
= contact
!= null && contact
.isBlocked();
92 final var profileShared
= contact
!= null && contact
.isProfileSharingEnabled();
93 if (contactRecord
.getGivenName().isPresent()
94 || contactRecord
.getFamilyName().isPresent()
95 || blocked
!= contactRecord
.isBlocked()
96 || profileShared
!= contactRecord
.isProfileSharingEnabled()) {
97 final var contactBuilder
= contact
== null ? Contact
.newBuilder() : Contact
.newBuilder(contact
);
98 final var name
= contactRecord
.getGivenName().orElse("") + " " + contactRecord
.getFamilyName().orElse("");
99 final var newContact
= contactBuilder
.withBlocked(contactRecord
.isBlocked())
100 .withName(name
.trim())
101 .withProfileSharingEnabled(contactRecord
.isProfileSharingEnabled())
103 account
.getContactStore().storeContact(recipientId
, newContact
);
106 if (contactRecord
.getProfileKey().isPresent()) {
108 final var profileKey
= new ProfileKey(contactRecord
.getProfileKey().get());
109 account
.getProfileStore().storeProfileKey(recipientId
, profileKey
);
110 } catch (InvalidInputException e
) {
111 logger
.warn("Received invalid contact profile key from storage");
114 if (contactRecord
.getIdentityKey().isPresent()) {
116 final var identityKey
= new IdentityKey(contactRecord
.getIdentityKey().get());
117 account
.getIdentityKeyStore().saveIdentity(recipientId
, identityKey
, new Date());
119 final var trustLevel
= TrustLevel
.fromIdentityState(contactRecord
.getIdentityState());
120 if (trustLevel
!= null) {
121 account
.getIdentityKeyStore().setIdentityTrustLevel(recipientId
, identityKey
, trustLevel
);
123 } catch (InvalidKeyException e
) {
124 logger
.warn("Received invalid contact identity key from storage");
129 private void readGroupV1Record(final SignalStorageRecord
record) {
130 if (record == null || record.getGroupV1().isEmpty()) {
134 final var groupV1Record
= record.getGroupV1().get();
135 final var groupIdV1
= GroupId
.v1(groupV1Record
.getGroupId());
137 var group
= account
.getGroupStore().getGroup(groupIdV1
);
140 context
.getGroupHelper().sendGroupInfoRequest(groupIdV1
, account
.getSelfRecipientId());
141 } catch (Throwable e
) {
142 logger
.warn("Failed to send group request", e
);
144 group
= account
.getGroupStore().getOrCreateGroupV1(groupIdV1
);
146 if (group
!= null && group
.isBlocked() != groupV1Record
.isBlocked()) {
147 group
.setBlocked(groupV1Record
.isBlocked());
148 account
.getGroupStore().updateGroup(group
);
152 private void readGroupV2Record(final SignalStorageRecord
record) {
153 if (record == null || record.getGroupV2().isEmpty()) {
157 final var groupV2Record
= record.getGroupV2().get();
158 if (groupV2Record
.isArchived()) {
162 final GroupMasterKey groupMasterKey
;
164 groupMasterKey
= new GroupMasterKey(groupV2Record
.getMasterKeyBytes());
165 } catch (InvalidInputException e
) {
166 logger
.warn("Received invalid group master key from storage");
170 final var group
= context
.getGroupHelper().getOrMigrateGroup(groupMasterKey
, 0, null);
171 if (group
.isBlocked() != groupV2Record
.isBlocked()) {
172 group
.setBlocked(groupV2Record
.isBlocked());
173 account
.getGroupStore().updateGroup(group
);
177 private void readAccountRecord(final SignalStorageManifest manifest
) throws IOException
{
178 Optional
<StorageId
> accountId
= manifest
.getAccountStorageId();
179 if (accountId
.isEmpty()) {
180 logger
.warn("Manifest has no account record, ignoring.");
184 SignalStorageRecord
record = getSignalStorageRecord(accountId
.get());
185 if (record == null) {
186 logger
.warn("Could not find account record, even though we had an ID, ignoring.");
190 SignalAccountRecord accountRecord
= record.getAccount().orElse(null);
191 if (accountRecord
== null) {
192 logger
.warn("The storage record didn't actually have an account, ignoring.");
196 if (!accountRecord
.getE164().equals(account
.getNumber())) {
197 context
.getAccountHelper().checkWhoAmiI();
200 account
.getConfigurationStore().setReadReceipts(accountRecord
.isReadReceiptsEnabled());
201 account
.getConfigurationStore().setTypingIndicators(accountRecord
.isTypingIndicatorsEnabled());
202 account
.getConfigurationStore()
203 .setUnidentifiedDeliveryIndicators(accountRecord
.isSealedSenderIndicatorsEnabled());
204 account
.getConfigurationStore().setLinkPreviews(accountRecord
.isLinkPreviewsEnabled());
205 if (accountRecord
.getPhoneNumberSharingMode() != AccountRecord
.PhoneNumberSharingMode
.UNRECOGNIZED
) {
206 account
.getConfigurationStore()
207 .setPhoneNumberSharingMode(switch (accountRecord
.getPhoneNumberSharingMode()) {
208 case EVERYBODY
-> PhoneNumberSharingMode
.EVERYBODY
;
209 case NOBODY
-> PhoneNumberSharingMode
.NOBODY
;
210 default -> PhoneNumberSharingMode
.CONTACTS
;
213 account
.getConfigurationStore().setPhoneNumberUnlisted(accountRecord
.isPhoneNumberUnlisted());
215 if (accountRecord
.getProfileKey().isPresent()) {
216 ProfileKey profileKey
;
218 profileKey
= new ProfileKey(accountRecord
.getProfileKey().get());
219 } catch (InvalidInputException e
) {
220 logger
.warn("Received invalid profile key from storage");
223 if (profileKey
!= null) {
224 account
.setProfileKey(profileKey
);
225 final var avatarPath
= accountRecord
.getAvatarUrlPath().orElse(null);
226 context
.getProfileHelper().downloadProfileAvatar(account
.getSelfRecipientId(), avatarPath
, profileKey
);
230 context
.getProfileHelper()
233 accountRecord
.getGivenName().orElse(null),
234 accountRecord
.getFamilyName().orElse(null),
241 private SignalStorageRecord
getSignalStorageRecord(final StorageId accountId
) throws IOException
{
242 List
<SignalStorageRecord
> records
;
244 records
= dependencies
.getAccountManager()
245 .readStorageRecords(account
.getStorageKey(), Collections
.singletonList(accountId
));
246 } catch (InvalidKeyException e
) {
247 logger
.warn("Failed to read storage records, ignoring.");
250 return records
.size() > 0 ? records
.get(0) : null;
253 private List
<SignalStorageRecord
> getSignalStorageRecords(final List
<StorageId
> storageIds
) throws IOException
{
254 List
<SignalStorageRecord
> records
;
256 records
= dependencies
.getAccountManager().readStorageRecords(account
.getStorageKey(), storageIds
);
257 } catch (InvalidKeyException e
) {
258 logger
.warn("Failed to read storage records, ignoring.");