1 package org
.asamk
.signal
.manager
.storage
.recipients
;
3 import com
.fasterxml
.jackson
.databind
.ObjectMapper
;
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
;
12 import java
.io
.ByteArrayInputStream
;
13 import java
.io
.ByteArrayOutputStream
;
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
;
23 import java
.util
.Optional
;
24 import java
.util
.UUID
;
25 import java
.util
.stream
.Collectors
;
27 public class RecipientStore
{
29 private final static Logger logger
= LoggerFactory
.getLogger(RecipientStore
.class);
31 private final ObjectMapper objectMapper
;
32 private final File file
;
33 private final RecipientMergeHandler recipientMergeHandler
;
35 private final Map
<RecipientId
, SignalServiceAddress
> recipients
;
36 private final Map
<RecipientId
, RecipientId
> recipientsMerged
= new HashMap
<>();
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
,
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
)))),
53 } catch (FileNotFoundException e
) {
54 logger
.debug("Creating new recipient store.");
55 return new RecipientStore(objectMapper
, file
, recipientMergeHandler
, new HashMap
<>(), 0);
59 private RecipientStore(
60 final ObjectMapper objectMapper
,
62 final RecipientMergeHandler recipientMergeHandler
,
63 final Map
<RecipientId
, SignalServiceAddress
> recipients
,
66 this.objectMapper
= objectMapper
;
68 this.recipientMergeHandler
= recipientMergeHandler
;
69 this.recipients
= recipients
;
73 public SignalServiceAddress
resolveServiceAddress(RecipientId recipientId
) {
74 synchronized (recipients
) {
75 while (recipientsMerged
.containsKey(recipientId
)) {
76 recipientId
= recipientsMerged
.get(recipientId
);
78 return recipients
.get(recipientId
);
83 public SignalServiceAddress
resolveServiceAddress(SignalServiceAddress address
) {
84 return resolveServiceAddress(resolveRecipient(address
, true));
87 public RecipientId
resolveRecipient(UUID uuid
) {
88 return resolveRecipient(new SignalServiceAddress(uuid
, null), false);
91 public RecipientId
resolveRecipient(String number
) {
92 return resolveRecipient(new SignalServiceAddress(null, number
), false);
95 public RecipientId
resolveRecipient(SignalServiceAddress address
) {
96 return resolveRecipient(address
, true);
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()));
109 }).collect(Collectors
.toList());
111 for (var pair
: toBeMerged
) {
112 recipientMergeHandler
.mergeRecipients(pair
.first(), pair
.second());
117 public RecipientId
resolveRecipientUntrusted(SignalServiceAddress address
) {
118 return resolveRecipient(address
, false);
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.
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());
134 if (pair
.second().isPresent()) {
135 recipientMergeHandler
.mergeRecipients(pair
.first(), pair
.second().get());
140 private Pair
<RecipientId
, Optional
<RecipientId
>> resolveRecipientLocked(
141 SignalServiceAddress address
, boolean isHighTrust
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());
150 if (byNumber
.isEmpty() && byUuid
.isEmpty()) {
151 logger
.debug("Got new recipient, both uuid and number are unknown");
153 if (isHighTrust
|| !address
.getUuid().isPresent() || !address
.getNumber().isPresent()) {
154 return new Pair
<>(addNewRecipient(address
), Optional
.empty());
157 return new Pair
<>(addNewRecipient(new SignalServiceAddress(address
.getUuid().get(), null)),
162 || !address
.getUuid().isPresent()
163 || !address
.getNumber().isPresent()
164 || byNumber
.equals(byUuid
)) {
165 return new Pair
<>(byUuid
.orElseGet(byNumber
::get
), Optional
.empty());
168 if (byNumber
.isEmpty()) {
169 logger
.debug("Got recipient existing with uuid, updating with high trust number");
170 recipients
.put(byUuid
.get(), address
);
172 return new Pair
<>(byUuid
.get(), Optional
.empty());
175 if (byUuid
.isEmpty()) {
176 logger
.debug("Got recipient existing with number, updating with high trust uuid");
177 recipients
.put(byNumber
.get(), address
);
179 return new Pair
<>(byNumber
.get(), Optional
.empty());
182 final var byNumberAddress
= recipients
.get(byNumber
.get());
183 if (byNumberAddress
.getUuid().isPresent()) {
185 "Got separate recipients for high trust number and uuid, recipient for number has different uuid, so stripping its number");
187 recipients
.put(byNumber
.get(), new SignalServiceAddress(byNumberAddress
.getUuid().get(), null));
188 recipients
.put(byUuid
.get(), address
);
190 return new Pair
<>(byUuid
.get(), Optional
.empty());
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());
197 return new Pair
<>(byUuid
.get(), byNumber
);
200 private RecipientId
addNewRecipient(final SignalServiceAddress serviceAddress
) {
201 final var nextRecipientId
= nextId();
202 recipients
.put(nextRecipientId
, serviceAddress
);
204 return nextRecipientId
;
207 private Optional
<RecipientId
> findByName(final String number
) {
208 return recipients
.entrySet()
210 .filter(entry
-> entry
.getValue().getNumber().isPresent() && number
.equals(entry
.getValue()
214 .map(Map
.Entry
::getKey
);
217 private Optional
<RecipientId
> findByUuid(final UUID uuid
) {
218 return recipients
.entrySet()
220 .filter(entry
-> entry
.getValue().getUuid().isPresent() && uuid
.equals(entry
.getValue()
224 .map(Map
.Entry
::getKey
);
227 private RecipientId
nextId() {
228 return new RecipientId(++this.lastId
);
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()
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
);
242 var input
= new ByteArrayInputStream(inMemoryOutput
.toByteArray());
243 try (var outputStream
= new FileOutputStream(file
)) {
244 input
.transferTo(outputStream
);
246 } catch (Exception e
) {
247 logger
.error("Error saving recipient store file: {}", e
.getMessage());
251 public boolean isEmpty() {
252 synchronized (recipients
) {
253 return recipients
.isEmpty();
257 private static class Storage
{
259 private List
<Recipient
> recipients
;
263 // For deserialization
267 public Storage(final List
<Recipient
> recipients
, final long lastId
) {
268 this.recipients
= recipients
;
269 this.lastId
= lastId
;
272 public List
<Recipient
> getRecipients() {
276 public long getLastId() {
280 public static class Recipient
{
286 // For deserialization
287 private Recipient() {
290 public Recipient(final long id
, final String name
, final String uuid
) {
296 public long getId() {
300 public String
getName() {
304 public String
getUuid() {
310 public interface RecipientMergeHandler
{
312 void mergeRecipients(RecipientId recipientId
, RecipientId toBeMergedRecipientId
);