]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java
63e6ca5980350c2a662712e24b6fe7f0cc8a1dae
[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
36 public StorageHelper(
37 final SignalAccount account, final SignalDependencies dependencies, final GroupHelper groupHelper
38 ) {
39 this.account = account;
40 this.dependencies = dependencies;
41 this.groupHelper = groupHelper;
42 }
43
44 public void readDataFromStorage() throws IOException {
45 logger.debug("Reading data from remote storage");
46 Optional<SignalStorageManifest> manifest;
47 try {
48 manifest = dependencies.getAccountManager()
49 .getStorageManifestIfDifferentVersion(account.getStorageKey(), account.getStorageManifestVersion());
50 } catch (InvalidKeyException e) {
51 logger.warn("Manifest couldn't be decrypted, ignoring.");
52 return;
53 }
54
55 if (!manifest.isPresent()) {
56 logger.debug("Manifest is up to date, does not exist or couldn't be decrypted, ignoring.");
57 return;
58 }
59
60 account.setStorageManifestVersion(manifest.get().getVersion());
61
62 readAccountRecord(manifest.get());
63
64 final var storageIds = manifest.get()
65 .getStorageIds()
66 .stream()
67 .filter(id -> !id.isUnknown() && id.getType() != ManifestRecord.Identifier.Type.ACCOUNT_VALUE)
68 .collect(Collectors.toList());
69
70 for (final var record : getSignalStorageRecords(storageIds)) {
71 if (record.getType() == ManifestRecord.Identifier.Type.GROUPV2_VALUE) {
72 readGroupV2Record(record);
73 } else if (record.getType() == ManifestRecord.Identifier.Type.GROUPV1_VALUE) {
74 readGroupV1Record(record);
75 } else if (record.getType() == ManifestRecord.Identifier.Type.CONTACT_VALUE) {
76 readContactRecord(record);
77 }
78 }
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 final var group = account.getGroupStore().getGroup(groupIdV1);
133 if (group == null) {
134 try {
135 groupHelper.sendGroupInfoRequest(groupIdV1, account.getSelfRecipientId());
136 } catch (Throwable e) {
137 logger.warn("Failed to send group request", e);
138 }
139 }
140 final var groupV1 = account.getGroupStore().getOrCreateGroupV1(groupIdV1);
141 if (groupV1.isBlocked() != groupV1Record.isBlocked()) {
142 groupV1.setBlocked(groupV1Record.isBlocked());
143 account.getGroupStore().updateGroup(groupV1);
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 = groupHelper.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 account.getConfigurationStore().setReadReceipts(accountRecord.isReadReceiptsEnabled());
192 account.getConfigurationStore().setTypingIndicators(accountRecord.isTypingIndicatorsEnabled());
193 account.getConfigurationStore()
194 .setUnidentifiedDeliveryIndicators(accountRecord.isSealedSenderIndicatorsEnabled());
195 account.getConfigurationStore().setLinkPreviews(accountRecord.isLinkPreviewsEnabled());
196
197 if (accountRecord.getProfileKey().isPresent()) {
198 try {
199 account.setProfileKey(new ProfileKey(accountRecord.getProfileKey().get()));
200 } catch (InvalidInputException e) {
201 logger.warn("Received invalid profile key from storage");
202 }
203 }
204 }
205
206 private SignalStorageRecord getSignalStorageRecord(final StorageId accountId) throws IOException {
207 List<SignalStorageRecord> records;
208 try {
209 records = dependencies.getAccountManager()
210 .readStorageRecords(account.getStorageKey(), Collections.singletonList(accountId));
211 } catch (InvalidKeyException e) {
212 logger.warn("Failed to read storage records, ignoring.");
213 return null;
214 }
215 return records.size() > 0 ? records.get(0) : null;
216 }
217
218 private List<SignalStorageRecord> getSignalStorageRecords(final List<StorageId> storageIds) throws IOException {
219 List<SignalStorageRecord> records;
220 try {
221 records = dependencies.getAccountManager().readStorageRecords(account.getStorageKey(), storageIds);
222 } catch (InvalidKeyException e) {
223 logger.warn("Failed to read storage records, ignoring.");
224 return List.of();
225 }
226 return records;
227 }
228 }