]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java
426806ff365d81cc4e78456a72b48a6f9c510de5
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / helper / StorageHelper.java
1 package org.asamk.signal.manager.helper;
2
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;
22
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;
28
29 public class StorageHelper {
30
31 private final static Logger logger = LoggerFactory.getLogger(StorageHelper.class);
32
33 private final SignalAccount account;
34 private final SignalDependencies dependencies;
35 private final Context context;
36
37 public StorageHelper(final Context context) {
38 this.account = context.getAccount();
39 this.dependencies = context.getDependencies();
40 this.context = context;
41 }
42
43 public void readDataFromStorage() throws IOException {
44 logger.debug("Reading data from remote storage");
45 Optional<SignalStorageManifest> manifest;
46 try {
47 manifest = dependencies.getAccountManager()
48 .getStorageManifestIfDifferentVersion(account.getStorageKey(), account.getStorageManifestVersion());
49 } catch (InvalidKeyException e) {
50 logger.warn("Manifest couldn't be decrypted, ignoring.");
51 return;
52 }
53
54 if (manifest.isEmpty()) {
55 logger.debug("Manifest is up to date, does not exist or couldn't be decrypted, ignoring.");
56 return;
57 }
58
59 account.setStorageManifestVersion(manifest.get().getVersion());
60
61 final var storageIds = manifest.get().getStorageIds().stream().filter(id -> !id.isUnknown()).toList();
62
63 for (final var record : getSignalStorageRecords(storageIds)) {
64 if (record.getType() == ManifestRecord.Identifier.Type.ACCOUNT_VALUE) {
65 readAccountRecord(record);
66 } else if (record.getType() == ManifestRecord.Identifier.Type.GROUPV2_VALUE) {
67 readGroupV2Record(record);
68 } else if (record.getType() == ManifestRecord.Identifier.Type.GROUPV1_VALUE) {
69 readGroupV1Record(record);
70 } else if (record.getType() == ManifestRecord.Identifier.Type.CONTACT_VALUE) {
71 readContactRecord(record);
72 }
73 }
74 logger.debug("Done reading data from remote storage");
75 }
76
77 private void readContactRecord(final SignalStorageRecord record) {
78 if (record == null || record.getContact().isEmpty()) {
79 return;
80 }
81
82 final var contactRecord = record.getContact().get();
83 final var address = contactRecord.getAddress();
84
85 final var recipientId = account.getRecipientResolver().resolveRecipient(address);
86 final var contact = account.getContactStore().getContact(recipientId);
87 final var blocked = contact != null && contact.isBlocked();
88 final var profileShared = contact != null && contact.isProfileSharingEnabled();
89 if (contactRecord.getGivenName().isPresent()
90 || contactRecord.getFamilyName().isPresent()
91 || blocked != contactRecord.isBlocked()
92 || profileShared != contactRecord.isProfileSharingEnabled()) {
93 final var contactBuilder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
94 final var newContact = contactBuilder.withBlocked(contactRecord.isBlocked())
95 .withGivenName(contactRecord.getGivenName().orElse(null))
96 .withFamilyName(contactRecord.getFamilyName().orElse(null))
97 .withProfileSharingEnabled(contactRecord.isProfileSharingEnabled())
98 .build();
99 account.getContactStore().storeContact(recipientId, newContact);
100 }
101
102 if (contactRecord.getProfileKey().isPresent()) {
103 try {
104 final var profileKey = new ProfileKey(contactRecord.getProfileKey().get());
105 account.getProfileStore().storeProfileKey(recipientId, profileKey);
106 } catch (InvalidInputException e) {
107 logger.warn("Received invalid contact profile key from storage");
108 }
109 }
110 if (contactRecord.getIdentityKey().isPresent()) {
111 try {
112 final var identityKey = new IdentityKey(contactRecord.getIdentityKey().get());
113 account.getIdentityKeyStore().saveIdentity(recipientId, identityKey, new Date());
114
115 final var trustLevel = TrustLevel.fromIdentityState(contactRecord.getIdentityState());
116 if (trustLevel != null) {
117 account.getIdentityKeyStore().setIdentityTrustLevel(recipientId, identityKey, trustLevel);
118 }
119 } catch (InvalidKeyException e) {
120 logger.warn("Received invalid contact identity key from storage");
121 }
122 }
123 }
124
125 private void readGroupV1Record(final SignalStorageRecord record) {
126 if (record == null || record.getGroupV1().isEmpty()) {
127 return;
128 }
129
130 final var groupV1Record = record.getGroupV1().get();
131 final var groupIdV1 = GroupId.v1(groupV1Record.getGroupId());
132
133 var group = account.getGroupStore().getGroup(groupIdV1);
134 if (group == null) {
135 try {
136 context.getGroupHelper().sendGroupInfoRequest(groupIdV1, account.getSelfRecipientId());
137 } catch (Throwable e) {
138 logger.warn("Failed to send group request", e);
139 }
140 group = account.getGroupStore().getOrCreateGroupV1(groupIdV1);
141 }
142 if (group != null && group.isBlocked() != groupV1Record.isBlocked()) {
143 group.setBlocked(groupV1Record.isBlocked());
144 account.getGroupStore().updateGroup(group);
145 }
146 }
147
148 private void readGroupV2Record(final SignalStorageRecord record) {
149 if (record == null || record.getGroupV2().isEmpty()) {
150 return;
151 }
152
153 final var groupV2Record = record.getGroupV2().get();
154 if (groupV2Record.isArchived()) {
155 return;
156 }
157
158 final GroupMasterKey groupMasterKey;
159 try {
160 groupMasterKey = new GroupMasterKey(groupV2Record.getMasterKeyBytes());
161 } catch (InvalidInputException e) {
162 logger.warn("Received invalid group master key from storage");
163 return;
164 }
165
166 final var group = context.getGroupHelper().getOrMigrateGroup(groupMasterKey, 0, null);
167 if (group.isBlocked() != groupV2Record.isBlocked()) {
168 group.setBlocked(groupV2Record.isBlocked());
169 account.getGroupStore().updateGroup(group);
170 }
171 }
172
173 private void readAccountRecord(final SignalStorageRecord record) throws IOException {
174 if (record == null) {
175 logger.warn("Could not find account record, even though we had an ID, ignoring.");
176 return;
177 }
178
179 SignalAccountRecord accountRecord = record.getAccount().orElse(null);
180 if (accountRecord == null) {
181 logger.warn("The storage record didn't actually have an account, ignoring.");
182 return;
183 }
184
185 if (!accountRecord.getE164().equals(account.getNumber())) {
186 context.getAccountHelper().checkWhoAmiI();
187 }
188
189 account.getConfigurationStore().setReadReceipts(accountRecord.isReadReceiptsEnabled());
190 account.getConfigurationStore().setTypingIndicators(accountRecord.isTypingIndicatorsEnabled());
191 account.getConfigurationStore()
192 .setUnidentifiedDeliveryIndicators(accountRecord.isSealedSenderIndicatorsEnabled());
193 account.getConfigurationStore().setLinkPreviews(accountRecord.isLinkPreviewsEnabled());
194 if (accountRecord.getPhoneNumberSharingMode() != AccountRecord.PhoneNumberSharingMode.UNRECOGNIZED) {
195 account.getConfigurationStore()
196 .setPhoneNumberSharingMode(switch (accountRecord.getPhoneNumberSharingMode()) {
197 case EVERYBODY -> PhoneNumberSharingMode.EVERYBODY;
198 case NOBODY -> PhoneNumberSharingMode.NOBODY;
199 default -> PhoneNumberSharingMode.CONTACTS;
200 });
201 }
202 account.getConfigurationStore().setPhoneNumberUnlisted(accountRecord.isPhoneNumberUnlisted());
203
204 if (accountRecord.getProfileKey().isPresent()) {
205 ProfileKey profileKey;
206 try {
207 profileKey = new ProfileKey(accountRecord.getProfileKey().get());
208 } catch (InvalidInputException e) {
209 logger.warn("Received invalid profile key from storage");
210 profileKey = null;
211 }
212 if (profileKey != null) {
213 account.setProfileKey(profileKey);
214 final var avatarPath = accountRecord.getAvatarUrlPath().orElse(null);
215 context.getProfileHelper().downloadProfileAvatar(account.getSelfRecipientId(), avatarPath, profileKey);
216 }
217 }
218
219 context.getProfileHelper()
220 .setProfile(false,
221 false,
222 accountRecord.getGivenName().orElse(null),
223 accountRecord.getFamilyName().orElse(null),
224 null,
225 null,
226 null,
227 null);
228 }
229
230 private SignalStorageRecord getSignalStorageRecord(final StorageId accountId) throws IOException {
231 List<SignalStorageRecord> records;
232 try {
233 records = dependencies.getAccountManager()
234 .readStorageRecords(account.getStorageKey(), Collections.singletonList(accountId));
235 } catch (InvalidKeyException e) {
236 logger.warn("Failed to read storage records, ignoring.");
237 return null;
238 }
239 return records.size() > 0 ? records.get(0) : null;
240 }
241
242 private List<SignalStorageRecord> getSignalStorageRecords(final List<StorageId> storageIds) throws IOException {
243 List<SignalStorageRecord> records;
244 try {
245 records = dependencies.getAccountManager().readStorageRecords(account.getStorageKey(), storageIds);
246 } catch (InvalidKeyException e) {
247 logger.warn("Failed to read storage records, ignoring.");
248 return List.of();
249 }
250 return records;
251 }
252 }