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