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