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