1 package org
.asamk
.signal
.manager
.helper
;
3 import org
.asamk
.signal
.manager
.SignalDependencies
;
4 import org
.asamk
.signal
.manager
.TrustLevel
;
5 import org
.asamk
.signal
.manager
.api
.PhoneNumberSharingMode
;
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
.zkgroup
.InvalidInputException
;
10 import org
.signal
.zkgroup
.groups
.GroupMasterKey
;
11 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
12 import org
.slf4j
.Logger
;
13 import org
.slf4j
.LoggerFactory
;
14 import org
.whispersystems
.libsignal
.IdentityKey
;
15 import org
.whispersystems
.libsignal
.InvalidKeyException
;
16 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
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
.Collections
;
26 import java
.util
.Date
;
27 import java
.util
.List
;
28 import java
.util
.stream
.Collectors
;
30 public class StorageHelper
{
32 private final static Logger logger
= LoggerFactory
.getLogger(StorageHelper
.class);
34 private final SignalAccount account
;
35 private final SignalDependencies dependencies
;
36 private final GroupHelper groupHelper
;
37 private final ProfileHelper profileHelper
;
40 final SignalAccount account
,
41 final SignalDependencies dependencies
,
42 final GroupHelper groupHelper
,
43 final ProfileHelper profileHelper
45 this.account
= account
;
46 this.dependencies
= dependencies
;
47 this.groupHelper
= groupHelper
;
48 this.profileHelper
= profileHelper
;
51 public void readDataFromStorage() throws IOException
{
52 logger
.debug("Reading data from remote storage");
53 Optional
<SignalStorageManifest
> manifest
;
55 manifest
= dependencies
.getAccountManager()
56 .getStorageManifestIfDifferentVersion(account
.getStorageKey(), account
.getStorageManifestVersion());
57 } catch (InvalidKeyException e
) {
58 logger
.warn("Manifest couldn't be decrypted, ignoring.");
62 if (!manifest
.isPresent()) {
63 logger
.debug("Manifest is up to date, does not exist or couldn't be decrypted, ignoring.");
67 account
.setStorageManifestVersion(manifest
.get().getVersion());
69 readAccountRecord(manifest
.get());
71 final var storageIds
= manifest
.get()
74 .filter(id
-> !id
.isUnknown() && id
.getType() != ManifestRecord
.Identifier
.Type
.ACCOUNT_VALUE
)
75 .collect(Collectors
.toList());
77 for (final var record : getSignalStorageRecords(storageIds
)) {
78 if (record.getType() == ManifestRecord
.Identifier
.Type
.GROUPV2_VALUE
) {
79 readGroupV2Record(record);
80 } else if (record.getType() == ManifestRecord
.Identifier
.Type
.GROUPV1_VALUE
) {
81 readGroupV1Record(record);
82 } else if (record.getType() == ManifestRecord
.Identifier
.Type
.CONTACT_VALUE
) {
83 readContactRecord(record);
88 private void readContactRecord(final SignalStorageRecord
record) {
89 if (record == null || !record.getContact().isPresent()) {
93 final var contactRecord
= record.getContact().get();
94 final var address
= contactRecord
.getAddress();
96 final var recipientId
= account
.getRecipientStore().resolveRecipient(address
);
97 final var contact
= account
.getContactStore().getContact(recipientId
);
98 if (contactRecord
.getGivenName().isPresent() || contactRecord
.getFamilyName().isPresent() || (
99 (contact
== null || !contact
.isBlocked()) && contactRecord
.isBlocked()
101 final var newContact
= (contact
== null ? Contact
.newBuilder() : Contact
.newBuilder(contact
)).withBlocked(
102 contactRecord
.isBlocked())
103 .withName((contactRecord
.getGivenName().or("") + " " + contactRecord
.getFamilyName().or("")).trim())
105 account
.getContactStore().storeContact(recipientId
, newContact
);
108 if (contactRecord
.getProfileKey().isPresent()) {
110 final var profileKey
= new ProfileKey(contactRecord
.getProfileKey().get());
111 account
.getProfileStore().storeProfileKey(recipientId
, profileKey
);
112 } catch (InvalidInputException e
) {
113 logger
.warn("Received invalid contact profile key from storage");
116 if (contactRecord
.getIdentityKey().isPresent()) {
118 final var identityKey
= new IdentityKey(contactRecord
.getIdentityKey().get());
119 account
.getIdentityKeyStore().saveIdentity(recipientId
, identityKey
, new Date());
121 final var trustLevel
= TrustLevel
.fromIdentityState(contactRecord
.getIdentityState());
122 if (trustLevel
!= null) {
123 account
.getIdentityKeyStore().setIdentityTrustLevel(recipientId
, identityKey
, trustLevel
);
125 } catch (InvalidKeyException e
) {
126 logger
.warn("Received invalid contact identity key from storage");
131 private void readGroupV1Record(final SignalStorageRecord
record) {
132 if (record == null || !record.getGroupV1().isPresent()) {
136 final var groupV1Record
= record.getGroupV1().get();
137 final var groupIdV1
= GroupId
.v1(groupV1Record
.getGroupId());
139 final var group
= account
.getGroupStore().getGroup(groupIdV1
);
142 groupHelper
.sendGroupInfoRequest(groupIdV1
, account
.getSelfRecipientId());
143 } catch (Throwable e
) {
144 logger
.warn("Failed to send group request", e
);
147 final var groupV1
= account
.getGroupStore().getOrCreateGroupV1(groupIdV1
);
148 if (groupV1
.isBlocked() != groupV1Record
.isBlocked()) {
149 groupV1
.setBlocked(groupV1Record
.isBlocked());
150 account
.getGroupStore().updateGroup(groupV1
);
154 private void readGroupV2Record(final SignalStorageRecord
record) {
155 if (record == null || !record.getGroupV2().isPresent()) {
159 final var groupV2Record
= record.getGroupV2().get();
160 if (groupV2Record
.isArchived()) {
164 final GroupMasterKey groupMasterKey
;
166 groupMasterKey
= new GroupMasterKey(groupV2Record
.getMasterKeyBytes());
167 } catch (InvalidInputException e
) {
168 logger
.warn("Received invalid group master key from storage");
172 final var group
= groupHelper
.getOrMigrateGroup(groupMasterKey
, 0, null);
173 if (group
.isBlocked() != groupV2Record
.isBlocked()) {
174 group
.setBlocked(groupV2Record
.isBlocked());
175 account
.getGroupStore().updateGroup(group
);
179 private void readAccountRecord(final SignalStorageManifest manifest
) throws IOException
{
180 Optional
<StorageId
> accountId
= manifest
.getAccountStorageId();
181 if (!accountId
.isPresent()) {
182 logger
.warn("Manifest has no account record, ignoring.");
186 SignalStorageRecord
record = getSignalStorageRecord(accountId
.get());
187 if (record == null) {
188 logger
.warn("Could not find account record, even though we had an ID, ignoring.");
192 SignalAccountRecord accountRecord
= record.getAccount().orNull();
193 if (accountRecord
== null) {
194 logger
.warn("The storage record didn't actually have an account, ignoring.");
198 if (!accountRecord
.getE164().equals(account
.getAccount())) {
199 // TODO implement changed number handling
202 account
.getConfigurationStore().setReadReceipts(accountRecord
.isReadReceiptsEnabled());
203 account
.getConfigurationStore().setTypingIndicators(accountRecord
.isTypingIndicatorsEnabled());
204 account
.getConfigurationStore()
205 .setUnidentifiedDeliveryIndicators(accountRecord
.isSealedSenderIndicatorsEnabled());
206 account
.getConfigurationStore().setLinkPreviews(accountRecord
.isLinkPreviewsEnabled());
207 if (accountRecord
.getPhoneNumberSharingMode() != AccountRecord
.PhoneNumberSharingMode
.UNRECOGNIZED
) {
208 account
.getConfigurationStore()
209 .setPhoneNumberSharingMode(switch (accountRecord
.getPhoneNumberSharingMode()) {
210 case EVERYBODY
-> PhoneNumberSharingMode
.EVERYBODY
;
211 case NOBODY
-> PhoneNumberSharingMode
.NOBODY
;
212 default -> PhoneNumberSharingMode
.CONTACTS
;
215 account
.getConfigurationStore().setPhoneNumberUnlisted(accountRecord
.isPhoneNumberUnlisted());
217 if (accountRecord
.getProfileKey().isPresent()) {
218 ProfileKey profileKey
;
220 profileKey
= new ProfileKey(accountRecord
.getProfileKey().get());
221 } catch (InvalidInputException e
) {
222 logger
.warn("Received invalid profile key from storage");
225 if (profileKey
!= null) {
226 account
.setProfileKey(profileKey
);
227 final var avatarPath
= accountRecord
.getAvatarUrlPath().orNull();
228 profileHelper
.downloadProfileAvatar(account
.getSelfRecipientId(), avatarPath
, profileKey
);
232 profileHelper
.setProfile(false,
233 accountRecord
.getGivenName().orNull(),
234 accountRecord
.getFamilyName().orNull(),
240 private SignalStorageRecord
getSignalStorageRecord(final StorageId accountId
) throws IOException
{
241 List
<SignalStorageRecord
> records
;
243 records
= dependencies
.getAccountManager()
244 .readStorageRecords(account
.getStorageKey(), Collections
.singletonList(accountId
));
245 } catch (InvalidKeyException e
) {
246 logger
.warn("Failed to read storage records, ignoring.");
249 return records
.size() > 0 ? records
.get(0) : null;
252 private List
<SignalStorageRecord
> getSignalStorageRecords(final List
<StorageId
> storageIds
) throws IOException
{
253 List
<SignalStorageRecord
> records
;
255 records
= dependencies
.getAccountManager().readStorageRecords(account
.getStorageKey(), storageIds
);
256 } catch (InvalidKeyException e
) {
257 logger
.warn("Failed to read storage records, ignoring.");