]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java
393953d949fba9ebc10b7ad05bd0c3c6555cefe2
[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.api.Contact;
4 import org.asamk.signal.manager.api.GroupId;
5 import org.asamk.signal.manager.api.PhoneNumberSharingMode;
6 import org.asamk.signal.manager.api.Profile;
7 import org.asamk.signal.manager.api.TrustLevel;
8 import org.asamk.signal.manager.internal.SignalDependencies;
9 import org.asamk.signal.manager.jobs.DownloadProfileAvatarJob;
10 import org.asamk.signal.manager.storage.SignalAccount;
11 import org.asamk.signal.manager.storage.recipients.RecipientAddress;
12 import org.signal.libsignal.protocol.IdentityKey;
13 import org.signal.libsignal.protocol.InvalidKeyException;
14 import org.signal.libsignal.zkgroup.InvalidInputException;
15 import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
16 import org.signal.libsignal.zkgroup.profiles.ProfileKey;
17 import org.slf4j.Logger;
18 import org.slf4j.LoggerFactory;
19 import org.whispersystems.signalservice.api.storage.SignalAccountRecord;
20 import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
21 import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
22 import org.whispersystems.signalservice.api.storage.StorageId;
23 import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord;
24
25 import java.io.IOException;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.Optional;
31 import java.util.stream.Collectors;
32
33 public class StorageHelper {
34
35 private static final Logger logger = LoggerFactory.getLogger(StorageHelper.class);
36
37 private final SignalAccount account;
38 private final SignalDependencies dependencies;
39 private final Context context;
40
41 public StorageHelper(final Context context) {
42 this.account = context.getAccount();
43 this.dependencies = context.getDependencies();
44 this.context = context;
45 }
46
47 public void readDataFromStorage() throws IOException {
48 final var storageKey = account.getOrCreateStorageKey();
49 if (storageKey == null) {
50 logger.debug("Storage key unknown, requesting from primary device.");
51 context.getSyncHelper().requestSyncKeys();
52 return;
53 }
54
55 logger.debug("Reading data from remote storage");
56 Optional<SignalStorageManifest> manifest;
57 try {
58 manifest = dependencies.getAccountManager()
59 .getStorageManifestIfDifferentVersion(storageKey, account.getStorageManifestVersion());
60 } catch (InvalidKeyException e) {
61 logger.warn("Manifest couldn't be decrypted, ignoring.");
62 return;
63 }
64
65 if (manifest.isEmpty()) {
66 logger.debug("Manifest is up to date, does not exist or couldn't be decrypted, ignoring.");
67 return;
68 }
69
70 logger.trace("Remote storage manifest has {} records", manifest.get().getStorageIds().size());
71 final var storageIds = manifest.get()
72 .getStorageIds()
73 .stream()
74 .filter(id -> !id.isUnknown())
75 .collect(Collectors.toSet());
76
77 Optional<SignalStorageManifest> localManifest = account.getStorageManifest();
78 localManifest.ifPresent(m -> m.getStorageIds().forEach(storageIds::remove));
79
80 logger.trace("Reading {} new records", manifest.get().getStorageIds().size());
81 for (final var record : getSignalStorageRecords(storageIds)) {
82 logger.debug("Reading record of type {}", record.getType());
83 if (record.getType() == ManifestRecord.Identifier.Type.ACCOUNT.getValue()) {
84 readAccountRecord(record);
85 } else if (record.getType() == ManifestRecord.Identifier.Type.GROUPV2.getValue()) {
86 readGroupV2Record(record);
87 } else if (record.getType() == ManifestRecord.Identifier.Type.GROUPV1.getValue()) {
88 readGroupV1Record(record);
89 } else if (record.getType() == ManifestRecord.Identifier.Type.CONTACT.getValue()) {
90 readContactRecord(record);
91 }
92 }
93 account.setStorageManifestVersion(manifest.get().getVersion());
94 account.setStorageManifest(manifest.get());
95 logger.debug("Done reading data from remote storage");
96 }
97
98 private void readContactRecord(final SignalStorageRecord record) {
99 if (record == null || record.getContact().isEmpty()) {
100 return;
101 }
102
103 final var contactRecord = record.getContact().get();
104 final var aci = contactRecord.getAci().orElse(null);
105 final var pni = contactRecord.getPni().orElse(null);
106 if (contactRecord.getNumber().isEmpty() && aci == null && pni == null) {
107 return;
108 }
109 final var address = new RecipientAddress(aci, pni, contactRecord.getNumber().orElse(null));
110 var recipientId = account.getRecipientResolver().resolveRecipient(address);
111 if (aci != null && contactRecord.getUsername().isPresent()) {
112 recipientId = account.getRecipientTrustedResolver()
113 .resolveRecipientTrusted(aci, contactRecord.getUsername().get());
114 }
115
116 final var contact = account.getContactStore().getContact(recipientId);
117 final var blocked = contact != null && contact.isBlocked();
118 final var profileShared = contact != null && contact.isProfileSharingEnabled();
119 final var archived = contact != null && contact.isArchived();
120 final var hidden = contact != null && contact.isHidden();
121 final var contactGivenName = contact == null ? null : contact.givenName();
122 final var contactFamilyName = contact == null ? null : contact.familyName();
123 if (blocked != contactRecord.isBlocked()
124 || profileShared != contactRecord.isProfileSharingEnabled()
125 || archived != contactRecord.isArchived()
126 || hidden != contactRecord.isHidden()
127 || (
128 contactRecord.getSystemGivenName().isPresent() && !contactRecord.getSystemGivenName()
129 .get()
130 .equals(contactGivenName)
131 )
132 || (
133 contactRecord.getSystemFamilyName().isPresent() && !contactRecord.getSystemFamilyName()
134 .get()
135 .equals(contactFamilyName)
136 )) {
137 logger.debug("Storing new or updated contact {}", recipientId);
138 final var contactBuilder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
139 final var newContact = contactBuilder.withIsBlocked(contactRecord.isBlocked())
140 .withIsProfileSharingEnabled(contactRecord.isProfileSharingEnabled())
141 .withIsArchived(contactRecord.isArchived())
142 .withIsHidden(contactRecord.isHidden());
143 if (contactRecord.getSystemGivenName().isPresent() || contactRecord.getSystemFamilyName().isPresent()) {
144 newContact.withGivenName(contactRecord.getSystemGivenName().orElse(null))
145 .withFamilyName(contactRecord.getSystemFamilyName().orElse(null));
146 }
147 account.getContactStore().storeContact(recipientId, newContact.build());
148 }
149
150 final var profile = account.getProfileStore().getProfile(recipientId);
151 final var profileGivenName = profile == null ? null : profile.getGivenName();
152 final var profileFamilyName = profile == null ? null : profile.getFamilyName();
153 if ((
154 contactRecord.getProfileGivenName().isPresent() && !contactRecord.getProfileGivenName()
155 .get()
156 .equals(profileGivenName)
157 ) || (
158 contactRecord.getProfileFamilyName().isPresent() && !contactRecord.getProfileFamilyName()
159 .get()
160 .equals(profileFamilyName)
161 )) {
162 final var profileBuilder = profile == null ? Profile.newBuilder() : Profile.newBuilder(profile);
163 final var newProfile = profileBuilder.withGivenName(contactRecord.getProfileGivenName().orElse(null))
164 .withFamilyName(contactRecord.getProfileFamilyName().orElse(null))
165 .build();
166 account.getProfileStore().storeProfile(recipientId, newProfile);
167 }
168 if (contactRecord.getProfileKey().isPresent()) {
169 try {
170 logger.trace("Storing profile key {}", recipientId);
171 final var profileKey = new ProfileKey(contactRecord.getProfileKey().get());
172 account.getProfileStore().storeProfileKey(recipientId, profileKey);
173 } catch (InvalidInputException e) {
174 logger.warn("Received invalid contact profile key from storage");
175 }
176 }
177 if (contactRecord.getIdentityKey().isPresent() && aci != null) {
178 try {
179 logger.trace("Storing identity key {}", recipientId);
180 final var identityKey = new IdentityKey(contactRecord.getIdentityKey().get());
181 account.getIdentityKeyStore().saveIdentity(aci, identityKey);
182
183 final var trustLevel = TrustLevel.fromIdentityState(contactRecord.getIdentityState());
184 if (trustLevel != null) {
185 account.getIdentityKeyStore().setIdentityTrustLevel(aci, identityKey, trustLevel);
186 }
187 } catch (InvalidKeyException e) {
188 logger.warn("Received invalid contact identity key from storage");
189 }
190 }
191 }
192
193 private void readGroupV1Record(final SignalStorageRecord record) {
194 if (record == null || record.getGroupV1().isEmpty()) {
195 return;
196 }
197
198 final var groupV1Record = record.getGroupV1().get();
199 final var groupIdV1 = GroupId.v1(groupV1Record.getGroupId());
200
201 var group = account.getGroupStore().getGroup(groupIdV1);
202 if (group == null) {
203 try {
204 context.getGroupHelper().sendGroupInfoRequest(groupIdV1, account.getSelfRecipientId());
205 } catch (Throwable e) {
206 logger.warn("Failed to send group request", e);
207 }
208 group = account.getGroupStore().getOrCreateGroupV1(groupIdV1);
209 }
210 if (group != null && group.isBlocked() != groupV1Record.isBlocked()) {
211 group.setBlocked(groupV1Record.isBlocked());
212 account.getGroupStore().updateGroup(group);
213 }
214 }
215
216 private void readGroupV2Record(final SignalStorageRecord record) {
217 if (record == null || record.getGroupV2().isEmpty()) {
218 return;
219 }
220
221 final var groupV2Record = record.getGroupV2().get();
222 if (groupV2Record.isArchived()) {
223 return;
224 }
225
226 final GroupMasterKey groupMasterKey;
227 try {
228 groupMasterKey = new GroupMasterKey(groupV2Record.getMasterKeyBytes());
229 } catch (InvalidInputException e) {
230 logger.warn("Received invalid group master key from storage");
231 return;
232 }
233
234 final var group = context.getGroupHelper().getOrMigrateGroup(groupMasterKey, 0, null);
235 if (group.isBlocked() != groupV2Record.isBlocked()) {
236 group.setBlocked(groupV2Record.isBlocked());
237 account.getGroupStore().updateGroup(group);
238 }
239 }
240
241 private void readAccountRecord(final SignalStorageRecord record) throws IOException {
242 if (record == null) {
243 logger.warn("Could not find account record, even though we had an ID, ignoring.");
244 return;
245 }
246
247 SignalAccountRecord accountRecord = record.getAccount().orElse(null);
248 if (accountRecord == null) {
249 logger.warn("The storage record didn't actually have an account, ignoring.");
250 return;
251 }
252
253 if (!accountRecord.getE164().equals(account.getNumber())) {
254 context.getAccountHelper().checkWhoAmiI();
255 }
256
257 account.getConfigurationStore().setReadReceipts(accountRecord.isReadReceiptsEnabled());
258 account.getConfigurationStore().setTypingIndicators(accountRecord.isTypingIndicatorsEnabled());
259 account.getConfigurationStore()
260 .setUnidentifiedDeliveryIndicators(accountRecord.isSealedSenderIndicatorsEnabled());
261 account.getConfigurationStore().setLinkPreviews(accountRecord.isLinkPreviewsEnabled());
262 account.getConfigurationStore().setPhoneNumberSharingMode(switch (accountRecord.getPhoneNumberSharingMode()) {
263 case EVERYBODY -> PhoneNumberSharingMode.EVERYBODY;
264 case NOBODY, UNKNOWN -> PhoneNumberSharingMode.NOBODY;
265 });
266 account.getConfigurationStore().setPhoneNumberUnlisted(accountRecord.isPhoneNumberUnlisted());
267 account.setUsername(accountRecord.getUsername());
268
269 if (accountRecord.getProfileKey().isPresent()) {
270 ProfileKey profileKey;
271 try {
272 profileKey = new ProfileKey(accountRecord.getProfileKey().get());
273 } catch (InvalidInputException e) {
274 logger.warn("Received invalid profile key from storage");
275 profileKey = null;
276 }
277 if (profileKey != null) {
278 account.setProfileKey(profileKey);
279 final var avatarPath = accountRecord.getAvatarUrlPath().orElse(null);
280 context.getJobExecutor().enqueueJob(new DownloadProfileAvatarJob(avatarPath));
281 }
282 }
283
284 context.getProfileHelper()
285 .setProfile(false,
286 false,
287 accountRecord.getGivenName().orElse(null),
288 accountRecord.getFamilyName().orElse(null),
289 null,
290 null,
291 null,
292 null);
293 }
294
295 private SignalStorageRecord getSignalStorageRecord(final StorageId accountId) throws IOException {
296 List<SignalStorageRecord> records;
297 try {
298 records = dependencies.getAccountManager()
299 .readStorageRecords(account.getStorageKey(), Collections.singletonList(accountId));
300 } catch (InvalidKeyException e) {
301 logger.warn("Failed to read storage records, ignoring.");
302 return null;
303 }
304 return !records.isEmpty() ? records.get(0) : null;
305 }
306
307 private List<SignalStorageRecord> getSignalStorageRecords(final Collection<StorageId> storageIds) throws IOException {
308 List<SignalStorageRecord> records;
309 try {
310 records = dependencies.getAccountManager()
311 .readStorageRecords(account.getStorageKey(), new ArrayList<>(storageIds));
312 } catch (InvalidKeyException e) {
313 logger.warn("Failed to read storage records, ignoring.");
314 return List.of();
315 }
316 return records;
317 }
318 }