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