]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java
b1a653fd7aa5b19e9758355e712ab33a9a42ec37
[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.Collections;
25 import java.util.Date;
26 import java.util.List;
27 import java.util.Optional;
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.isEmpty()) {
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().isEmpty()) {
83 return;
84 }
85
86 final var contactRecord = record.getContact().get();
87 final var address = contactRecord.getAddress();
88
89 final var recipientId = account.getRecipientResolver().resolveRecipient(address);
90 final var contact = account.getContactStore().getContact(recipientId);
91 final var blocked = contact != null && contact.isBlocked();
92 final var profileShared = contact != null && contact.isProfileSharingEnabled();
93 if (contactRecord.getGivenName().isPresent()
94 || contactRecord.getFamilyName().isPresent()
95 || blocked != contactRecord.isBlocked()
96 || profileShared != contactRecord.isProfileSharingEnabled()) {
97 final var contactBuilder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
98 final var name = contactRecord.getGivenName().orElse("") + " " + contactRecord.getFamilyName().orElse("");
99 final var newContact = contactBuilder.withBlocked(contactRecord.isBlocked())
100 .withName(name.trim())
101 .withProfileSharingEnabled(contactRecord.isProfileSharingEnabled())
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().isEmpty()) {
131 return;
132 }
133
134 final var groupV1Record = record.getGroupV1().get();
135 final var groupIdV1 = GroupId.v1(groupV1Record.getGroupId());
136
137 var group = account.getGroupStore().getGroup(groupIdV1);
138 if (group == null) {
139 try {
140 context.getGroupHelper().sendGroupInfoRequest(groupIdV1, account.getSelfRecipientId());
141 } catch (Throwable e) {
142 logger.warn("Failed to send group request", e);
143 }
144 group = account.getGroupStore().getOrCreateGroupV1(groupIdV1);
145 }
146 if (group != null && group.isBlocked() != groupV1Record.isBlocked()) {
147 group.setBlocked(groupV1Record.isBlocked());
148 account.getGroupStore().updateGroup(group);
149 }
150 }
151
152 private void readGroupV2Record(final SignalStorageRecord record) {
153 if (record == null || record.getGroupV2().isEmpty()) {
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 = context.getGroupHelper().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.isEmpty()) {
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().orElse(null);
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.getNumber())) {
197 context.getAccountHelper().checkWhoAmiI();
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 if (accountRecord.getPhoneNumberSharingMode() != AccountRecord.PhoneNumberSharingMode.UNRECOGNIZED) {
206 account.getConfigurationStore()
207 .setPhoneNumberSharingMode(switch (accountRecord.getPhoneNumberSharingMode()) {
208 case EVERYBODY -> PhoneNumberSharingMode.EVERYBODY;
209 case NOBODY -> PhoneNumberSharingMode.NOBODY;
210 default -> PhoneNumberSharingMode.CONTACTS;
211 });
212 }
213 account.getConfigurationStore().setPhoneNumberUnlisted(accountRecord.isPhoneNumberUnlisted());
214
215 if (accountRecord.getProfileKey().isPresent()) {
216 ProfileKey profileKey;
217 try {
218 profileKey = new ProfileKey(accountRecord.getProfileKey().get());
219 } catch (InvalidInputException e) {
220 logger.warn("Received invalid profile key from storage");
221 profileKey = null;
222 }
223 if (profileKey != null) {
224 account.setProfileKey(profileKey);
225 final var avatarPath = accountRecord.getAvatarUrlPath().orElse(null);
226 context.getProfileHelper().downloadProfileAvatar(account.getSelfRecipientId(), avatarPath, profileKey);
227 }
228 }
229
230 context.getProfileHelper()
231 .setProfile(false,
232 false,
233 accountRecord.getGivenName().orElse(null),
234 accountRecord.getFamilyName().orElse(null),
235 null,
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 }