]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java
533808d885827d6f148519bf2d0dd5dbc724d210
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / storage / recipients / RecipientStore.java
1 package org.asamk.signal.manager.storage.recipients;
2
3 import com.fasterxml.jackson.databind.ObjectMapper;
4
5 import org.asamk.signal.manager.storage.Utils;
6 import org.slf4j.Logger;
7 import org.slf4j.LoggerFactory;
8 import org.whispersystems.libsignal.util.Pair;
9 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
10 import org.whispersystems.signalservice.api.util.UuidUtil;
11
12 import java.io.ByteArrayInputStream;
13 import java.io.ByteArrayOutputStream;
14 import java.io.File;
15 import java.io.FileInputStream;
16 import java.io.FileNotFoundException;
17 import java.io.FileOutputStream;
18 import java.io.IOException;
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Optional;
24 import java.util.UUID;
25 import java.util.stream.Collectors;
26
27 public class RecipientStore {
28
29 private final static Logger logger = LoggerFactory.getLogger(RecipientStore.class);
30
31 private final ObjectMapper objectMapper;
32 private final File file;
33 private final RecipientMergeHandler recipientMergeHandler;
34
35 private final Map<RecipientId, SignalServiceAddress> recipients;
36 private final Map<RecipientId, RecipientId> recipientsMerged = new HashMap<>();
37
38 private long lastId;
39
40 public static RecipientStore load(File file, RecipientMergeHandler recipientMergeHandler) throws IOException {
41 final var objectMapper = Utils.createStorageObjectMapper();
42 try (var inputStream = new FileInputStream(file)) {
43 var storage = objectMapper.readValue(inputStream, Storage.class);
44 return new RecipientStore(objectMapper,
45 file,
46 recipientMergeHandler,
47 storage.recipients.stream()
48 .collect(Collectors.toMap(r -> new RecipientId(r.id),
49 r -> new SignalServiceAddress(org.whispersystems.libsignal.util.guava.Optional.fromNullable(
50 r.uuid).transform(UuidUtil::parseOrThrow),
51 org.whispersystems.libsignal.util.guava.Optional.fromNullable(r.name)))),
52 storage.lastId);
53 } catch (FileNotFoundException e) {
54 logger.debug("Creating new recipient store.");
55 return new RecipientStore(objectMapper, file, recipientMergeHandler, new HashMap<>(), 0);
56 }
57 }
58
59 private RecipientStore(
60 final ObjectMapper objectMapper,
61 final File file,
62 final RecipientMergeHandler recipientMergeHandler,
63 final Map<RecipientId, SignalServiceAddress> recipients,
64 final long lastId
65 ) {
66 this.objectMapper = objectMapper;
67 this.file = file;
68 this.recipientMergeHandler = recipientMergeHandler;
69 this.recipients = recipients;
70 this.lastId = lastId;
71 }
72
73 public SignalServiceAddress resolveServiceAddress(RecipientId recipientId) {
74 synchronized (recipients) {
75 while (recipientsMerged.containsKey(recipientId)) {
76 recipientId = recipientsMerged.get(recipientId);
77 }
78 return recipients.get(recipientId);
79 }
80 }
81
82 @Deprecated
83 public SignalServiceAddress resolveServiceAddress(SignalServiceAddress address) {
84 return resolveServiceAddress(resolveRecipient(address, true));
85 }
86
87 public RecipientId resolveRecipient(UUID uuid) {
88 return resolveRecipient(new SignalServiceAddress(uuid, null), false);
89 }
90
91 public RecipientId resolveRecipient(String number) {
92 return resolveRecipient(new SignalServiceAddress(null, number), false);
93 }
94
95 public RecipientId resolveRecipient(SignalServiceAddress address) {
96 return resolveRecipient(address, true);
97 }
98
99 public List<RecipientId> resolveRecipients(List<SignalServiceAddress> addresses) {
100 final List<RecipientId> recipientIds;
101 final List<Pair<RecipientId, RecipientId>> toBeMerged = new ArrayList<>();
102 synchronized (recipients) {
103 recipientIds = addresses.stream().map(address -> {
104 final var pair = resolveRecipientLocked(address, true);
105 if (pair.second().isPresent()) {
106 toBeMerged.add(new Pair<>(pair.first(), pair.second().get()));
107 }
108 return pair.first();
109 }).collect(Collectors.toList());
110 }
111 for (var pair : toBeMerged) {
112 recipientMergeHandler.mergeRecipients(pair.first(), pair.second());
113 }
114 return recipientIds;
115 }
116
117 public RecipientId resolveRecipientUntrusted(SignalServiceAddress address) {
118 return resolveRecipient(address, false);
119 }
120
121 /**
122 * @param isHighTrust true, if the number/uuid connection was obtained from a trusted source.
123 * Has no effect, if the address contains only a number or a uuid.
124 */
125 private RecipientId resolveRecipient(SignalServiceAddress address, boolean isHighTrust) {
126 final Pair<RecipientId, Optional<RecipientId>> pair;
127 synchronized (recipients) {
128 pair = resolveRecipientLocked(address, isHighTrust);
129 if (pair.second().isPresent()) {
130 recipientsMerged.put(pair.second().get(), pair.first());
131 }
132 }
133
134 if (pair.second().isPresent()) {
135 recipientMergeHandler.mergeRecipients(pair.first(), pair.second().get());
136 }
137 return pair.first();
138 }
139
140 private Pair<RecipientId, Optional<RecipientId>> resolveRecipientLocked(
141 SignalServiceAddress address, boolean isHighTrust
142 ) {
143 final var byNumber = !address.getNumber().isPresent()
144 ? Optional.<RecipientId>empty()
145 : findByName(address.getNumber().get());
146 final var byUuid = !address.getUuid().isPresent()
147 ? Optional.<RecipientId>empty()
148 : findByUuid(address.getUuid().get());
149
150 if (byNumber.isEmpty() && byUuid.isEmpty()) {
151 logger.debug("Got new recipient, both uuid and number are unknown");
152
153 if (isHighTrust || !address.getUuid().isPresent() || !address.getNumber().isPresent()) {
154 return new Pair<>(addNewRecipient(address), Optional.empty());
155 }
156
157 return new Pair<>(addNewRecipient(new SignalServiceAddress(address.getUuid().get(), null)),
158 Optional.empty());
159 }
160
161 if (!isHighTrust
162 || !address.getUuid().isPresent()
163 || !address.getNumber().isPresent()
164 || byNumber.equals(byUuid)) {
165 return new Pair<>(byUuid.orElseGet(byNumber::get), Optional.empty());
166 }
167
168 if (byNumber.isEmpty()) {
169 logger.debug("Got recipient existing with uuid, updating with high trust number");
170 recipients.put(byUuid.get(), address);
171 save();
172 return new Pair<>(byUuid.get(), Optional.empty());
173 }
174
175 if (byUuid.isEmpty()) {
176 logger.debug("Got recipient existing with number, updating with high trust uuid");
177 recipients.put(byNumber.get(), address);
178 save();
179 return new Pair<>(byNumber.get(), Optional.empty());
180 }
181
182 final var byNumberAddress = recipients.get(byNumber.get());
183 if (byNumberAddress.getUuid().isPresent()) {
184 logger.debug(
185 "Got separate recipients for high trust number and uuid, recipient for number has different uuid, so stripping its number");
186
187 recipients.put(byNumber.get(), new SignalServiceAddress(byNumberAddress.getUuid().get(), null));
188 recipients.put(byUuid.get(), address);
189 save();
190 return new Pair<>(byUuid.get(), Optional.empty());
191 }
192
193 logger.debug("Got separate recipients for high trust number and uuid, need to merge them");
194 recipients.put(byUuid.get(), address);
195 recipients.remove(byNumber.get());
196 save();
197 return new Pair<>(byUuid.get(), byNumber);
198 }
199
200 private RecipientId addNewRecipient(final SignalServiceAddress serviceAddress) {
201 final var nextRecipientId = nextId();
202 recipients.put(nextRecipientId, serviceAddress);
203 save();
204 return nextRecipientId;
205 }
206
207 private Optional<RecipientId> findByName(final String number) {
208 return recipients.entrySet()
209 .stream()
210 .filter(entry -> entry.getValue().getNumber().isPresent() && number.equals(entry.getValue()
211 .getNumber()
212 .get()))
213 .findFirst()
214 .map(Map.Entry::getKey);
215 }
216
217 private Optional<RecipientId> findByUuid(final UUID uuid) {
218 return recipients.entrySet()
219 .stream()
220 .filter(entry -> entry.getValue().getUuid().isPresent() && uuid.equals(entry.getValue()
221 .getUuid()
222 .get()))
223 .findFirst()
224 .map(Map.Entry::getKey);
225 }
226
227 private RecipientId nextId() {
228 return new RecipientId(++this.lastId);
229 }
230
231 private void save() {
232 // Write to memory first to prevent corrupting the file in case of serialization errors
233 try (var inMemoryOutput = new ByteArrayOutputStream()) {
234 var storage = new Storage(recipients.entrySet()
235 .stream()
236 .map(pair -> new Storage.Recipient(pair.getKey().getId(),
237 pair.getValue().getNumber().orNull(),
238 pair.getValue().getUuid().transform(UUID::toString).orNull()))
239 .collect(Collectors.toList()), lastId);
240 objectMapper.writeValue(inMemoryOutput, storage);
241
242 var input = new ByteArrayInputStream(inMemoryOutput.toByteArray());
243 try (var outputStream = new FileOutputStream(file)) {
244 input.transferTo(outputStream);
245 }
246 } catch (Exception e) {
247 logger.error("Error saving recipient store file: {}", e.getMessage());
248 }
249 }
250
251 public boolean isEmpty() {
252 synchronized (recipients) {
253 return recipients.isEmpty();
254 }
255 }
256
257 private static class Storage {
258
259 private List<Recipient> recipients;
260
261 private long lastId;
262
263 // For deserialization
264 private Storage() {
265 }
266
267 public Storage(final List<Recipient> recipients, final long lastId) {
268 this.recipients = recipients;
269 this.lastId = lastId;
270 }
271
272 public List<Recipient> getRecipients() {
273 return recipients;
274 }
275
276 public long getLastId() {
277 return lastId;
278 }
279
280 public static class Recipient {
281
282 private long id;
283 private String name;
284 private String uuid;
285
286 // For deserialization
287 private Recipient() {
288 }
289
290 public Recipient(final long id, final String name, final String uuid) {
291 this.id = id;
292 this.name = name;
293 this.uuid = uuid;
294 }
295
296 public long getId() {
297 return id;
298 }
299
300 public String getName() {
301 return name;
302 }
303
304 public String getUuid() {
305 return uuid;
306 }
307 }
308 }
309
310 public interface RecipientMergeHandler {
311
312 void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId);
313 }
314 }