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