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